为什么在调用实体侦听器时,我的实体的某些字段为null?

时间:2015-12-16 17:27:51

标签: hibernate jpa java-ee

我有一个这样的实体:

@EntityListeners({MyEntityListener.class})
public class MyEntity {
    @OneToMany(mappedBy = "myentity", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<MyDependentEntity> dependents;

    //other attributes that are mapped to columns in MyEntity table
}

依赖实体:

public class MyDependentEntity {
    @ManyToOne(optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
    @JoinColumn(name = "myentity_id")
    private MyEntity myEntity;
}

和MyEntityListener:

public class MyEntityListener {
    @PostPersist
    public void postPersist(MyEntity entity) {
        logStuff(entity);
    }
}

这个启动它的代码:

MyDependentEntity dependent = new MyDependentEntity();
dependent.set...(...);
dependent.set...(...);

MyEntity entity = new MyEntity();
entity.set...(...);
entity.set...(...);
entity.set...(...);
entity.setDependents(Collections.singleton(dependent))
dependent.setEntity(entity);
entityManager.merge(entity);

它工作,实体监听器被触发。 问题是:dependents属性为null(所有其他属性都已正确填充)。

数据库具有正确的数据(实体和相应的依赖项)。 调试我可以看到持久化的实体在属性中具有依赖性。但是当它进入实体监听器时,依赖属性为空。

有没有办法可以在实体监听器中填充dependents属性?我需要它,所以我可以处理刚刚保存的实体。

1 个答案:

答案 0 :(得分:2)

我已经在Hibernate中对此进行了测试,并且由于您要持久保存新实例,因此您可以通过以下方式解决此问题:

  1. 致电entityManager.persist(entity)而不是entityManager.merge(entity)

  2. 将@PostPersist调用添加回实体本身,而不是定义单独的侦听器类(https://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Example_of_Entity_event_annotations)。

  3. 为什么呢?

    首先,请注意,调用entityManager.merge(entity)会返回一个新的托管实例,是这个新创建的实例传递给您的侦听器 - 而不是您传递给merge()的实例。您可以添加一些调试来确认这一点。

    对于merge和persist之间的差异,请参见此处:

    JPA EntityManager: Why use persist() over merge()?

    JPA规范(3.2.7.1)指出在调用与新实例的合并时:

      
        
    • 如果X是新的实体实例,则新的管理实体实例X&#39;已创建,并且X的状态将复制到新的管理实体中   实例X&#39;。
    •   
    • 对于具有级联元素值cascade = MERGE或cascade = ALL的X关系引用的所有实体Y,Y被合并   递归地为Y&#39;。对于由X,X&#39;引用的所有这样的Y.被设置为   参考Y&#39;。 (注意,如果X被管理,那么X是与之相同的对象   X&#39;。)
    •   

    JPA规范还指出:

      

    PostPersist和PostRemove方法将在调用之后调用   数据库插入和删除操作分别。这些数据库   操作可能会在持久,合并或删除后直接发生   已经调用了操作,或者它们可能在刷新后直接发生   已经发生了操作(可能在交易结束时)。

    因此,以下事件序列可以解释您所看到的内容:

    1. 调用合并结果,创建一个新的托管实例,此时仅合并了基本属性。
    2. 父实体发生数据库刷新。
    3. post persist listener执行并传递这个新的托管实例。
    4. 级联操作应用于新的托管实例(现在只是在此新实例上设置的关联)。
    5. 关联的实体将刷新到数据库。
    6. 启用Hibernate SQL日志记录支持上述内容:对父实体的insert语句之后以及刷新关联集合之前立即执行对侦听器的调用。

      以下似乎也证实了在基本属性合并和相关集合合并之间的某个时刻调用了侦听器。

      MyEntity e2 = entityManager.merge(entity);
      --> listener called and size of associated collection is 0
      System.out.println(e2.getDependents().size()); // outputs 1