提问者:小点点

将复合键与对象一起使用时传递给持久化的分离实体


我在保存附加了子对象的实体时遇到了问题。以下代码在尝试保存Ad实体时抛出“org. hibernate.PerstientObjectException:传递给持久化的分离实体:nl.test.api.domain.属性。属性”,因为知道它的子对象字段中有Cascade.ALL。重要的是,我使用IdClass作为复合主键,并使用对象Ad和属性作为AdAt的复合主键的一部分。

我理解异常的含义,但无论哪种方式我都无法修复它,特别是因为创建方法是事务性的。有什么想法吗?

根/广告对象:

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

(...)
}

“子对象”(包含一个复合键,其中@ManyToOne对象是键的一部分)

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

最后,我创建父对象(Ad)并附加子对象(Ad属性)的方法:

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

共1个答案

匿名用户

被抛出是因为属性不是瞬态实例,AFAIK,您的二级缓存与此无关!

检查您是否使用CascadeType. PERSIST/ALL关联直接或通过其他Entity使用分配的id实体持久化属性实体,

要解决这个问题,您可以使用EntityManager. merge,但是最好检查您的持久性和级联策略以更好地控制您的数据流,并且永远不要公开修改主键的方法(除非在配置器中,如果您不使用自动生成的策略)。

您也可以在此SO线程中检查其他可能的解决方案以及您的异常!

注:

CompositeAdAtbantePk. equals中使用instanceOf而不是getClass(),因为Hibernate可能会使用代理,您也应该检查nullid

…此外,如果您使用自动生成的标识,请避免在equals/hasCode方法中使用surrogateid,因为它在所有实体状态中都不是恒定的,请使用业务密钥,我建议您阅读有关在Hibernate中实现Equals和HashCode的信息,因为它指出:

…与其使用数据库标识符进行相等比较,你应该使用一组用于equals()的属性来标识你的各个对象。例如,如果你有一个“Item”类,它有一个“name”String和“创建”Date,我可以同时使用两者来实现一个好的equals()方法。不需要使用持久标识符,所谓的“业务键”要好得多。它是一个自然键,但这次使用它没有错!

希望有帮助!