0

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;
}
user2116499
  • 403
  • 1
  • 5
  • 15
  • You're setting `adAttribute.setAd(ad)`, but I can't see `adAttribute.setAttribute(attribute`)? It's mandatory to set the two `@ids` before saving, and it's recommended to not use a setter for `@Id` but use a constructor with arguments instead! – O.Badr Nov 07 '17 at 00:14
  • The attribute is already set in the adAttribute in a method that calls the create method (and is also transactional). In that case, shouldn't it be working? Thanks for the reply. – user2116499 Nov 07 '17 at 08:41
  • I actually have an idea of what's going on. The first save/create works. But the second doesn't and says the Attribute is dettached. I'm using cacheable to store the Attributes. I guess then the second time it's already cached and not in context, causing the issue. Any ideas on how to fix this without having to remove cache? Thanks – user2116499 Nov 07 '17 at 08:58
  • (Sorry for being late), the exception is about saving `Attribute` entity, and I don't think that saving `Ad` is involved here, can you add exception stack trace? – O.Badr Nov 13 '17 at 06:37

1 Answers1

1

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!

O.Badr
  • 2,853
  • 2
  • 27
  • 36