在侦听器

时间:2017-03-13 18:18:48

标签: java hibernate

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

@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时,我们都希望创建必要的AnotherDependentEntityPostInsertEventListener个实例。我们正尝试通过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已部分为空。

2 个答案:

答案 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"并且entityid都是同一个实例,因此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 );