Hibernate重置序列缓存

Ole*_*nyk 7 java database hibernate

例子:

  • 假设实体 E 具有由序列 e_seq 生成的 id
  • 假设数据库中sequence的值初始为0,increment配置为50
  • 当hibernate启动时,它获取序列的下一个值(即0+50=50)并保留可用值的内部缓存(即0-50区间内的值)
  • 只要缓存有可用值,就不会再向 dbms 发出请求来获取序列的下一个值
  • 仅当您创建实体 E 的 50 个实例后,才会使用 50 个 id,并且 hibernate 会向 dbms 询问下一个值。
  • 假设 hibernate 缓存还有 50 个可用的 id
  • 假设一个低级过程(如数据迁移)使用 SQL 语句(不使用 hibernate API)在数据库中插入 100 个 E 类型的实体,id 为 1 到 100,然后将序列值重置为 100
  • 如果应用程序尝试从其 API 创建新实体,它将使用从 hibernate 缓存中获取的 id,但该 id 已被低级过程使用,因此导致重复 id 异常

因此,我需要找到一种方法来告诉 Hibernate“重置其 ids 缓存”,或者换句话说“强制 Hibernate 再次联系 dbms 以获取当前序列值”。

wal*_*len 6

一个低级过程 [...] 插入 100 个实体 [...] id 从 1 到 100

为什么该低级过程会自行生成 ID?为什么不使用该序列?

Hibernate 的pooled 和 pooled-lo ID 生成机制的全部要点,您似乎正在使用(并且绝对应该使用,如果您没有使用),就是能够安全地缓存 ID ,即使在任何使用的外部进程上也是如此Hibernate 控制之外的序列

如果该外部进程也使用该序列,那么您的问题就会消失,因为 Hibernate 的缓存值都不会被使用;下一批缓存的值将从外部进程最后生成的任何序列值开始,避免冲突:

  1. Hibernate 缓存值 0-49。sequence.NEXTVAL将会是50。
  2. 外部进程插入 100 行。sequence.NEXTVAL将是5050。
  3. Hibernate 最终使用所有缓存的值,并请求下一个序列值。
  4. Hibernate 缓存值 5050-5099。sequence.NEXTVAL将是5100。
  5. ETC。

假设您使用 Hibernate 的池化(-lo)ID 策略,您的问题的解决方案不是禁用或重置 Hibernate 的缓存并影响您的应用程序性能;解决方案是让任何外部进程在将数据插入该表时也使用 NEXTVAL() 为实体生成适当的 ID,而不是提供自己的值。


担忧:

“但那样我的身份证上就会出现空白!”
所以呢?
您的 ID 列中存在间隙是没有任何问题的。您的目标是避免 ID 冲突,并确保您的应用程序不会在每次创建实体时两次访问数据库(一次用于序列,一次用于实际插入)。如果为此付出的代价就是没有一组整齐、完美连续的 ID,那就这样吧!如果你问我的话,这很划算;)

“但是随后使用 Hibernate 的缓存值创建的实体的 ID 会低于之前由外部进程创建的实体!”
所以呢?
拥有 ID 列的主要目标是能够通过单个值唯一标识行。区分创建顺序不应该成为您管理 ID 值的一个因素;时间戳列更适合于此。

“但是ID值增长太快了!我刚刚插入了50行,已经有几千行了!我会用完数字的!”
好吧,这是合理的担忧。但如果您使用序列,则很可能您使用的是 Oracle 或 PostgreSQL,也可能是 SQL Server。我对吗?
嗯,PostgreSQL 的序列 MAXVALUEbigint是 9223372036854775807。SQL Server 也是如此。如果您的进程每毫秒不间断地插入一个新行,则仍需要500 万年才能到达序列末尾。Oracle 的序列 MAXVALUE 为 999999999999999999999999999,比该值大几个数量级。
所以...只要正确选择 ID 列和序列的数据类型,在这方面您就是安全的。


lap*_*ets -1

您是否尝试过清除当前会话并创建一个新会话?这会强制 Hibernate 重新查询数据库以获取当前序列值。

换句话说,您可以使用该方法Session.flush()Session.clear()

Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();

// Perform some operations that use the id cache

session.flush();
session.clear();

// Perform some more operations that use the id cache

transaction.commit();
session.close();
Run Code Online (Sandbox Code Playgroud)

或者您可以使用EntityManager.refresh()它将从数据库刷新实例的状态,并在此过程中使用当前序列值更新内部缓存:

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();

// Perform some operations that use the id cache

em.refresh(entity);

// Perform some more operations that use the id cache

em.getTransaction().commit();
em.close();
Run Code Online (Sandbox Code Playgroud)