如何在 Hibernate 3.6 中的主键上正确级联保存一对一的双向关系

2022-09-01 03:12:53

我与共享密钥有一对一的双向实体关系。当我尝试保存关联的所有者时,我收到针对关系的拥有方的“空 id 生成”异常。我正在利用休眠实体管理器并使用spring进行事务管理。

拥有实体

@Entity
@Table(name = "lead")
public class Lead
{
    private Long leadId;

    private LeadAffiliate leadAffiliate;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getLeadId()
    {
        return leadId;
    }

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    public LeadAffiliate getLeadAffiliate()
    {
        return leadAffiliate;
    }
}

自有实体

@Entity
@Table(name = "lead_affiliate")
public class LeadAffiliate
{
    private Long leadId;

    private Lead lead;

    @Id
    public Long getLeadId()
    {
        return leadId;
    }

    @MapsIdmappedBy = "leadAffiliate")
    @OneToOne(cascade = CascadeType.All)
    @PrimaryKeyJoinColumn
    @JoinColumn(name = "lead_id")
    public Lead getLead()
    {
        return lead;
    }
}

并且下面的代码用于保存实体:

LeadAffiliate aff = new LeadAffiliate();

aff.setLead(lead);
lead.setLeadAffiliate(aff);

em.persist(lead);

这一切都在休眠3.5.0-Final中工作得很好。当尝试升级到3.5.6-Final或3.6.0.Final时,当我开始获取“为LeadAffiliate生成的空ID”错误时:

javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.sellingsource.bizdev.entities.LeadAffiliate
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1214)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1147)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1153)
    at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:678)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:365)
    at $Proxy152.persist(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
    at $Proxy120.persist(Unknown Source)
    at com.sellingsource.common.dao.JpaGenericDao.create(JpaGenericDao.java:38)
    ... 64 more
Caused by: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.sellingsource.bizdev.entities.LeadAffiliate
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:123)
    at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:69)
    at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:179)
    at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
    at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:799)
    at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:791)
    at org.hibernate.engine.EJB3CascadingAction$1.cascade(EJB3CascadingAction.java:48)
    at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
    at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
    at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
    at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
    at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:450)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:282)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:129)
    at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:69)
    at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:179)
    at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
    at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
    at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:808)
    at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:782)
    at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:786)
    at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:672)
    ... 77 more

顺便说一句,我不确定Lead Affiliate上的注释是否一开始就是正确的。他们工作了,但似乎有点笨拙。因此,我已将它们更改为:

@Entity
@Table(name = "lead_affiliate")
public class LeadAffiliate
{
    private Long leadId;

    private Lead lead;

    @Id
    @GenericGenerator(name = "foreign", strategy = "foreign", parameters = {
                    @org.hibernate.annotations.Parameter(name = "property", value="lead")
    })
    @GeneratedValue(generator = "foreign")
    public Long getLeadId()
    {
        return leadId;
    }

    @OneToOne(mappedBy = "leadAffiliate")
    @PrimaryKeyJoinColumn
    public Lead getLead()
    {
        return lead;
    }
}

但是,通过这些更改,我得到了相同的结果。(适用于 3.5.0,但不适用于 3.5.6 或 3.6.0)

有没有一种新的方法来做到这一点,或者这是一个错误?我担心的是,由于一个错误:/,我的代码目前正在工作。


答案 1

规范说派生实体应该是关系的拥有方:

2.4.1 与派生身份对应的主键

当前一个实体(“从属”实体)是与父实体的多对一或一对一关系的所有者并且外键将关系从从从属实体映射到父实体时,实体的标识可以从另一个实体(“父”实体)的标识派生。

在你的情况下是派生的,所以它应该是所有者,当应该被标记为非拥有方。以下内容适用于 3.5.0 和 3.5.6:LeadAffiliate LeadmappedBy

public class Lead { 
    @Id @GeneratedValue
    private Long leadId; 
 
    @OneToOne(cascade = CascadeType.ALL, mappedBy = "lead")
    private LeadAffiliate leadAffiliate; 

    ...
}

.

public class LeadAffiliate {  
    @Id
    private Long leadId;  
  
    @OneToOne @MapsId
    private Lead lead; 

    ...
}

答案 2

我的答案不会解释为什么Hibernate 3.5.0-Final可以工作,但不能使用3.5.6-Final或3.6.0.Final(你应该报告这一点,我称之为回归)。

无论如何,派生标识符在 JPA 2.0 中以标准方式得到更好的支持,在你的情况下,我认为你可以简单地用注释来注释你的关系。OneToOneId

更新:正如 axtavt 所强调的,当使用派生标识符时,“依赖”实体必须是关系的所有者。因此,依赖实体的完整映射将是:

@Entity
@Table(name = "lead_affiliate")
public class LeadAffiliate {
    private Lead lead;

    @Id
    @OneToOne
    @JoinColumn(name="FK")
    public Lead getLead() {
        return lead;
    }
}

和“父”实体:

@Entity
@Table(name = "lead")
public class Lead {
    private Long leadId;

    private LeadAffiliate leadAffiliate;

    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getLeadId() {
        return leadId;
    }

    @OneToOne(cascade = CascadeType.ALL, mappedBy="lead")
    public LeadAffiliate getLeadAffiliate() {
        return leadAffiliate;
    }
}

这是一个有效的 JPA 2.0 映射,可与 EclipseLink 配合使用。但是,Hibernate不喜欢它,也不会实例化(该死的!EntityManagerFactory

作为解决方法,您必须使用 axtavt 建议的解决方案,即声明主键属性以及关系属性,并在关系属性上使用。MapsId

但是上述应该有效,Hibernate中有一个IMO错误(报告为HHH-5695)。

引用

  • JPA 2.0 规范
    • 第 2.4.1 节 “对应于派生身份的主键”(冗长,涵盖许多情况)
  • JPA维基教科书

推荐