Reputation: 413
I'm having problems in saving an entity that has child objects attached. The following code is throwing "org.hibernate.PersistentObjectException: detached entity passed to persist: nl.test.api.domain.attribute.Attribute" when trying to save the Ad entity, knowing that it has Cascade.ALL in its child object field. Important to say that I'm using IdClass for composite primary key and using objects Ad and Attribute as parts of the composite primary key of AdAttribute.
I understand what the exception means but either way I'm not able to fix it, specially because the creation method is Transactional. Any thoughts?
The root/Ad object:
@Entity
@DynamicInsert
@DynamicUpdate
@Table(name = "ad")
public class Ad implements SearchableAdDefinition {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
private User user;
@OneToMany(mappedBy = "ad", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<AdAttribute> adAttributes;
(...)
}
The "Child object" (which contains a composite key in which the @ManyToOne objects are part of the key)
@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;
}
}
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());
}
And finally, the method I create the parent object (Ad) and attach the child objects (Ad Attributes):
@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> adAttributes) {
Ad ad = new Ad();
ad.setTitle(title);
ad.setUser(user);
ad.setCategory(category);
ad.setStatus(status);
ad.setDescription(description);
ad.setUrl(url);
ad.setPrice(price);
ad.setPriceType(priceType);
ad.setPhotoCount(photoCount);
ad.setMinimumBid(minimumBid);
ad.setOptions(options);
ad.setImporter(importer);
ad.setAdAttributes(adAttributes);
for (AdAttribute adAttribute : ad.getAdAttributes()) {
adAttribute.setAd(ad);
}
ad = adRepository.save(ad);
solrAdDocumentRepository.save(AdDocument.adDocumentBuilder(ad));
return ad;
}
Upvotes: 0
Views: 1053
Reputation: 3131
The PersistentObjectException ...detached entity passed to persist...
is thrown because Attribute
is not a transient instance, and AFAIK, your second level cache has nothing to do here!
Check if you're persisting Attribute
entity with an assigned id
, either directly or via other Entity
using CascadeType.PERSIST/ALL
association,
To solve it, you can use EntityManager.merge, but It's better to review your persistence and cascade strategy to better control your data flow, and do never expose a method that modify a primary key (except in a contructor if you're not using the auto-generated strategy).
You may check other possible solutions in this SO thread as well for your exception!
Note:
In CompositeAdAttributePk.equals
use instanceOf
instead of getClass()
as hibernate may use a proxy + you should check for null id
as well.
...futhermore avoid using surrogate id
in equals/hasCode
methods if you're using auto-generated identity, as it's not constant throughout all entity states, use business key instead, I suggest you to read about implementing Equals and HashCode in Hibernate, as It states:
...Instead of using the database identifier for the equality comparison, you should use a set of properties for equals() that identify your individual objects. For example, if you have an "Item" class and it has a "name" String and "created" Date, I can use both to implement a good equals() method. No need to use the persistent identifier, the so called "business key" is much better. It's a natural key, but this time there is nothing wrong in using it!
Hope it helps!
Upvotes: 1