我想在我的@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不会更改查询,该查询仍在更新所有列。 UPDATE :我设法通过使用@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控制台,我启动一个Task实例:启动一个新的异步线程,它从数据库加载Task并在事务之外启动一些长时间运行的操作。 任务正在运行时,我在Web控制台上更改其优先级并保存。我看到调试消息“优先级从10更改为20”,数据库保存新值。 hibernate记录的更新查询是
update Task set priority=? where id=?
这是正确的,并表明@DynamicUpdate工作正常。 几分钟后,异步线程终止其任务,设置executionTime并保存。 hibernate查询现在是
update Task set executionTime=?, priority=? where id=?
这不是预期的。正在使用@DynamicUpdate(查询中不包含“foo”属性),但“优先级”显然已被标记为已修改,即使未调用其setter也是如此。它永远不会直接在代码中访问。
对我来说看起来很奇怪:两个线程使用的Task实例是不同的(或者两个更新都会设置新的优先级)但显然“脏标志”是共同的!真的是这样吗?
答案 0 :(得分:0)
通常使用某种形式的locking来解决您遇到的问题,以避免两个线程同时更改同一行。
一般而言,您通常有两种锁定方法:乐观或悲观。
<强>乐观强>
这种类型的锁定涉及引入持久性提供程序使用的特定字段,以在更新时确定某个其他事务是否已更改实体,因为它是当前线程最后一次读取的。如果持久性提供程序检测到这种情况,它将抛出异常,允许您处理它。在这种情况下,您可以重新读取实体状态并重新应用必要的更改并尝试再次保存或抛出您自己的例外。
<强>悲观强>
这种类型的锁定将锁定控制向下推送到数据库,在该数据库中,在线程1读取数据时应用行级锁定,防止任何其他线程在第一个线程提交数据之前读取该行。虽然这看起来似乎是人们想要的表面,但这种方法通常不能很好地扩展,特别是在多线程/事务很可能需要访问和操作相近的时间段的情况下。
答案 1 :(得分:0)
在这上面浪费了一个晚上。给来到这里的人的一小部分信息..
就我而言,我已经修改了我的应用程序,以便通过使用JPARepository中的普通findById()在更新之前系统地重新加载实体。这对于需要花费一些时间才能完成但要处理在操作之前检索到的实体的线程来说尤其重要。
从理论上讲,这仍然会导致更新丢失,就像在实体刷新和保存之间修改数据的情况一样。此处更多信息:
在丢失的更新不可接受的情况下,我添加了乐观锁定(基于版本字段),并拒绝了具有可更新性的线程的修改。
当这不可能时,我在再次保存之前刷新了StaleStateException捕获中的对象(仅需要一次)