首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >保存具有缓存对象的实体会导致分离实体异常。

保存具有缓存对象的实体会导致分离实体异常。
EN

Stack Overflow用户
提问于 2017-11-07 09:36:07
回答 1查看 1K关注 0票数 2

我试图使用Spring /(.save)在DB中保存一个实体,其中包含通过@Cache方法加载的另一个实体。换句话说,我试图保存一个包含属性实体的Ad实体,这些属性是使用Spring @Cache加载的。

正因为如此,我让一个独立的实体被传递给持久化异常。

我的问题是,是否有一种方法可以保存仍然使用@Cache作为属性的实体?

我查了一下,但找不到任何做同样事情的人,特别是知道我使用的CrudRepository只有.save()方法,据我所知,它管理持久化、更新、合并等。

任何帮助都是非常感谢的。

提前谢谢。

Ad.java

代码语言:javascript
代码运行次数:0
运行
复制
@Entity
@DynamicInsert
@DynamicUpdate
@Table(name = "ad")
public class Ad implements SearchableAdDefinition {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private User user;

    @OneToMany(mappedBy = "ad", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Set<AdAttribute> adAttributes;

(.....) }

AdAttribute.java

代码语言:javascript
代码运行次数:0
运行
复制
@Entity
@Table(name = "attrib_ad")
@IdClass(CompositeAdAttributePk.class)
public class AdAttribute {

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ad_id")
    private Ad ad;

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "attrib_id")
    private Attribute attribute;

    @Column(name = "value", length = 75)
    private String value;

    public Ad getAd() {
        return ad;
    }

    public void setAd(Ad ad) {
        this.ad = ad;
    }

    public Attribute getAttribute() {
        return attribute;
    }

    public void setAttribute(Attribute attribute) {
        this.attribute = attribute;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}


@Embeddable
 class CompositeAdAttributePk implements Serializable {
    private Ad ad;
    private Attribute attribute;

    public CompositeAdAttributePk() {

    }

    public CompositeAdAttributePk(Ad ad, Attribute attribute) {
        this.ad = ad;
        this.attribute = attribute;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CompositeAdAttributePk compositeAdAttributePk = (CompositeAdAttributePk) o;
        return ad.getId().equals(compositeAdAttributePk.ad.getId()) && attribute.getId().equals(compositeAdAttributePk.attribute.getId());

    }

    @Override
    public int hashCode() {
        return Objects.hash(ad.getId(), attribute.getId());
    }

}

方法用于加载属性:

代码语言:javascript
代码运行次数:0
运行
复制
@Cacheable(value = "requiredAttributePerCategory", key = "#category.id")
public List<CategoryAttribute> findRequiredCategoryAttributesByCategory(Category category) {

    return categoryAttributeRepository.findCategoryAttributesByCategoryAndAttribute_Required(category, 1);
}

方法用于创建/持久化Ad:

代码语言:javascript
代码运行次数:0
运行
复制
@Transactional
public Ad create(String title, User user, Category category, AdStatus status, String description, String url, Double price, AdPriceType priceType, Integer photoCount, Double minimumBid, Integer options, Importer importer, Set<AdAttribute> adAtributes) {
    //Assert.notNull(title, "Ad title must not be null");

    Ad ad = adCreationService.createAd(title, user, category, status, description, url, price, priceType, photoCount, minimumBid, options, importer, adAtributes);

    for (AdAttribute adAttribute : ad.getAdAttributes()) {
        adAttribute.setAd(ad);

/* If I add this here, I don't face any exception, but then I don't take benefit from using cache:
        Attribute attribute = attributeRepository.findById(adAttribute.getAttribute().getId()).get();
        adAttribute.setAttribute(attribute);
*/

    }

    ad = adRepository.save(ad);

    solrAdDocumentRepository.save(AdDocument.adDocumentBuilder(ad));

    return ad;
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-10-17 07:40:07

我不知道你是否还需要这个答案,因为这是很长的时间,你问了这个问题。然而,我将把我的意见留在这里,其他人可能会从中得到帮助。

假设您从应用程序的其他部分调用了您的findRequiredCategoryAttributesByCategory方法。Spring将首先在缓存中检查,然后什么也找不到。然后,它将尝试从数据库中获取它。因此,它将创建一个hibernate会话,打开一个事务,获取数据,关闭事务和会话。最后,从函数返回后,它将将结果集存储在缓存中,以供以后使用。

您必须记住,当前缓存中的这些值是使用hibernate会话获取的,该会话现在已关闭。因此,它们与任何会话无关,现在处于分离状态。

现在,您正在尝试保存和Ad实体。为此,spring创建了一个新的hibernate会话,Ad实体被附加到这个特定的会话中。但是从缓存中获取的attributes对象是分离的。这就是为什么当您试图持久化Ad实体时,您得到的是分离的实体异常

要解决这个问题,您需要将这些对象重新附加到当前的hibernate会话中,我使用merge()方法进行此操作。来自hibernate文档,这里是https://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/Session.html

将给定对象的状态复制到具有相同标识符的持久对象上。如果当前没有与会话相关联的持久实例,则将加载该实例。返回持久实例。如果未保存给定实例,请保存该实例的副本并将其作为新的持久实例返回。给定实例不会与会话关联。如果关联与cascade=“合并”映射,此操作将级联到关联实例。

简单地说,这将将对象附加到hibernate会话。在调用findRequiredCategoryAttributesByCategory方法之后,您应该做什么,编写如下内容

代码语言:javascript
代码运行次数:0
运行
复制
List attributesFromCache = someService.findRequiredCategoryAttributesByCategory();
List attributesAttached = entityManager.merge( attributesFromCache );

现在将attributesAttached设置为Ad对象。这不会引发异常,因为属性列表现在是当前Hibernate会话的一部分。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47154380

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档