【问题标题】:Why does a JPA @PreUpdate-annotated method get called during a query?为什么在查询期间会调用 JPA @PreUpdate-annotated 方法?
【发布时间】:2012-08-23 17:58:03
【问题描述】:

我有一个命名查询,它返回 Collection 的实体。

这些实体上有一个@PreUpdate-annotated 方法。在query.getResultList() 期间调用此方法。因此,实体在持久化上下文中发生了变化,这意味着在事务提交时,实体被写回数据库。

这是为什么? JPA 2.0 规范没有明确提到 @PreUpdate 应该由查询执行调用。

【问题讨论】:

  • 我偶然在 EclipseLink 2.3.2.v20111125-r10461 中看到了这种行为,以防它只是该实现中的一个严重错误。
  • 我刚刚遇到了同样的问题,但使用的是 Hibernate。所以看起来这不是一个实现问题,而是一个预期的行为。不知何故,这似乎是由于 JTA 引起的一些自动刷新。

标签: jpa


【解决方案1】:

规范说:

PreUpdate 和 PostUpdate 回调发生在 分别对实体数据进行数据库更新操作。这些数据库 操作可能发生在实体状态更新或它们 可能在状态刷新到数据库时发生(可能是 在交易结束时)。

在这种情况下,调用query.getResultList() 会触发em.flush(),以便查询可以包含从当前EntityManager 会话中更改的内容。 em.flush() 将所有更改推送到数据库(进行所有 UPDATE、INSERT 调用)。在 UPDATE 通过 JDBC 发送之前,@PreUpdate 会调用相应的钩子。

【讨论】:

  • 但是,只有在有可用更改时才会调用它。这个问题真的让我怀疑 PostUpdate 是否有任何好处......
  • 我试图重现 OP 的问题,因为听起来每次调用查询时 EntityManager 都会被刷新。但事实并非如此。据我所知,只有在对数据库进行实际更改时才会调用 @PostUpdate 方法。如果您对尚未刷新到数据库的 EntityManager 进行了更改,则 query.getResultList 将触发对数据库的刷新,这是人们应该期望的行为。
  • 在我的情况下,我确实有 @PostLoad 和 @PreUpdate 更改同一个字段,所以在 query.getResultList() 时,@PostLoad 被调用,更新字段,方法返回并触发两者冲洗和@PreUpdate?无论如何,我的用例是在存储到数据库之前对字段进行编码,并在读取时对其进行解码。似乎实体监听器在这里没用。
【解决方案2】:

这只是我对 rzymek 的回答以及一些后续代码的评论:

我试图重现 OP 的问题,因为听起来每次调用查询时 EntityManager 都会被刷新。但事实并非如此。据我所知,只有在对数据库进行实际更改时才会调用 @PostUpdate 方法。如果您对尚未刷新到数据库的 EntityManager 进行了更改,则 query.getResultList 将触发对数据库的刷新,这是人们应该期望的行为。

        Place valinorDb = em.find(Place.class, valinorId);
        // this should not trigger an PostUpdate and doesn't
        // TODO: unit-testify this
        em.merge(valinorDb);

        valinorDb.setName("Valinor123");
        valinorDb.setName("Valinor");
        // this shouldn't trigger an PostUpdate because the Data is the same as in the beginning and doesn't
        em.merge(valinorDb);

        {
            // this is done to test the behaviour of PostUpdate because of
            // this:
            // http://stackoverflow.com/questions/12097485/why-does-a-jpa-preupdate-annotated-method-get-called-during-a-query
            //
            // this was tested by hand, but should maybe changed into a unit
            // test? PostUpdate will only get called when there is an actual
            // change present (at least for Hibernate & EclipseLink) so we
            // should be fine
            // to use PostUpdate for automatically updating our index

            // this doesn't trigger a flush as well as the merge didn't even trigger one
            Place place = (Place) em.createQuery("SELECT a FROM Place a")
                    .getResultList().get(0);

            Sorcerer newSorcerer = new Sorcerer();
            newSorcerer.setName("Odalbort the Unknown");

            place.getSorcerers().add(newSorcerer);

            //this WILL trigger an PostUpdate as the underlying data actually has changed.
            place = (Place) em.createQuery("SELECT a FROM Place a")
                    .getResultList().get(0);
        }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-25
    • 1970-01-01
    • 1970-01-01
    • 2010-12-25
    • 1970-01-01
    • 2017-01-21
    相关资源
    最近更新 更多