java与OptimisticLockType之间存在问题。脏兮兮的没按预期工作
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
的更新将被拒绝。为什么
# 1 楼答案
您没有声明任何事务边界。 因此,对存储库的每次调用都会在自己的事务中运行。 这就是为什么实体作为
save
操作的一部分被加载(再次)如果将加载、等待、保存周期打包到单个事务中,您将看到所需的行为
在测试中,可以使用^{} 来实现这一点,其中usage is described in the Spring Reference Documentation
关于后续问题:
我没有以下证据,但这是我强烈怀疑的
使用
DIRTY
时,所有列的原始状态都存储在会话中,因此它是当前事务中加载操作的状态对于
VERSION
,原始状态是实体的一部分,因此它可以跨事务边界生存虽然我同意交易应该尽可能短,但它们不应该短于完成工作所需的时间。只要没有任何阻塞锁,几秒钟的事务就不会有问题
如果你想指出一个不好的做法,即需要一个很好的解释为什么要使用它,那可能会使用
OptimisticLockType.DIRTY
。建议使用OptimisticLockType.VERSION
和默认值是有原因的