user2116499
user2116499

Reputation: 413

Detached entity passed to persist when using Composite Key with Objects

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

Answers (1)

O.Badr
O.Badr

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

Related Questions