【问题标题】:Hibernate/JPA - Entity listener not being called properlyHibernate/JPA - 实体监听器没有被正确调用
【发布时间】:2011-12-04 03:25:25
【问题描述】:

我正在尝试在我的 Seam/Hibernate/JPA 应用程序中利用 EntityListener 对象和回调方法。我在 JBoss 5.1 上使用 Seam 2.2 管理的持久性上下文,在后端使用 PostgreSQL 9.1。我声明了以下实体:

@Entity(name = "TestEntity")
@EntityListeners(TestCallback.class)
@Table(name = "tbl_test")
public class TestEntity implements Serializable {

    private static final long serialVersionUID = 2016897066783042092L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "xxx")
    @SequenceGenerator(name = "xxx", sequenceName = "xxx")
    @Index(name = "xxx")
    @DocumentId
    private Long id = null;

    @Column
    private String test = null;
...
}

以及以下 EntityListener 回调类:

public class TestCallback {

    /**
     * Logger for this class
     */
    private Log logger = null;

    public TestCallback() {
        logger = Logging.getLog(TestCallback.class);
    }

    @PrePersist
    public void prePersist(TestEntity e) {
        logger.debug("prePersist(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("prePersist(TestEntity) - end"); //$NON-NLS-1$
    }

    @PostPersist
    public void postPersist(TestEntity e) {
        logger.debug("postPersist(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("postPersist(TestEntity) - end"); //$NON-NLS-1$
    }

    @PostLoad
    public void postLoad(TestEntity e) {
        logger.debug("postLoad(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("postLoad(TestEntity) - end"); //$NON-NLS-1$
    }

    @PreUpdate
    public void preUpdate(TestEntity e) {
        logger.debug("preUpdate(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("preUpdate(TestEntity) - end"); //$NON-NLS-1$
    }

    @PostUpdate
    public void postUpdate(TestEntity e) {
        logger.debug("postUpdate(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("postUpdate(TestEntity) - end"); //$NON-NLS-1$
    }

    @PreRemove
    public void preRemove(TestEntity e) {
        logger.debug("preRemove(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("preRemove(TestEntity) - end"); //$NON-NLS-1$
    }

    @PostRemove
    public void postRemove(TestEntity e) {
        logger.debug("postRemove(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("postRemove(TestEntity) - end"); //$NON-NLS-1$
    }
}

但是,当我运行我的测试时,我并没有看到我的所有回调方法都像我预期的那样被调用。我已经对以下场景进行了测试:

  • 保留新项目
  • 更新现有项目
  • 加载项目
  • 删除项目

但是,我看到的唯一回调是:

  • @PrePersist
  • @PreRemove
  • @PostLoad
  • @PreUpdate

剩余的回调没有按预期执行。这是正常行为吗?我只是误会了吗?这是否与 Seam 管理事务的方式有关?或者,我只是没有做对吗?

如果您能提供任何帮助,我将不胜感激。

编辑:根据要求,这是我正在调用的确切代码和我收到的输出:

测试 1:

public void runTest() {
    logger.debug("runTest() - start"); //$NON-NLS-1$

    TestEntity e = new TestEntity();
    e.setTest("XXX");

    this.entityManager.persist(e);
    this.entityManager.flush();
    this.entityManager.clear();

    logger.debug("runTest() - end"); //$NON-NLS-1$
}

输出 1:

12:27:56,307 INFO  [STDOUT] 29735 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest() - start
12:27:56,312 INFO  [STDOUT] 29740 DEBUG myapp.test.entities.TestCallback  -  - prePersist(TestEntity) - start
12:27:56,312 INFO  [STDOUT] 29740 DEBUG myapp.test.entities.TestCallback  -  - prePersist(TestEntity) - end
12:27:56,347 INFO  [STDOUT] 29775 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest() - end

测试 2:

public void runTest2() {
        logger.debug("runTest2() - start"); //$NON-NLS-1$

        String sql = "SELECT DISTINCT t FROM TestEntity t";
        Query q = this.entityManager.createQuery(sql);

        List<TestEntity> l = q.getResultList();
        for (int i = 0; i < l.size(); i++) {
            String x = l.get(i).getTest();
            logger.debug("runTest2() - String x=" + x); //$NON-NLS-1$
        }

        logger.debug("runTest2() - end"); //$NON-NLS-1$
    }

输出 2:

12:28:36,964 INFO  [STDOUT] 70392 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest2() - start
12:28:36,982 INFO  [STDOUT] 70410 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - start
12:28:36,982 INFO  [STDOUT] 70410 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - end
12:28:36,982 INFO  [STDOUT] 70410 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest2() - String x=XXX
12:28:36,983 INFO  [STDOUT] 70411 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest2() - end

测试 3:

public void runTest3() {
        logger.debug("runTest3() - start"); //$NON-NLS-1$

        String sql = "SELECT DISTINCT t FROM TestEntity t";
        Query q = this.entityManager.createQuery(sql);

        List<TestEntity> l = q.getResultList();
        for (int i = 0; i < l.size(); i++) {
            l.get(i).setTest("YYY" + System.currentTimeMillis());
            this.entityManager.persist(l.get(i));
        }
        this.entityManager.flush();
        this.entityManager.clear();

        Random rand = new SecureRandom();

        q = this.entityManager.createQuery(sql);
        l = q.getResultList();
        for (int i = 0; i < l.size(); i++) {
            this.entityManager.remove(l.get(i));
        }

        this.entityManager.flush();
        this.entityManager.clear();

        logger.debug("runTest3() - end"); //$NON-NLS-1$
    }

输出 3:

12:30:00,404 INFO  [STDOUT] 153832 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest3() - start
12:30:00,407 INFO  [STDOUT] 153835 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - start
12:30:00,407 INFO  [STDOUT] 153835 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - end
12:30:00,408 INFO  [STDOUT] 153836 DEBUG myapp.test.entities.TestCallback  -  - preUpdate(TestEntity) - start
12:30:00,408 INFO  [STDOUT] 153836 DEBUG myapp.test.entities.TestCallback  -  - preUpdate(TestEntity) - end
12:30:00,410 INFO  [STDOUT] 153838 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - start
12:30:00,411 INFO  [STDOUT] 153839 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - end
12:30:00,414 INFO  [STDOUT] 153842 DEBUG myapp.test.entities.TestCallback  -  - preRemove(TestEntity) - start
12:30:00,414 INFO  [STDOUT] 153842 DEBUG myapp.test.entities.TestCallback  -  - preRemove(TestEntity) - end
12:30:00,453 INFO  [STDOUT] 153881 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest3() - end

【问题讨论】:

  • 请看我的回答,我已经更新了。我希望它有帮助!而且,如果您让我说一点:下次您更新答案时,请对答案发表评论,谁的所有者似乎对您的问题感兴趣。我之所以这么说,是因为我没有收到任何关于您已更新问题的通知(我已手动检查!:)。提前致谢!
  • 如何注入你的 entityManager?包含 runTest 方法的类是否是接缝组件(使用 @Name 注释)?您也可以发布您的 components.xml 文件吗?我已经非常努力地重现了您的问题,但直到现在都没有运气。
  • 我用标准的@In EntityManager entityManager 注入我的entitymanager;宣言。我的测试类是带有@Name("testAction") 的Seam 组件。 runTest*() 方法未声明为 @Transactional 或任何东西。这真的是一个非常简单的用例,所以我不知道为什么它不起作用。

标签: hibernate jpa callback seam entitylisteners


【解决方案1】:

对不起,如果我给出错误的答案......我不知道 Seam。

但是在您的主题中,您说的是“休眠/JPA”,这不清楚。 您是使用来自 SessionFactory 的 Session 还是来自 EntityManagerFactory 的 EntityManager 来持久化实体?

有一个很大的不同,因为如果你使用带有 SessionFactory 的 Seam,最大的不同是默认情况下,JPA 侦听器(触发你的带注释的回调)在默认情况下不会注册,而它们是使用 EntityManagerFactory。 因此,您可能正在使用 SessionFactory,而您项目中配置该会话工厂的其他人只注册了所有 JPA 回调侦听器的一个子集。

见: http://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/configuration.html#d0e865


编辑: 好的,抱歉,您使用的是 EntityManager...

但也许尝试将 SessionFactory 放在 EntityManagerFactory 后面并查看注册了哪些事件侦听器是个好主意。你一个人在那个应用程序上吗?如果有人尝试注册自定义/旧事件监听器或其他东西,他可能已经覆盖了 JPA 事件监听器。

这可以通过以下方式实现:

EntityManager em = ...
Session session = (Session)em.getDelegage()
SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl)session.getSessionFactory();
EventListeners el = sessionFactoryImpl.getEventListeners()

然后你可以看看里面是什么,例如,根据你的问题,你可以比较:

el.getPreLoadEventListeners()
el.getPreDeleteEventListeners()

记住“默认行为”是: http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html_single/#d0e865

似乎可以轻松覆盖 JPA 默认侦听器,请参阅: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/listeners.html

如果您向我们展示您的 persistence.xml,那就太好了

【讨论】:

  • 我从 EntityManager 打来电话。
  • 我是应用程序的唯一开发者,所以没有注册额外的事件监听器,除非 Seam 声明了一个我不知道的事件监听器。
  • 这就是为什么你宁愿像我上面所说的那样使用已注册的听众列表;)
  • 我再次查看了我的 persistence.xml 文件,发现 Hibernate Search 被声明为一系列 FullTextIndexEventListener。我假设这是问题所在,对吗?也就是说,我如何使用 Hibernate Search 侦听器以及我的自定义侦听器?是否存在某种链接?
  • 您的意思是用默认的 jpa 侦听器注册您的自定义休眠搜索侦听器?通常你可以定义多个事件监听器来响应同一个事件,根据休眠代码,每个事件都关联一个事件监听器数组。您将必须了解如何使用 JPA 为单个事件注册多个事件侦听器。再次您的 persistence.xml 将有所帮助;)感谢您的赏金
【解决方案2】:

如果没有您运行的实际测试、运行它们的方式以及您获得的输出(来自您的侦听器),很难说任何聪明的东西,但是使用 MySQL 与下表和数据

CREATE TABLE `test` (
  `id` int(11)
  `data` char(1),
  PRIMARY KEY (`id`)
);
+----+------+
| id | data |
+----+------+
|  1 | a    |
+----+------+

我已经运行了这个测试

EntityManagerFactory emf = ...
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

// persisting new item
Test b = new Test();
b.id = 2;
b.data = 'b';

tx.begin();
em.persist(b);
tx.commit();

// update existing item
b.data = 'a';

tx.begin();
em.merge(b);
tx.commit();

// load existing
Test a = em.find(Test.class, 1);

// remove existing
tx.begin();
em.remove(b);
tx.commit();

em.close();
emf.close();

得到以下输出

prePersist(2: b)
postPersist(2: b)
preUpdate(2: a)
postUpdate(2: a)
postLoad(1: a)
preRemove(2: a)
postRemove(2: a)

(我在Test 中实现了toString(),所以它会发出id + ": " + data,我已经将我的监听器类中的日志语句减少到System.out.format("preXxx(%s)\n", e),以便更好地阅读它。)

请看看这个是否适合你的初学者环境,这样我们就可以缩小问题的范围,但至少发布一些你的输入/输出。


我尝试为自己启动一个简单的 JBoss/Seam 测试应用程序来运行测试,但是我没有得到 Seam ......无论如何,我看了你的例子,这就是我认为你的问题所在。

测试 1 / 输出 1

我假设你的runTest() 方法启动了一个托管持久会话或类似的东西,所以在方法被调用之前entityManager.getTransaction().begin() 被Seam 调用,当方法存在时entityManager.getTransaction().commit() 被调用。这是我对您的场景中似乎发生的事情的看法。

// transaction begin
// persist - @PrePersist called
this.entityManager.persist(e);
// flush - @PostPersist should be called
this.entityManager.flush();
// clear - the context is cleared
this.entityManager.clear();
// transaction end

我已经测试过这个场景,但是使用带有手动持久性管理的 MySQL @PostPersist 被调用。

我尝试将entityManager.setFlushMode(...) 设置为FlushModeType.AUTOFlushModeType.COMMIT,但没有任何改变。然而,在 Seam 文档中有第三个枚举,FlushModeType.MANUAL,它不在 JPA 规范中并且是特定于 Seam 的。它应该馈送@Begin注解(这个注解可能表明被注解的方法开始手头的事务)。

我很确定,在您的情况下,flush() 在事务结束之前不会向数据库提交任何内容(方法运行out)。如果您删除对clear() 方法的调用,则应调用@PostPersist

测试 2 / 输出 2

这对我来说似乎很好!

测试 3 / 输出 3

我认为你的问题是 @PrePersist@PostPersist 根本没有被调用,但你调用了 persist()。因为您使用find(),所以返回的实例已经被管理。给他们打电话 persist() 什么也没做。然而,改变它们的状态并调用flush() 会触发@PreUpdate@PostUpdate 没有被调用,原因与@PostPersist 在第一次测试中没有被调用的原因完全相同:在任何东西可以传播到数据库之前清除持久性上下文。

结论

您的问题归结为您在将任何内容提交到数据库之前clear() 您的持久性上下文。

我建议您进行一些真正简单的测试,并尝试使用我提到的FlushModeType 设置。更新您的休眠版本。我使用 3.x.x 系列(3.8.6.Final)的最新版本。顺便说一句,您并没有完全与我们分享您的 Hibernate 版本,但这可能很重要!

【讨论】:

  • 但是,当我的代码停止运行时,我确实看到了数据库中反映的更改。我看到“测试”的值按预期更新。事实上,整个代码运行没有问题。唯一的问题是我的 some 实体回调方法没有按预期调用。我会根据您的建议进行一些更改,看看会发生什么。
  • @Shadowman 但是,当我的代码停止运行时,我确实看到了数据库中反映的更改。 所以 @PostPersist 没有被调用,你 clear()会话,但是,您的数据仍然存在? :D Weird. 你是否在你的代码中使用了 Seam-ish 注释,比如@Begin@End?如果是的话,如果您也可以发布这些或您的整个配置(persistence.xml 也是,如果您有一些 magic 的话,那将是有帮助的)。我也会将 Postgres 换成其他东西,以确保它不是问题的原因。尝试缩小问题范围。
  • 代码中没有开始或结束标记,因为我在运行测试时没有创建接缝对话。稍后我将尝试发布我的 persistence.xml 文件。是的,数据成功持久化。我只是没有看到所有的侦听器方法都被调用,这就是我如此困惑的原因。
  • @Shadowman 如果您可以发布一个完整的示例(包含 Seam 配置和其他内容,可能在 ZIP 或其他内容中),我可以尝试看看它。我从未使用过 Seam,所以我不知道如何创建 Seam 应用程序,而且无论如何我都很难模仿 您的 设置。
【解决方案3】:

这不是正常行为。我在一个项目中使用 seam 2.2.2 和 seam 管理的持久性上下文、jpa/hibernate、jboss 5.1 和 mysql,并且所有回调都被正确调用。 也许您的测试用例存在错误。

【讨论】:

    猜你喜欢
    • 2012-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-12
    相关资源
    最近更新 更多