在 JPA/Hibernate 中正确使用 flush()

Posted

技术标签:

【中文标题】在 JPA/Hibernate 中正确使用 flush()【英文标题】:Correct use of flush() in JPA/Hibernate 【发布时间】:2011-05-15 13:51:14 【问题描述】:

我正在收集有关 flush() 方法的信息,但我不太清楚何时使用它以及如何正确使用它。根据我的阅读,我的理解是持久化上下文的内容将与数据库同步,即。 e.发出未完成的报表或刷新实体数据。

现在我得到了以下两个实体 AB 的场景(一对一的关系,但不是由 JPA 强制执行或建模的)。 A 有一个复合 PK,它是手动设置的,还有一个自动生成的 IDENTITY 字段recordId。这个recordId 应该作为A 的外键写入实体B。我将AB 保存在一个事务中。问题是自动生成的值A.recordId 在事务中不可用,除非我在A 上调用em.persist() 后显式调用em.flush()。 (如果我有一个自动生成的 IDENTITY PK,那么该值会直接在实体中更新,但这里不是这种情况。)

em.flush() 在事务中使用会造成任何伤害吗?

【问题讨论】:

【参考方案1】:

em.flush() 的确切细节可能取决于实现。 一般来说,像 Hibernate 这样的 JPA 提供程序可以缓存它们应该发送到数据库的 SQL 指令,通常直到您实际提交事务为止。 例如,您调用em.persist(),Hibernate 记住它必须进行数据库 INSERT,但在您提交事务之前不会实际执行指令。 Afaik,这主要是出于性能原因。

在某些情况下,无论如何您都希望立即执行 SQL 指令;通常当您需要某些副作用的结果时,例如自动生成的键或数据库触发器。

em.flush()所做的就是清空内部SQL指令缓存,立即执行到数据库中。

底线:不会造成任何损害,只有您可能会受到(轻微的)性能影响,因为您在将 SQL 指令发送到数据库的最佳时机方面覆盖了 JPA 提供程序的决定。

【讨论】:

他说了什么。 em.flush() 行为与 java.io.Flushable.flush() 相呼应,其中所有缓冲数据都被发送到任何合适的目的地。 如果 flush() 将数据发送到数据库?如果在那之后抛出异常会发生什么?实体管理器会回滚所有内容吗?甚至是第一次刷新时写入的数据? flush() 向数据库发送 SQL 指令,如 INSERT、UPDATE 等。它不会发送 COMMIT,因此如果在 flush() 后出现异常,您仍然可以完全回滚。 您可以回滚数据库,但它不会回滚对对象的任何更改,例如,自动递增的“版本”、自动生成的 ID 等。此外,实体管理器将在回滚后关闭。请注意,如果您尝试将对象“合并”到另一个会话,特别是自动递增的“版本”可能会导致 OptimisticLockException。 除了触发副作用之外,使用 flush() 的另一个原因是,如果您希望能够使用 JPQL/HQL(例如在测试中)读取数据库中操作的效果。 JPA 在执行这些查询时无法使用缓存数据,因此只会读取实际在数据库中的内容。【参考方案2】:

在事务中使用 em.flush() 会造成任何伤害吗?

是的,它可能会在数据库中持有比必要的更长的时间。

通常,当使用 JPA 时,您将事务管理委托给容器(也称为 CMT - 在业务方法上使用 @Transactional 注释),这意味着事务在进入方法时自动启动并在最后提交/回滚。如果让 EntityManager 处理数据库同步,则只会在提交之前触发 sql 语句执行,从而导致数据库中的短暂锁定。否则,您手动刷新的写入操作可能会在手动刷新和自动提交之间保留锁定,根据剩余的方法执行时间,这可能会很长。

请注意,某些操作会自动触发刷新:对同一会话执行本机查询(EM 状态必须刷新才能被 SQL 查询访问),使用本机生成的 id 插入实体(由数据库生成,因此插入必须触发语句,因此 EM 能够检索生成的 id 并正确管理关系)

【讨论】:

【参考方案3】:

实际上,em.flush(),不仅仅是发送缓存的 SQL 命令。它尝试将持久性上下文同步到底层数据库。如果您的缓存包含要同步的集合,这可能会导致您的进程消耗大量时间。

谨慎使用。

【讨论】:

以上是关于在 JPA/Hibernate 中正确使用 flush()的主要内容,如果未能解决你的问题,请参考以下文章

Play 2.4 / Ebean / JPA / hibernate-entitymanager 的正确配置是啥?

Play 2.4 JPA/Hibernate EntityManager 不刷新到数据库

JPA(休眠)映射OneToMany不正确?

如何使用 JPA 和 Hibernate 在 UTC 时区中存储日期/时间和时间戳

为啥在 JPA 2/Hibernate 中使用共享主键时实体需要可序列化?

数据在数据库 jpa/hibernate 中插入两次