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

Posted

技术标签:

【中文标题】如何在 Hibernate 3.6 中正确级联保存主键上的一对一双向关系【英文标题】:How do I properly cascade save a one-to-one, bidirectional relationship on primary key in Hibernate 3.6 【发布时间】:2011-04-30 23:14:45 【问题描述】:

我与共享密钥建立了一对一的双向实体关系。当我尝试保存关联的所有者时,我得到一个针对关系拥有方的“生成空 id”异常。我正在使用 hibernate-entitymanager 并使用 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)

我是否需要一种新的方法来执行此操作,或者这是一个错误?我担心的是我的代码由于错误而目前正在运行:/。

【问题讨论】:

我使用普通的 JPA 1.0 来实现你的目标请参阅 ***.com/questions/2001007/2039553#2039553 只需将 Hibernate Cascade 注释替换为其 JPA 对应项 【参考方案1】:

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

2.4.1 派生身份对应的主键

一个人的身份 实体可能来源于 另一个实体的身份( “父”实体)当前者 实体(“依赖”实体)是 多对一或一对一的所有者 与母公司的关系和 外键映射关系 从受抚养人到父母。

在您的情况下,LeadAffiliate 是派生的,所以它应该是所有者,而 Lead 应该被 mappedBy 标记为非拥有方。以下适用于 3.5.0 和 3.5.6:

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; 

    ...

【讨论】:

不幸的是,Hibernate 不喜欢@Id 方法(需要填补一个新的错误......)。所以 +1 是一个适用于 Hibernate 的解决方案。 我已确认此更改适用于 3.5.6 我确实必须添加一个 @JoinColumn 注释,因为它试图使用错误的列名。但在那之后它运作良好。我还验证了它在 3.6.0 中有效。听起来确实像,根据文档,这是休眠 3.5 中的一个缺陷,它允许我的原始注释工作。 在这个问题上卡了 2 天,这个答案肯定会得到更多的支持【参考方案2】:

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

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

更新:正如 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(该死!)。

作为解决方法,您必须使用solution suggested by axtavt 即声明主键属性以及关系属性,并在关系属性上使用MapsId

但是上面应该可以工作,IMO 在 Hibernate 中有一个错误(报告为HHH-5695)。

参考文献

JPA 2.0 规范 第 2.4.1 节“派生身份对应的主键”(冗长,涵盖很多情况) JPA 维基书 Primary Keys through OneToOne Relationships

【讨论】:

我使用 OpenJPA 2.2.2,这对我有用(JPA 2.0)

以上是关于如何在 Hibernate 3.6 中正确级联保存主键上的一对一双向关系的主要内容,如果未能解决你的问题,请参考以下文章

级联保存不适用于 Hibernate 4 和 @OneToMany

Hibernate--cascade级联

Hibernate:OneToMany 通过级联保存孩子

hibernate---级联保存级联删除

hibernate级联修改之后接着查询时,查不出来?,用hibernate做的返回对象为null,求解

hibernate中多表映射关系配置