web-dev-qa-db-ja.com

Hibernate 3.6で主キーの1対1の双方向の関係を適切にカスケード保存するにはどうすればよいですか

共有キーと1対1の双方向エンティティ関係があります。アソシエーションの所有者を保存しようとすると、リレーションシップの所有側に対して「null 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);

これはすべて、hibernate 3.5.0-Finalで完全に機能します。 3.5.6-Finalまたは3.6.0.Finalにアップグレードしようとすると、「LeadAffiliate用に生成されたnull 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

余談ですが、リードアフィリエイトに関する注釈が最初から非常に正しいかどうかはわかりません。彼らは働いたが、ちょっとしたように見えた。だから私はそれらを次のように変更しました:

@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.6.0では動作しますが、3.5.6または3.6.0では動作しません)

これを行う必要がある新しい方法はありますか、これはバグですか?私の懸念は、私のコードがバグのために現在動作していることです:/。

37
Mike Lively

仕様では、派生エンティティが関係の所有側である必要があるとされています。

2.4.1導出されたIDに対応する主キー

エンティティのアイデンティティは、前のエンティティ(「依存」エンティティ)が多対1または1対1の関係の所有者である場合、別のエンティティ(「親」エンティティ)のアイデンティティから派生する場合があります。親エンティティと外部キーは、依存関係から親への関係をマップします。

あなたの場合、LeadAffiliateは派生しているので、LeadmappedByによって非所有側としてマークされる必要がある場合、それは所有者でなければなりません。以下は、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; 

    ...
}
40
axtavt

私の答えでは、Hibernate 3.5.0-Finalで動作する理由を説明しませんが、3.5.6-Finalまたは3.6.0.Finalでは動作しません(これを報告する必要があり、これを回帰と呼びます)。

とにかく、派生した識別子はJPA 2.0で標準的な方法ではるかによくサポートされており、あなたの場合、OneToOne関係にId注釈を付けるだけでよいと思います。

更新: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を使用します。

しかし、上記は動作するはずです。IMOにはHibernateのバグがあります( HHH-5695 として報告されます)。

参照資料

9
Pascal Thivent