背景
- 有一个定时任务需要扫描数据库,会把符合条件的数据组装成多个单据,并进行一系列的回写操作。
- 原先是将整个定时任务设置成一个大的事务,当有一份单据出现回写异常,前面正常的单据也会全部进行回滚。显然是不合理的,所以现在需要将每一份单据的插入和回写放到独立的小事务里面进行管理。
- 代码修改后,每一份单据可以单独进行管理了,但是却出现了重复订单号插入的异常。
- 单据流水号生成通过数据库实现
伪代码逻辑
@Transactional
public void createOrder() {
...
...
for() {
// 生成单据号 用的insert...on duplicate key update
generateBillNo();
...
...
// 相关单据处理
process();
}
}
@Transactional(REQUIRES_NEW)
public void process() {
insert();
...
writeBack();
}
解决过程
- 第一反应,小事务提交之后,回到原来的事务时跟原来的sqlsession用的不是同一个,所以流水号的值没有读到已经更新了的值。然后发现---想多了
- 连续调用生成订单号方法,发现只有第一次有自增,后续取到的值都是同一个值
@Transactional
public void createOrder() {
...
...
for() {
// 生成单据号 用的insert...on duplicate key update
generateBillNo();
generateBillNo();
generateBillNo();
...
...
// 相关单据处理
process();
}
}
- 对比大事务的代码逻辑,大事务在每次调用生成单据号方法的间隔中会有数据库的插入或者更新操作,进行测试发现在生成单据号的方法间加上数据库的插入或者更新操作,订单号的值自增就会成功
@Transactional
public void createOrder() {
...
...
for() {
// 生成单据号 用的insert...on duplicate key update
generateBillNo();
// 数据库插入或者更新操作
insert();
generateBillNo();
insert();
generateBillNo();
...
...
// 相关单据处理
process();
}
}
- 查阅资料发现,mybatis的一级缓存默认会开启,同一个事务中,只有发生任意的数据库插入或者更新操作,缓存的数据才会进行更新
- 所以在大事务中,调用生成单据号的方法间隔中,有相应单据的各种回写操作,所以缓存更新了,流水号也就能取到最新的值。
- 分成单独的事务进行管理后,所有更新数据库的操作都是在独立的一个事务中完成的,生成单据号的方法间就没有任何的更新插入操作,所以缓存就不会更新,读到的值也就一直不会变。
- 最后还有一个问题,生成单据号的方法,本身就是一个更新数据库的操作,为什么缓存没有更新成功??
最终发现,更新语句使用了@SELECT的注解。。。我G┻━┻I (ヽʕ̢̣̣̣̣̩̩̩̩`Д ́Ɂ̡̣̣̣̣̩̩̩̩ノ( A┻━┻O