Spring @Transactional 一次处理一个方法
Spring @Transactional one transaction of method at a time
所需的解决方案:
每年帐户都会注册到数据库并获得从 1 到 n 的唯一编号。明年完结后账号注册时从1到n重复
我有 table 存储(示例):
id , available_number, current_year
someId0, 8000 , 2000
someId1, 2500 , 2001
方法片段:
@Transactional
public AccountNumber getAndIncreaseCorrectAccountNumber(String current_year) {
AccountNumber accountNumber;
Optional<AccountNumber> foundAccountNumber = Optional.ofNullable(AccountNumberRepository.findByYear(current_year));
if(!foundAccountNumber .isPresent()) {
accountNumber = new AccountNumber();
accountNumber .setAvailableNumber(1L);
accountNumber .setCurrentYear(current_year);
} else {
accountNumber = foundAccountNumber.get();
accountNumber.setAvailableNumber(accountNumber.getAvailableNumber() + 1);
}
return accountRepository.save(accountNumber);
}
问题:
由于多个帐户在最后一天(最后一分钟)注册,因此帐户变得相同 available_number
。注意到 4 个帐户在 1 秒内注册了相同的 available_number
(注册之间的差异约为 0.1 秒)
我认为这与一笔交易开始然后另一笔交易中断而另一笔交易未完成 getting
和 saving
另一 gets
相同 available_number
思考如何解决:
- 已阅读有关
Isolation
和 SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
确实会阻止交易与其他交易的交互,但不会阻止两个交易获得相同的交易 available_number
。
另请阅读 Propagation
和 REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW)
id 的作用是,如果在前一个事务未完成时出现新事务,则先前冻结,然后新事务完成,然后首先解冻并完成。不清楚的是,在我的情况下,第一笔交易可能会获得价值,然后冻结,然后在使用相同的 available_number
完成新交易后,第一笔交易取消费用并以相同的 available_number
(或我是我理解错了吗?)
- 另一种选择是以某种方式在一次 SQL 调用中转换此方法。要编写
nativeQuery
update select,它具有逻辑并执行存储和返回对象(有什么建议吗?)。这样只要 @Transactional
应该够了吧?
- 有没有办法将方法
Transactions
存储在堆栈中并一次执行一个(之后关闭第一个事务并打开新事务)?
子问题:有没有办法模拟多个请求 @test
并检查新解决方案是否可行? (新帐户注册仅在明年进行)
正如@JBNizet 所写和我在其他文章中读到的那样,OptimisticLock 解决了这个问题,因为它只允许一个修改行的事务成功。
其他事务以 ObjectOptimisticLockingFailureException
抛出。
然后我使用 try{}catch{}
对 ObjectOptimisticLockingFailureException
进行递归以重定向回相同的方法
棘手的想法是 getAndIncreaseCorrectAccountNumber
的逻辑在深层,在同一事务中发生了许多其他 CRUD
逻辑,所以我需要将我的 try{}catch{}
拉到上面整个 @Transaction
顶层(否则主要对象被修改而不是回滚,所以 try{}catch{}
似乎在最上层 @Transaction
之上时工作)
在代码中我做了什么:
- 在
AccountNumber
中为 OptimisticLock 添加了 PostgreSQL 版本的行
@Column(name = "version_num") @Version private int version;
在存储库层 AccountNumberRepository
添加 @Lock(LockModeType.OPTIMISTIC)
就在方法上方
添加了 try{}catch{}
用于捕获 OptimisticLock 并启用递归到同一方法
public String submitSomeObjectWithRecursionHandling(@NotNull SomeObject someObject, @NotBlank String accountId){
try {
return submitSomeObject(someObject, accountId);
} catch (ObjectOptimisticLockingFailureException | OptimisticLockException e) {
e.printStackTrace();
return submitSomeObjectWithRecursionHandling(someObject, accountId);
}
}
submitSomeObject(someObject, accountId)
开始了 @Transaction
我没能实现的是编写集成测试。
所以我创建了 10 个准备提交 SomeObects
并从前端发送请求立即提交,它首先用复制的 accountNumber
重现了错误,后来在我修复后它显示递归是处理问题
如果您有一些方法可以在集成测试中测试此解决方案,请告诉我。
所需的解决方案:
每年帐户都会注册到数据库并获得从 1 到 n 的唯一编号。明年完结后账号注册时从1到n重复
我有 table 存储(示例):
id , available_number, current_year
someId0, 8000 , 2000
someId1, 2500 , 2001
方法片段:
@Transactional
public AccountNumber getAndIncreaseCorrectAccountNumber(String current_year) {
AccountNumber accountNumber;
Optional<AccountNumber> foundAccountNumber = Optional.ofNullable(AccountNumberRepository.findByYear(current_year));
if(!foundAccountNumber .isPresent()) {
accountNumber = new AccountNumber();
accountNumber .setAvailableNumber(1L);
accountNumber .setCurrentYear(current_year);
} else {
accountNumber = foundAccountNumber.get();
accountNumber.setAvailableNumber(accountNumber.getAvailableNumber() + 1);
}
return accountRepository.save(accountNumber);
}
问题:
由于多个帐户在最后一天(最后一分钟)注册,因此帐户变得相同 available_number
。注意到 4 个帐户在 1 秒内注册了相同的 available_number
(注册之间的差异约为 0.1 秒)
我认为这与一笔交易开始然后另一笔交易中断而另一笔交易未完成 getting
和 saving
另一 gets
相同 available_number
思考如何解决:
- 已阅读有关
Isolation
和SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
确实会阻止交易与其他交易的交互,但不会阻止两个交易获得相同的交易available_number
。 另请阅读Propagation
和REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW)
id 的作用是,如果在前一个事务未完成时出现新事务,则先前冻结,然后新事务完成,然后首先解冻并完成。不清楚的是,在我的情况下,第一笔交易可能会获得价值,然后冻结,然后在使用相同的available_number
完成新交易后,第一笔交易取消费用并以相同的available_number
(或我是我理解错了吗?) - 另一种选择是以某种方式在一次 SQL 调用中转换此方法。要编写
nativeQuery
update select,它具有逻辑并执行存储和返回对象(有什么建议吗?)。这样只要@Transactional
应该够了吧? - 有没有办法将方法
Transactions
存储在堆栈中并一次执行一个(之后关闭第一个事务并打开新事务)?
子问题:有没有办法模拟多个请求 @test
并检查新解决方案是否可行? (新帐户注册仅在明年进行)
正如@JBNizet 所写和我在其他文章中读到的那样,OptimisticLock 解决了这个问题,因为它只允许一个修改行的事务成功。
其他事务以 ObjectOptimisticLockingFailureException
抛出。
然后我使用 try{}catch{}
对 ObjectOptimisticLockingFailureException
进行递归以重定向回相同的方法
棘手的想法是 getAndIncreaseCorrectAccountNumber
的逻辑在深层,在同一事务中发生了许多其他 CRUD
逻辑,所以我需要将我的 try{}catch{}
拉到上面整个 @Transaction
顶层(否则主要对象被修改而不是回滚,所以 try{}catch{}
似乎在最上层 @Transaction
之上时工作)
在代码中我做了什么:
- 在
AccountNumber
中为 OptimisticLock 添加了 PostgreSQL 版本的行
@Column(name = "version_num") @Version private int version;
在存储库层
AccountNumberRepository
添加@Lock(LockModeType.OPTIMISTIC)
就在方法上方添加了
try{}catch{}
用于捕获 OptimisticLock 并启用递归到同一方法
public String submitSomeObjectWithRecursionHandling(@NotNull SomeObject someObject, @NotBlank String accountId){ try { return submitSomeObject(someObject, accountId); } catch (ObjectOptimisticLockingFailureException | OptimisticLockException e) { e.printStackTrace(); return submitSomeObjectWithRecursionHandling(someObject, accountId); } }
submitSomeObject(someObject, accountId)
开始了 @Transaction
我没能实现的是编写集成测试。
所以我创建了 10 个准备提交 SomeObects
并从前端发送请求立即提交,它首先用复制的 accountNumber
重现了错误,后来在我修复后它显示递归是处理问题
如果您有一些方法可以在集成测试中测试此解决方案,请告诉我。