假设我们有一些看起来像这样的实体(我试着保持简单):
@Entity
class MainEntity {
//nothing too special here
}
@Entity
class DependentEntity implements Serializable {
@Id
@OneToOne( fetch = FetchType.LAZY, optional = false )
@JoinColumn( name = "mainEntityId", insertable = false, updatable = false, nullable = false )
private MainEntity mainEntity;
}
@Entity
@IdClass(AnotherDependentEntityId.class)
class AnotherDependentEntity {
@Id
@ManyToOne( fetch = FetchType.LAZY, optional = false )
@JoinColumn( name = "mainEntityId", insertable = false, updatable = false, nullable = false )
private MainEntity mainEntity;
@Id
@Column ( name = "type", insertable = false, updatable = false, nullable = false )
private SomeEnum type;
}
正如您所看到的,每个DependentEntity
最多只能有一个MainEntity
,但AnotherDependentEntity
多个SomeEnum
- 每个值MainEntity
都有一个。
现在每当有人创建DependentEntity
时,我们都希望创建必要的AnotherDependentEntity
和PostInsertEventListener
个实例。我们正尝试通过Session subSession = pEvent.getSession().sessionWithOptions()
.autoClose( true)
.autoJoinTransactions( true )
.noInterceptor()
.openSession();
subSession.saveOrUpdate( new DependentEntity( mainEntity ) );
subSession.saveOrUpdate( new AnotherDependentEntity ( mainEntity, SomeEnum.VALUE1 ) );
和从属子会话来完成此操作,如下所示:
DependentEntity
它适用于AnotherDependentEntity
但不适用于saveOrUpdate()
,我们无法弄清楚原因。
我们收到第二条AnotherDependentEntity
的以下消息:
HHH000346:托管刷新期间出错[org.hibernate.HibernateException:复合标识符的任何部分都不能为null]
(在org.hibernate.tuple.entity.AbstractEntityTuplizer $ IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller.getIdentifier(AbstractEntityTuplizer.java:367))
调试代码时,似乎当执行mainEntity
上的持久性时mainEntity
属性为空,即使之前已设置(并且原始会话中的相同调试位置已正确)也是如此)。
这会导致AnotherDependentEntity
必须以某种方式设置为null的印象,即使看到断点的值也没有被触发,所以它必须通过反射发生(我们验证它是相同的{{1两种情况下的实例)。
所以这就是问题所在:
有谁知道为什么/ id的哪一部分被设置为null?也许我们做错了,并且有更好的方法(请注意:我们无法在MainEntity
中添加引用,只是因为几个原因让它级联起来)。
环境:带有Hibernate 5.2.7的Wildfly 10.1.0
修改
我们观察到的另一件事是:在调试时,可以看到实体persister同时引用了实体和id-class的实例(即AnotherDependentEntityId
)。 id-class包含两个值,即两个字段都为null,而实际实体的id已部分为空。
答案 0 :(得分:1)
我在hibernate.atlassian中发现了一个问题。试试这个解决方案......
答案 1 :(得分:1)
经过几个小时的调试后,情况变得更加清晰:
情况分析
最终将为实体调用AbstractEntityTuplizer.setIdentifier(...)
,之后引用mainEntity
变为空。
该方法的主体基本上是这样的:
if ( entityMetamodel.getIdentifierProperty().isEmbedded() ) {
if ( entity != id ) {
//copy the id (snipped for simplicity)
}
}
... //check and handling for a setter, not relevant here
else if ( identifierMapperType != null ) {
mappedIdentifierValueMarshaller.setIdentifier( entity, id, getEntityMode(), session );
}
在我们的案例中,mappedIdentifierValueMarshaller
已被调用,正如您可能猜到的那样,它是IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller
的一个实例。
该marshaller同时获取实体(其中已正确设置引用mainEntity
)以及包含字段Long mainEntity
的id-class实例(主实体的id为Long
)也已正确设置。
现在,编组人员被命名为#34;非常愚蠢",决定它需要通过id-class中的值查找id组件并相应地设置实体上的属性。但是,由于我们在新会话中操作,试图在持久化上下文中找到引用的MainEntity
失败(持久化上下文是一个新的空实例),因此mainEntity
被设置为null反射。
建议修复
到目前为止,我们现在知道会发生什么。但我们该如何解决呢?
不幸的是,似乎没有简单的方法来注册" MainEntity
与新会话的持久性上下文和艰难的路线(通过演员表等)看起来不明智,原因可能并不容易。
但是,由于它适用于DependentEntity
,因此必须有所不同。使用该实体,tuplizer直接进入第一个分支,即标识符似乎被映射为" embedded"并且entity
和id
都是同一个实例,因此setIdentifier(...)
没有做任何事情。
事实证明,有什么区别是id-class的存在。删除它并使AnotherDependentEntity
实施Serializable
可以解决问题,并且据我们所知,没有任何副作用(如果我们有兴趣让某人指出它们)。< / p>
该实体现在看起来像这样:
@Entity
class AnotherDependentEntity implements Serializable {
@Id
@ManyToOne( fetch = FetchType.LAZY, optional = false )
@JoinColumn( name = "mainEntityId", insertable = false, updatable = false, nullable = false )
private MainEntity mainEntity;
@Id
@Column ( name = "type", insertable = false, updatable = false, nullable = false )
private SomeEnum type;
}
有人可能会问&#34;如果没有id-class,我该如何使用EntityManager.find(...)
?&#34;。以下是它的工作方式:
Long mainEntityId = ...;
SomeEnum type = ...;
//We only have the main entity's id so we need a reference
MainEntity mainEntityRef = entityManager.getReference( MainEntity .class, mainEntityId );
//Create a "prototype" for the lookup, only the id values are relevant
AnotherDependentEntity lookupEntity = new AnotherDependentEntity( mainEntityRef, type);
//Now perform the actual find, i.e. find the entity which matches the id of the lookup entity
AnotherDependentEntity actualEntity = entityManager.find( AnotherDependentEntity.class, lookupEntity );