【问题标题】:Why are some fields of my entity null when the entity listener is called?为什么调用实体侦听器时我的实体的某些字段为空?
【发布时间】:2015-12-16 17:27:51
【问题描述】:

我有一个这样的实体:

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

它起作用并且实体监听器被触发。 问题是:依赖属性为空(所有其他属性都正确填充)。

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

有什么方法可以在实体监听器中填充依赖属性?我需要它,以便处理刚刚保存的实体。

【问题讨论】:

    标签: hibernate jpa jakarta-ee


    【解决方案1】:

    我已经在 Hibernate 中对此进行了测试,并且当您保留一个新实例时,您可以通过以下任一方式解决此问题:

    1. 调用entityManager.persist(entity) 而不是entityManager.merge(entity) 或者,

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

    为什么?

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

    有关合并和持久化之间的区别,请参见此处:

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

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

    • 如果 X 是一个新的实体实例,则创建一个新的托管实体实例 X',并将 X 的状态复制到新的托管实体中 实例 X'。
    • 对于由来自 X 的具有级联元素值 cascade=MERGE 或 cascade=ALL 的关系引用的所有实体 Y,Y 被合并 递归为 Y'。对于 X 引用的所有此类 Y,X' 设置为 参考 Y'。 (请注意,如果 X 是托管的,则 X 与 X'。)

    JPA 规范还指出:

    PostPersist 和 PostRemove 方法将在 数据库插入和删除操作。这些数据库 操作可能在持久化、合并或删除之后直接发生 操作已被调用,或者它们可能在刷新后直接发生 操作已发生(可能在事务结束时)。

    所以下面的一系列事件可以解释你所看到的:

    1. 调用合并会导致创建一个新的托管实例,此时仅合并基本属性。
    2. 为父实体发生数据库刷新。
    3. post persist listener 执行并被传递给这个新的托管实例。
    4. 级联操作应用于新的托管实例(现在才在这个新实例上设置关联)。
    5. 关联的实体被刷新到数据库中。

    启用 Hibernate SQL 日志记录支持上述情况:在父实体的插入语句之后和刷新关联集合之前立即执行对侦听器的调用。

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

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

    【讨论】:

    • 哇,很好的答案!谢谢!我更改了代码并且它可以工作,但是有一个问题:我会将实体侦听器添加到使用 merge() 来保存实体的现有系统中。有什么方法可以获取属性还是需要注入实体管理器并手动获取依赖项?
    • 没关系,我会将现有代码更改为使用persist :)
    • 或者您可以在实体本身上添加回调而不是使用单独的侦听器? en.wikibooks.org/wiki/Java_Persistence/…
    • 这是个好主意!我会和我的同事讨论我们会选择哪一个。谢谢!
    猜你喜欢
    • 2015-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多