一、前述
一直以来事务都是很多小伙伴的心病,今天我也踩了一次坑,记录分享下,加深理解
二、代码以及讲解
@Service
@Transactional
public class MiniUserServiceImpl extends ServiceImpl<MiniUserDao, MiniUser> implements MiniUserService {
private final static Logger logger = LoggerFactory.getLogger("MINI_USER_LOGGER");
@Resource
private MiniUserDao miniUserDao;
@Override
public void add(MiniUserAddDto miniUserAddDto){
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new Runnable() {
@Override
public void run( ) {
try {
for (int i = 0; i <3; i++) {
test(miniUserAddDto);
}
}catch (Exception e){
FaithSysException.throwFaithSysException(FaithSysExceptionEnum.APP_KEY_ERROR);
}
}
});
}
private void test(MiniUserAddDto miniUserAddDto) throws Exception{
try {
logger.info("添加小程序用户,请求参数:[{}]",miniUserAddDto.toString());
MiniUser miniUser = new MiniUser();
BeanUtil.copyProperties(miniUserAddDto,miniUser);
miniUser.setCtime(new Date());
miniUserDao.insert(miniUser);
async();
logger.info("添加小程序用户成功,插入id:[{}]",miniUser.getId());
}catch (Exception e){
logger.error("添加小程序用户失败,errMsg:[{}]",e.toString());
FaithSysException.throwFaithSysException(FaithSysExceptionEnum.INTERNAL_ERROR);
}
}
@Async
public void async()throws Exception{
Thread.sleep(2000);
MiniUser id = miniUserDao.selectOne(new QueryWrapper<MiniUser>().eq("id", 1231));
id.getAddress();
}
}
1、在整个 service 上添加了 @Transactional 注解,表示式声明式事务
2、接口请求进来 add 方法,定义线程池,执行调用 test 方法,test 方法里先保存一个对象,然后异步调用 async 异步方法
3、async 方法里的 sql 查询出来时特意查询不存在的数据行,所以对象为 Null,调用 get 方法,就会报错空指针异常
4、最后查询数据的时候,发现在 test 里插入的数据行,竟然没有回滚,最后发现,
4.1、不管是线程池创建的线程,还是 new Thread 出来的线程,线程里如果抛了异常出来,在 run 方法里,都不会抛出来异常,声明式事务 @Transactional 根本感知不到有异常了,但是这个异常又会终止程序的执行,这就很头疼了
4.2、异步注解 @Async 如果标注在调用类的方法上,那么调用类调用的时候,该注解会失效,导致还是同步进行
5、最终,尝试了另外的办法,就是在方法里创建一个事务管理器,用来手动的提交事务以及回滚事务,然后创建一个新的 service ,将异步方法放到新的 service 里面,通过在原来的 service 注入新的 service 来调用异步方法,代码修改如下所示:
MiniUserServiceImpl
@Service
@Transactional
public class MiniUserServiceImpl extends ServiceImpl<MiniUserDao, MiniUser> implements MiniUserService {
private final static Logger logger = LoggerFactory.getLogger("MINI_USER_LOGGER");
@Resource
private MiniUserDao miniUserDao;
@Autowired
private PlatformTransactionManager tx;
@Autowired
private Test test;
@Override
public void add(MiniUserAddDto miniUserAddDto){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = tx.getTransaction(def);
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new Runnable() {
@Override
public void run( ) {
try {
for (int i = 0; i <3; i++) {
test(miniUserAddDto);
}
tx.commit(status); // 提交事务
}catch (Exception e){
FaithSysException.throwFaithSysException(FaithSysExceptionEnum.APP_KEY_ERROR);
tx.rollback(status); // 回滚事务
}
}
});
}
private void test(MiniUserAddDto miniUserAddDto) throws Exception{
try {
logger.info("添加小程序用户,请求参数:[{}]",miniUserAddDto.toString());
MiniUser miniUser = new MiniUser();
BeanUtil.copyProperties(miniUserAddDto,miniUser);
miniUser.setCtime(new Date());
miniUserDao.insert(miniUser);
test.async();
logger.info("添加小程序用户成功,插入id:[{}]",miniUser.getId());
}catch (Exception e){
logger.error("添加小程序用户失败,errMsg:[{}]",e.toString());
FaithSysException.throwFaithSysException(FaithSysExceptionEnum.INTERNAL_ERROR);
}
}
Test:
@Service
public class Test {
@Resource
private MiniUserDao miniUserDao;
@Async
public void async()throws Exception{
Thread.sleep(5000);
MiniUser id = miniUserDao.selectOne(new QueryWrapper<MiniUser>().eq("id", 1231));
id.getAddress();
}
}
5.1、这样,当 Test 的方法里异常的时候,事务也不会回滚,因为是异步的线程,所以事务会失效,也就不会回滚,将 @Async 去掉,事务就不会失效
5.2 、这里值得注意的是如果用下面的代码创建事务管理器,会报错空指针异常,报错行代码为:
PlatformTransactionManager txManager = ContextLoader.getCurrentWebApplicationContext().getBean(PlatformTransactionManager.class);
用注入的方式来创建
6、还有一种可行的办法,就是使用 UncaughtExceptionHandler 来捕获未检查的异常,这里不多做说明