首页 > 解决方案 > 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' 的更新被拒绝。这是为什么?

标签: javahibernatekotlinspring-data-jpa

解决方案


您没有声明任何事务边界。因此,对存储库的每次调用都在其自己的事务中运行。这就是实体作为save操作的一部分(再次)被加载的原因。

如果您将加载、等待、保存周期包装到单个事务中,您将看到您想要的行为。

在您的测试中,您可以使用Spring 参考文档中描述TransactionTemplate的用法来执行此操作。

关于后续问题:

关于为什么VERSION有效的任何想法?

我没有以下证明,但这是我强烈怀疑的。

所有列的原始状态都存储在DIRTY会话中,因此它是当前事务中加载操作的状态。

原始状态是实体的一部分,因此VERSION它可以跨越事务边界。

如果我必须将加载后的所有内容都包装到事务中,这取决于我在加载和保存之间所做的事情,这不是一个不好的做法吗?

虽然我同意交易应该尽可能短,但它们不应短于完成工作所需的时间。只要您没有任何阻塞锁,我就不会期望几秒钟的事务有问题。

如果你想指出一个不好的做法,即需要一个很好的解释为什么你使用它,那可能会使用OptimisticLockType.DIRTY. OptimisticLockType.VERSION推荐和默认是有原因的。


推荐阅读