正在保存实体,其中包含缓存对象,导致分离实体异常



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

正因为如此,我有一个分离的实体被传递给Persist Exception。

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

我查了一下,但找不到任何人做同样的事情,特别是知道我使用的CrudRepository只有.save()方法,据我所知,它管理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;
}

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

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

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

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

若要解决此问题,您需要将这些对象重新附加到当前休眠会话。我使用merge()方法来执行此操作。来自hibernate文档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设置为您的广告对象。这不会引发异常,因为属性列表现在是当前Hibernate会话的一部分。

相关内容

  • 没有找到相关文章

最新更新