有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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) 个答案

  1. # 1 楼答案

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

    如果将加载、等待、保存周期打包到单个事务中,您将看到所需的行为

    在测试中,可以使用^{}来实现这一点,其中usage is described in the Spring Reference Documentation

    关于后续问题:

    Any ideas of why VERSION works?

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

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

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

    If I have to wrap everything since load into a transaction, wouldn’t that be a bad practice depending on what I’m doing between load and save?

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

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