Hibernate 3.6 で主キーの 1 対 1 の双方向リレーションシップを適切にカスケード保存するにはどうすればよいですか
質問
共有キーとの 1 対 1 の双方向エンティティ関係があります。関連付けの所有者を保存しようとすると、関係の所有者側に対して「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.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
余談ですが、そもそも 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 では動作しません)
これを行うために必要な新しい方法はありますか、それともバグですか?私の懸念は、バグのせいで私のコードが現在動作していることです:/。
解決
仕様では、派生エンティティが関係の所有側である必要があると記載されています。
2.4.1 派生 ID に対応する主キー
エンティティのアイデンティティは、以前のエンティティ(「依存」エンティティ)が多面または1対1の関係の所有者である場合、別のエンティティ(「親」エンティティ)のIDから派生する場合があります。親エンティティと外国キーは、依存から親への関係をマッピングします。
あなたの場合 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 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
関係属性について。
しかし、上記は機能するはずです。Hibernate には IMO のバグがあります (次のように報告されています)。 HHH-5695).
参考文献
- JPA 2.0仕様
- セクション 2.4.1「派生 ID に対応する主キー」 (長く、多くのケースをカバーしています)
- JPA ウィキブック