编程型事务更加灵活
声明式事务只需要加在方法头加@Transactional注解即可开启事务,但是还是不太灵活,意味着整个方法所进行对数据库操作都要加进事务,当然一次查询也要进入事务,这并不是我们想要的,我们在update、insert操作上进行事务操作,方便进行回滚。
public Boolean transactionCommit(String userName) {
//查询用户
SysUser sysUser = userMapper.selectUserByUserName(userName,null);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
if (null != sysUser) {
//用户信息状态更新 status更新为1
userMapper.updateStatus(userName);
}
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
//再次查询
SysUser sysUser1 = userMapper.selectUserByUserName(userName,"1");
/log/.info("状态为1的用户信息"+JSON./toJSONString/(sysUser1));
return true;
}
非事务执行
在使用事务之前,我们都应该思考一下,是不是所有的数据库操作都需要在事务中执行?
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
addData();
addLog();
updateCount();
return Boolean.TRUE;
})
}
上面的例子中,其实addLog增加操作日志方法 和 updateCount更新统计数量方法,是可以不在事务中执行的,因为操作日志和统计数量这种业务允许少量数据不一致的情况。
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
addData();
return Boolean.TRUE;
})
addLog();
updateCount();
}
异步处理
是不是事务中的所有方法都需要同步执行?我们都知道,方法同步执行需要等待方法返回,如果一个事务中同步执行的方法太多了,势必会造成等待时间过长,出现大事务问题。
看看下面这个列子:
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
order();
delivery();
return Boolean.TRUE;
})
}
order方法用于下单,delivery方法用于发货,是不是下单后就一定要马上发货呢?
答案是否定的。
这里发货功能其实可以走mq异步处理逻辑。
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
order();
return Boolean.TRUE;
})
sendMq();
}
事务优化例子
背景
我最近开发写的一个接口,大致是这么一个逻辑,我需要根据页面的提交的数据生成一个收款单,整体接口处理的业务如下,我把它们写在了一个接口里,可以理解为这是一个大事物,这个接口执行的时间是相对比较长的,而且将这些逻辑全部写在一个接口里面,本身来说也是不太合理的。
优化方案-异步并行处理
事务里如果无法避免远程调用,那么肯定是需要进行异步调用,因为无法保证远程接口的及时响应性,CompletableFuture异步编排特性可以用到,task1和task2任务结束后,执行task3。
CompletableFuture<Object> task1 =CompletableFuture.supplyAsync(() -> {
System.out.println("单号check线程" + Thread.currentThread().getId());
//单号check接口 校验失败抛出异常
return "账单实体信息";
}, executor);
CompletableFuture<Object> task2 = CompletableFuture.supplyAsync(() -> {
System.out.println("收款单生成线程" + Thread.currentThread().getId());
try {
//收款单生成
return “账单编号”;
Thread.sleep(3000);
System.out.println("任务2结束:");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, executor);
//task1、task2 执行完执行task3 ,需要感知task1和task2的执行结果
CompletableFuture<Boolean> future = task1.thenCombineAsync(task2, (t1, t2) -> {
System.out.println("账单金额回写线程" + Thread.currentThread().getId());
// t1 、t2返回判断
//回写返回结果
return ture;
}, executor);
总结
处理大事务的6种办法:
- 少用@Transactional注解
- 将查询(select)方法放到事务外
- 事务中避免远程调用
- 事务中避免一次性处理太多数据
- 非事务执行
- 异步处理