【问题标题】:How to make Hibernate @DynamicUpdate work with two updating threads?如何使 Hibernate @DynamicUpdate 与两个更新线程一起工作?
【发布时间】:2016-11-04 19:45:11
【问题描述】:

我想在我的@Entity 上使用@DynamicUpdate,因为我有两个线程可以更改同一行上的不同列,并且在没有动态查询生成的情况下,所有列都在每次更新时写入,第二个线程会覆盖写入的内容由第一个。

例子:

@Entity Task has attributes A and B set to false.
- Thread 1 loads Task#1 from the database and starts some long operation outside transaction
- Thread 2 loads Task#1 from the database and sets A=true, then saves
> the database holds Task#1 with A=true and B=false
- Thread 1 sets B=true, then saves
> the database holds Task#1 with A=false (!!!) and B=true
I was expecting to have A=true but it was overwritten with the 
original value when Thread 1 saved

问题在于,仅在 @Entity 上添加 @DynamicUpdate 并不会更改查询,它仍在更新所有列。 更新:我设法通过使用 @DynamicUpdate 注释所有类层次结构来完成这项工作(不仅仅是像以前那样保存属性的基类),但我仍然遇到问题(见下文)

我将 Spring Data JPA 1.10.2 与 Spring 4.2.6 和 Hibernate 5.1.0 一起使用。 一个线程使用 JpaRepository 上的自定义查找来获取实体,另一个线程使用同一 JpaRepository 上的 findOne() 获取实体。 最后都在 JpaRepository 上调用 save() 。 只有JpaRepository是@Transactional,所以读取和保存操作发生在不同的事务中。

我也尝试添加@SelectBeforeUpdate,但没有任何区别。

另外,有没有关于如何使用动态查询生成的文档?我在javadoc 中只找到了两行,但在用户指南中没有找到。

或者我应该完全使用不同的方法?任何解决方法?我只能想到用原生sql分别更新每一列,但那会很伤心。

更新: 现在我有了动态更新,理论上只保存更改的列。 但实际上,在一个线程上标记为已更改的列也由第二个线程存储! 这是更详细的情况:

我的实体名为 Task,具有“id”、“priority”、“executionTime”和“foo”属性。 它被子类化了两次:BigTask 扩展了 Task,HugeTask 扩展了 BigTask,但我在处理实例时使用了 Task。 所有类都使用@Entity 和@DynamicUpdate 进行注释。 在“setPriority()”方法中,我添加了一个调试行,用于打印前一个和新的优先级值。

使用 Web 控制台,我启动了一个任务实例:启动了一个新的异步线程,该线程从数据库加载任务并在事务之外启动一些长时间运行的操作。 在任务运行时,我在 Web 控制台上更改其优先级并保存。我看到调试消息“优先级从 10 更改为 20”并且数据库保存了新值。 hibernate记录的更新查询是

update Task set priority=? where id=?

这是正确的,表明@DynamicUpdate 工作正常。 几分钟后,异步线程终止其任务,设置 executionTime 并保存。现在是休眠查询

update Task set executionTime=?, priority=? where id=?

这不是预期的。正在使用@DynamicUpdate(“foo”属性不包含在查询中),但“priority”显然已被标记为已修改,即使它的设置器尚未被调用。而且它永远不会在代码中直接访问。

对我来说看起来很奇怪:两个线程使用的任务实例是不同的(或者两个更新都会设置新的优先级)但显然“脏标志”是共同的!真的是这样吗?

【问题讨论】:

  • 我现在遇到了一个非常相似的问题。您是否能够解决或解决此问题?
  • 不,我放弃了。我找到了一个特定于应用程序的解决方法。我记不太清了,但我想我确保所有线程都使用同一个实体实例,使用某种工厂模式和实体池。好伤心。

标签: hibernate spring-data


【解决方案1】:

您遇到的问题通常可以通过使用某种形式的locking 来解决,以避免两个线程同时更改同一行。

一般而言,您通常有两种锁定方法:乐观悲观

乐观
这种类型的锁定涉及引入持久性提供程序使用的特定字段,以确定在更新时是否有其他事务在当前线程上次读取实体后更改了该实体。如果持久性提供程序检测到这种情况,它将抛出异常,让您处理它。在这种情况下,您可以重新读取实体状态并重新应用必要的更改并尝试再次保存或抛出您自己的异常。

悲观
这种类型的锁定将锁定控制向下游推送到数据库,您在线程 1 读取数据时应用行级锁定,防止任何其他线程能够读取该行,直到第一个线程提交它的数据。虽然这看起来像是表面上想要的,但这种方法通常不能很好地扩展,特别是在多个线程/事务需要在接近的时间访问和操作相同行的可能性很大的情况下。

【讨论】:

  • 抱歉,我描述问题的速度可能太快了。两个线程不会同时访问同一行。他们在同一个实体上工作,但在不同时间访问数据库。我现在正在用更多细节更新我的问题。
  • 也许我可以按照您的建议解决事务问题,但我觉得 @DynamicUpdate 如果可行的话应该是正确的方法。
【解决方案2】:

在这件事上迷失了一个晚上.. 对于任何来这里的人来说都是小信息..

就我而言,我已经修改了我的应用程序,以便在更新之前使用 JPARepository 中的普通 findById() 系统地重新加载实体。这对于需要时间来完成但在操作之前检索到的实体上工作的线程特别重要。

这在理论上仍然会导致丢失更新,就像在实体刷新和保存之间修改数据的情况一样。更多信息在这里:

https://vladmihalcea.com/how-to-prevent-optimisticlockexception-using-hibernate-versionless-optimistic-locking/

在丢失更新不可接受的情况下,我添加了乐观锁定(基于版本字段)并拒绝来自具有可丢失更新的线程的修改。

当这不可能时,我在再次保存之前刷新了 StaleStateException 捕获中的对象(只需要一次)

【讨论】:

    猜你喜欢
    • 2020-11-05
    • 1970-01-01
    • 2014-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-09
    • 2019-12-12
    • 1970-01-01
    相关资源
    最近更新 更多