平时我们开发需要满足事务时,我们就会想到@Transactional注解,但是添加这个注解后也可能也不会满足我们的需求,甚至事务不生效。下面我就分析下事务失效的原因及事务如何使用。
下面开始介绍失效原因及解决方案。
目录
1.service没有被 Spring 管理
// @Service 必须要添加注解,被spring管理 public class TestTransactionalServiceImpl implements TestTransactionalService{ @Override @Transactional public void testTransactional() { } } 解决方案:添加@Service注解,使得事务类被spring管理
2.异常被捕捉了(异常被吃了)
@Transactional public void testTransactional() { try { // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); // 手动制造异常 result = 1/0; } catch(Exception e){ // 本质:异常被捕捉,事务检测不到有错误的存在 e.printStackTrace(); log.error("程序出现异常了"); } }本质:异常被捕捉,事务检测不到有错误的存在
解决方案1:把异常再次抛出 throw e;
} catch(Exception e){ e.printStackTrace(); log.error("程序出现异常了"); throw e; // 把异常再次抛出 }
解决方案2:在捕捉的异常中,手动添加回滚
} catch(Exception e){ e.printStackTrace(); log.error("程序出现异常了"); // 手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }
解决方案3(推荐):将业务代码单独抽出,在抽出的方法上加事务
@Override public void testTransactional() { try { testRollback(); } catch(Exception e){ e.printStackTrace(); log.error("程序出现异常了"); } } @Transactional public void testRollback(){ // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); // 手动制造异常 result = 1/0; }
3.调用本类方法,事务失效
@Override public void testTransactional() { try { testRollback(); } catch(Exception e){ e.printStackTrace(); } } @Transactional // 事务未生效 public void testRollback(){ // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); // 手动抛出异常 result = 1/0; }本质原因:因为发生了自身调用,调类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效。
解决方案:需要在testTransactional()方法上加@Transactional注解,testRollback()方法可以不用加(本类方法加了也不起作用)。
@Override @Transactional public void testTransactional() { try { testRollback(); } catch(Exception e){ e.printStackTrace(); } } // @Transactional(propagation = Propagation.REQUIRES_NEW) 加了也不起作用 public void testRollback(){ // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); // 手动抛出异常 result = 1/0; }
4.多线程调用
@Override @Transactional public void testTransactional() { ExecutorService executorService = Executors.newCachedThreadPool(); Future<Boolean> future = executorService.submit(() -> { // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); // 手动抛出异常 result = 1/0; return true; }); }本质:spring的事务是通过数据库连接来实现,数据库连接spring是放在threadLocal里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务。
解决方案:将业务代码单独抽出,在抽出的方法上加事务
5.异常类型错误
// @Transactional(rollbackFor = FdException.class) public void testTransactional() { try { // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); }catch(FdException e){ throw e; } } 这样事务是不生效的,因为默认回滚的是:RuntimeException及其子类,如果你想触发其他异常的回滚,需要在注解上配置一下:(rollbackFor = FdException.class)
6.非public方法修饰
@Transactional protected void testTransactional() { ExecutorService executorService = Executors.newCachedThreadPool(); Future<Boolean> future = executorService.submit(() -> { // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); // 手动抛出异常 result = 1/0; return true; }); }spring事务默认生效的方法权限都必须是public修饰
7.方法用final修饰
@Transactional public final void testRollback(){ // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); // 手动抛出异常 result = 1/0; }原因:因为spring事务是用动态代理实现,因此如果方法使用了final修饰,则代理类无法对目标方法进行重写,所以就不存在事务功能
解决方案:去掉final修饰
8.方法用static修饰
@Transactional public static void testRollback(){ // 修改数据===1=== AdCourse adCourse = new AdCourse(); adCourse.setId(5203314); adCourse.setName("计算机科学"); int i = adCourseMapper.updateById(adCourse); // 修改数据===2=== adCourse.setId(5219314); adCourse.setName("数据2"); int result = adCourseMapper.updateById(adCourse); // 手动抛出异常 result = 1/0; }原因:因为spring事务是用动态代理实现,因此如果方法使用了static修饰,则代理类无法对目标方法进行重写,所以就不存在事务功能
解决方案:去掉static修饰
9.选用了数据库引擎不支持事务的存储引擎
选用了数据库引擎不支持事务的存储引擎。比如mysql中选用MyISAM