有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我与共享密钥有一对一的双向实体关系。当我试图保存关联的所有者时,我得到一个针对关系的所有者端的“null id generated”异常。我正在使用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);

在hibernate 3.5.0-Final中,这一切都非常好。尝试升级到3.5.6-Final或3.6.0时。最终结果是当我开始获取“为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

顺便说一句,我不确定LeadAffiliate上的注释一开始是否完全正确。他们工作了,但似乎有点笨拙。因此,我将其改为:

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

我需要一种新的方法来做这件事,还是这是一个bug?我担心的是,我的代码目前正在工作,因为有一个bug:/


共 (2) 个答案

  1. # 1 楼答案

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

    2.4.1 Primary Keys Corresponding to Derived Identities

    The identity of an entity may be derived from the identity of another entity (the "parent" entity) when the former entity (the "dependent" entity) is the owner of a many-to-one or one-to-one relationship to the parent entity and a foreign key maps the relationship from dependent to parent.

    在您的例子中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; 
    
        ...
    }
    
  2. # 2 楼答案

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

    无论如何,JPA2.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;
        }
    }
    

    这是一个有效的JPA2.0映射,并可用于EclipseLink。但是,Hibernate不喜欢它,也不会实例化EntityManagerFactory(该死的!)

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

    但是上面的方法应该有效,因为Hibernate中有一个bug(报告为HHH-5695

    参考文献