【问题标题】:Issue with OptimisticLockType.DIRTY not working as expectedOptimisticLockType.DIRTY 无法按预期工作的问题
【发布时间】:2021-06-21 04:05:14
【问题描述】:

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 = "?",
)

我正在尝试模拟一个多线程环境(或运行同一应用的多个实例)同时从数据库中获取一个实体,并对其进行更改和持久化。 p>

当第一个保存任务运行时(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]

当第二个保存任务运行时(5s 后):

=== 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 的更新被拒绝。这是为什么呢?

【问题讨论】:

    标签: java hibernate kotlin spring-data-jpa


    【解决方案1】:

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

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

    在您的测试中,您可以使用TransactionTemplate 来执行此操作,而usage is described in the Spring Reference Documentation 则为。

    关于后续问题:

    知道为什么VERSION 有效吗?

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

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

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

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

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

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

    【讨论】:

    • 对为什么 VERSION 可以在没有事务的情况下工作有任何想法吗?另外,如果我必须将加载后的所有内容都包装到事务中,这取决于我在加载和保存之间所做的事情,这不是一个不好的做法吗?示例:我可能在保存/加载之间执行 http 请求,这可能会使事务挂起太久。
    • 感谢您的澄清。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-23
    • 2014-12-09
    • 2016-01-13
    • 2020-09-21
    相关资源
    最近更新 更多