Java 在侦听器中创建子实体时出现HibernateeException

Java 在侦听器中创建子实体时出现HibernateeException,java,hibernate,Java,Hibernate,假设我们有一些看起来像这样的实体(我会尽量保持简单): 正如您所看到的,每个main实体最多可以有一个依赖性,但多个另一个依赖性——每个SomeEnum的值都有一个 现在,每当有人创建一个main实体时,我们都希望创建必要的依赖性以及另一个依赖性实例。我们试图通过一个PostInsertEventListener和一个相关子会话来实现这一点,如下所示: Session subSession = pEvent.getSession().sessionWithOptions()

假设我们有一些看起来像这样的实体(我会尽量保持简单):

正如您所看到的,每个
main实体最多可以有一个
依赖性
,但多个
另一个依赖性
——每个
SomeEnum的值都有一个

现在,每当有人创建一个
main实体
时,我们都希望创建必要的
依赖性
以及另一个
依赖性
实例。我们试图通过一个
PostInsertEventListener
和一个相关子会话来实现这一点,如下所示:

Session subSession = pEvent.getSession().sessionWithOptions()         
    .autoClose( true)        
    .autoJoinTransactions( true )        
    .noInterceptor()
    .openSession();

subSession.saveOrUpdate( new DependentEntity( mainEntity ) );
subSession.saveOrUpdate( new AnotherDependentEntity ( mainEntity, SomeEnum.VALUE1 ) );
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 );
    }
@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;
}
它适用于
依赖性
,但不适用于
其他依赖性
,我们无法找出原因

对于第二个
saveOrUpdate()
,我们得到以下消息:

HH000346:托管刷新期间出错[org.hibernate.HibernateException:复合标识符的任何部分都不能为空]
(在org.hibernate.tuple.entity.AbstractEntityTuplizer$IncremediblyJPamapsidMappedIdentifierValueMarshaller.getIdentifier中(AbstractEntityTuplizer.java:367))

调试代码时,似乎在执行另一个依赖项上的persist时,
maintentity
属性为null,即使它以前设置过(并且原始会话中的同一调试位置也正确设置了它)

这给人的印象是,
maintentity
必须以某种方式设置为null,但即使是观察值的断点也没有被触发,因此它必须通过反射发生(我们验证了在这两种情况下它是相同的
另一个依赖性
实例)

所以问题是:

有人知道为什么/在哪里该id部分设置为空吗?也许我们做得都不对,还有更好的方法(注意:我们不能将引用添加到
maintentity
中,只是出于几个原因让它级联)

环境:Wildfly 10.1.0和Hibernate 5.2.7

编辑:


我们还观察到一件事:在调试过程中,可以看到实体持久器同时引用了实体和id类的实例(即
另一个dependententityid
)。id类包含两个值,即两个字段都为null,而实际实体的id部分为null。

我在中发现了一个问题。尝试使用此解决方案…

经过几个小时的调试,情况变得更加清楚:

形势分析

最终将为实体调用
AbstractEntityTuplizer.setIdentifier(…)
,之后引用
mainEntity
变为空

该方法的主体基本上如下所示:

Session subSession = pEvent.getSession().sessionWithOptions()         
    .autoClose( true)        
    .autoJoinTransactions( true )        
    .noInterceptor()
    .openSession();

subSession.saveOrUpdate( new DependentEntity( mainEntity ) );
subSession.saveOrUpdate( new AnotherDependentEntity ( mainEntity, SomeEnum.VALUE1 ) );
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 );
    }
@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;
}
在我们的例子中,调用了
mappedIdentifierValueMarshaller
,正如您可能猜到的,它是
的一个实例

该封送拆收器获取实体(其引用
maintentity
已正确设置)以及id类的实例,该id类包含字段
Long maintentity
(主实体的id是一个
Long
)且也已正确设置

现在,名为“难以置信的愚蠢”的封送处理程序决定需要通过id类中的值查找id组件,并相应地设置实体的属性。但是,由于我们在一个新会话中操作,试图在持久性上下文中查找引用的
maintentity
失败(持久性上下文是一个新的空实例),因此
maintentity
通过反射设置为null

建议的修复方法

到目前为止一切顺利,我们现在知道发生了什么。但是我们如何解决它呢

不幸的是,似乎没有简单的方法用新会话的持久性上下文“注册”主实体
maintentity
,而且走硬路线(通过强制转换等)并不明智,可能没有简单的方法是有原因的

但是,由于它与依赖性一起工作,因此必须存在差异。对于该实体,tuplizer直接进入第一个分支,即标识符似乎被映射为“嵌入式”,并且
实体
id
都是相同的实例,因此
setIdentifier(…)
什么都不做

事实证明,区别在于id类的存在。删除它并使另一个依赖性
实现
可序列化
修复了这个问题,就我们所知没有副作用(如果有副作用,我们很乐意有人指出)

实体现在看起来如下所示:

Session subSession = pEvent.getSession().sessionWithOptions()         
    .autoClose( true)        
    .autoJoinTransactions( true )        
    .noInterceptor()
    .openSession();

subSession.saveOrUpdate( new DependentEntity( mainEntity ) );
subSession.saveOrUpdate( new AnotherDependentEntity ( mainEntity, SomeEnum.VALUE1 ) );
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 );
    }
@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;
}
有人可能会问:“如果没有id类,如何使用
EntityManager.find(…)
?”。下面是它的工作原理:

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

嗯,虽然看起来很相似,但我不确定是同一个问题。除此之外,我没有看到解决方案,但也许我对此视而不见;)啊,不,它不是空的。我将把这一点添加到我的问题中。删除@IdClass对我来说是可行的,但是这个解决方案使我们的应用程序依赖于Hibernate,我们失去了可移植性,你找到其他解决方法了吗?这是非常愚蠢的错误indeed@SamyOmar不幸的是,在找到“解决办法”后,我们继续前进。在我们的例子中,依赖Hibernate并不是一个问题,因为我们知道自己的部署环境,并使用其他Hibernate特定的功能。也就是说,能够切换JPA实现通常不是一个实际的需求,所以您可能需要评估您是否真的需要这种可移植性。