【问题标题】:Saving Entity with Cached object in it causing Detached Entity Exception保存带有缓存对象的实体导致分离实体异常
【发布时间】:2017-11-07 09:36:07
【问题描述】:

我正在尝试使用 Spring Data/Crud Repository(.save) 在 DB 中保存一个实体,其中包含另一个通过 @Cache 方法加载的实体。换句话说,我正在尝试保存一个包含属性实体的广告实体,并且这些属性是使用 Spring @Cache 加载的。

正因为如此,我将一个分离的实体传递给了 Persist 异常。

我的问题是,有没有办法保存仍然使用@Cache 作为属性的实体?

我查了一下,但找不到任何人在做同样的事情,特别是知道我正在使用只有方法 .save() 的 CrudRepository,据我所知,它管理 Persist、Update、Merge 等。

非常感谢任何帮助。

提前致谢。

Ad.java

@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

@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());
    }

}

加载属性的方法:

@Cacheable(value = "requiredAttributePerCategory", key = "#category.id")
public List<CategoryAttribute> findRequiredCategoryAttributesByCategory(Category category) {

    return categoryAttributeRepository.findCategoryAttributesByCategoryAndAttribute_Required(category, 1);
}

用于创建/保留广告的方法:

@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;
}

【问题讨论】:

  • 请发布您的代码
  • 也许这可以提供一些关于分离实体的见解? stackoverflow.com/questions/8441450/…
  • 刚刚发布了代码,@BikramjitRajbongshi。
  • 会看看,@ValentinGrégoire,谢谢你的建议。我用代码更新了帖子,以防你也想看。谢谢!

标签: spring hibernate jpa spring-data spring-data-jpa


【解决方案1】:

我不知道您是否仍然需要这个答案,因为已经很长时间了,您问了这个问题。但是我要把我的cmets留在这里,其他人可能会从中得到帮助。

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

您必须记住,当前在缓存中的这些值是使用休眠会话获取的,该会话现已关闭。所以它们与任何会话都没有关系,现在处于分离状态。

现在,您正在尝试保存 Ad 实体。为此,spring 创建了一个新的休眠会话,并将 Ad 实体附加到此特定会话。但是您从缓存中获取的属性对象是分离的。这就是为什么当您尝试保留 Ad 实体时,您会收到 Detached Entity Exception

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

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

简单地说,这会将您的对象附加到休眠会话。 你应该怎么做,在调用你的 findRequiredCategoryAttributesByCategory 方法后,写类似

List attributesFromCache = someService.findRequiredCategoryAttributesByCategory();
List attributesAttached = entityManager.merge( attributesFromCache );

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

【讨论】:

    猜你喜欢
    • 2013-09-30
    • 2011-09-02
    • 2014-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-30
    • 2012-08-23
    • 1970-01-01
    相关资源
    最近更新 更多