java - OptimisticLockType.DIRTY 无法按预期工作的问题
问题描述
fun main(args: Array<String>)
{
runApplication<JpaTest>(*args).getBean(JpaTest::class.java).test()
}
@SpringBootApplication
class JpaTest
{
@Autowired
lateinit var repository: PersonRepository
fun test()
{
repository.save(Person())
runBlocking {
suspend fun update(name: String, delay: Long)
{
val p = repository.findById(1).get()
delay(delay)
println("=== $name")
p.name = name
repository.save(p)
}
withContext(Dispatchers.Default) {
awaitAll(
async { update("Right Name", 3000) },
async { update("Wrong Name", 5000) }
)
println(" ")
val p = repository.findById(1).get()
println("Final: $p")
println(" ")
}
}
exitProcess(0)
}
}
@Repository
interface PersonRepository : CrudRepository<Person, Long>
@Entity
@DynamicUpdate
@OptimisticLocking(type=OptimisticLockType.DIRTY)
data class Person(
@Id
@GeneratedValue
val id: Long = 0,
var name: String = "?",
)
我正在尝试模拟一个多线程环境(或运行同一应用程序的多个实例),其中一个实体同时从数据库中获取、更改和持久化。
当第一个保存任务运行时(3 秒后):
=== Right Name
Hibernate: select person0_.id as id1_0_0_, person0_.name as name2_0_0_ from person person0_ where person0_.id=?
2021-03-24 09:18:38.565 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2021-03-24 09:18:38.566 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_0_0_] : [VARCHAR]) - [UNKNOWN]
Hibernate: update person set name=? where id=? and name=?
2021-03-24 09:18:38.567 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Right Name]
2021-03-24 09:18:38.568 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]
2021-03-24 09:18:38.568 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [UNKNOWN]
当第二个保存任务运行时(5 秒后):
=== Wrong Name
Hibernate: select person0_.id as id1_0_0_, person0_.name as name2_0_0_ from person person0_ where person0_.id=?
2021-03-24 09:18:40.556 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2021-03-24 09:18:40.556 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_0_0_] : [VARCHAR]) - [Right Name]
Hibernate: update person set name=? where id=? and name=?
2021-03-24 09:18:40.557 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Wrong Name]
2021-03-24 09:18:40.557 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]
2021-03-24 09:18:40.557 TRACE 21791 --- [atcher-worker-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [Right Name]
请注意,这两个任务都为同一个对象获取数据库,然后在更新/保存之前休眠了 3 秒/5 秒。
这里的问题是在更新之前second task
再次获取了数据库,这意味着first task
更改丢失了。我尝试添加@SelectBeforeUpdate(false)
,但也没有用。
我期待当second task
试图持久化实体时,OptimisticLockException
会抛出一个。
如果我将 lockType 更改为VERSION
它按预期工作并且second task
' 的更新被拒绝。这是为什么?
解决方案
您没有声明任何事务边界。因此,对存储库的每次调用都在其自己的事务中运行。这就是实体作为save
操作的一部分(再次)被加载的原因。
如果您将加载、等待、保存周期包装到单个事务中,您将看到您想要的行为。
在您的测试中,您可以使用Spring 参考文档中描述TransactionTemplate
的用法来执行此操作。
关于后续问题:
关于为什么
VERSION
有效的任何想法?
我没有以下证明,但这是我强烈怀疑的。
所有列的原始状态都存储在DIRTY
会话中,因此它是当前事务中加载操作的状态。
原始状态是实体的一部分,因此VERSION
它可以跨越事务边界。
如果我必须将加载后的所有内容都包装到事务中,这取决于我在加载和保存之间所做的事情,这不是一个不好的做法吗?
虽然我同意交易应该尽可能短,但它们不应短于完成工作所需的时间。只要您没有任何阻塞锁,我就不会期望几秒钟的事务有问题。
如果你想指出一个不好的做法,即需要一个很好的解释为什么你使用它,那可能会使用OptimisticLockType.DIRTY
. OptimisticLockType.VERSION
推荐和默认是有原因的。
推荐阅读
- c++ - Docker 构建抛出错误“致命错误:json/json.h:没有来自 ubuntu:14.04 的此类文件或目录”
- api - oAuth 返回 400 错误请求
- ios - 使用 NSNetServices 类设置多个 iOS 设备之间的通信
- reactjs - catching error on successful api post - Axios
- spring - Spring 集成 - 重试在异常情况下建立连接
- c# - 如何在 xunit 中模拟 Db 连接?
- javascript - Javascript - 仅保留给定所需属性列表的值
- java - Spring Batch 内存泄漏 - 使用 JpaItemWriter 将 CSV 保存到数据库
- android - 根据父宽度设置recyclerview子宽度相等
- sql-server - 防止读取提交的 SELECT 中的死锁