概述
事务(Transaction):指数据库中执行的一系列操作被视为一个逻辑单元,要么全部成功地执行,要么全部失败回滚,保证数据的一致性和完整性。
@Transactional注解是Spring框架提供的用于声明事务的注解,作用于类和方法上。
@Transcation注解
事务实现原理
Spring的事务是依靠动态代理技术实现的,是在程序运行时给代理对象创建代理类。如果方法或类上添加了@Transcation注解,spring会自动给方法或类创建一个代理类,代理类中包含了开关事务的代码和原始操作。
示意图如下(原始类指加了@Transcation的类):
@Transcation使用
1、事务生效的情况:
这里用一个demo举例子:更新一条数据,我们先删除数据,再插入新数据(主键自动递增)。我们希望删除或插入哪一方失败,数据库都能回滚。
1. 外层有事务,内层无事务
内外层哪一方有异常,全部都会回滚。
//外层 @Transactional public void update(Category category) { this.categoryDao.delete(category); this.insert(category);//无事务,但有异常 } //内层 public void insert(Category category) { this.categoryDao.insert(category); int a=2/0; }
结论:外层有事务,内层也会有事务
2. 外层事务(requierd),内层事务(not_supported)
事务隔离级别:not_supported:
先说说事务隔离级别为not_supported时,事务会被挂起,等于没有事务,有异常也不会回滚。
@Transactional(propagation = Propagation.NOT_SUPPORTED) public boolean deleteById(Integer cid) { int i = this.categoryDao.deleteById(cid); if(i>0) { throw new RuntimeException("xxx"); //有异常也不回滚 } return true; }
如果外层事务为required(不写类型默认required):内外层哪一方有异常,全部都会回滚。
//外层 @Transactional(propagation = Propagation.REQUIRED) public void updateById(Category category) { this.categoryDao.insert(category); this.deleteById(category.getCid()); } //内层 @Transactional(propagation = Propagation.NOT_SUPPORTED) public boolean deleteById(Integer cid) { int i = this.categoryDao.deleteById(cid); if(i>0) { throw new RuntimeException("xxx"); } return true; }
结论:不管内层是什么级别的事务、有没有事务,只以外层事务为准
总结:
为什么外层有事务,内层就有事务?借用下图就能理解,method1方法上有事务注解,method1调用了method2。
生成的代理类长什么样子呢:用事务“包”住了这两个方法的原始代码。
2、事务不生效的情况:
1. 外层无事务,内层有事务
内层有异常会回滚,和外层无关。外层有异常不会回滚。
//外层 public void update(Category category) { this.delete(category); this.categoryDao.insert(category); //int a=2/0; //外有异常,内无异常,删除成功,插入失败 } //内层 @Transactional void delete(Category category) { this.categoryDao.delete(category.getId); //int a=2/0; //外无异常,内有异常,删除、插入都失败 }
结论:内层有事务,不影响外层。
2. 异常被 catch 住,而且没有再次抛出异常
无论是外层异常还是内层异常,只要捕获以后没有抛出异常,都不会回滚。总的来说没有异常不会回滚。
//外层 @Transactional public void updateById(Category category) { try { this.categoryDao.deleteById(category.getCid()); this.categoryDao.insert(category); int a=2/0; }catch (java.lang.Exception e){ System.out.println("updateById异常"); } }
//外层 public void update(Category category) { this.delete(category); this.categoryDao.insert(category); } //内层 @Transactional void delete(Category category) { try{ this.categoryDao.delete(category.getId); int a=2/0; }catch(Exception e){ } }
解决办法:
捕获后再次抛出异常。无论是内层、外层,只要重新抛出异常,就可以回滚。
//外层 public void update(Category category) { this.delete(category); this.categoryDao.insert(category); } //内层 @Transactional void delete(Category category) { try{ this.categoryDao.delete(category.getId); int a=2/0; }catch(Exception e){ throw new RuntimeException("service层deleteById方法异常");//可以自定义异常 } }
结论:没有异常不会回滚。
3. 抛出RuntimeException或Error以外的异常
@Transcation有个属性(方法),管理何种异常会引发回滚。
默认情况下,事务只在RuntimeException和Error上回滚。
抛出RuntimeException和Error以外类型的异常,不会回滚。 常见的非运行时异常有:SQLException、IOException、FileNotFoundException、ReflectiveOperationException等等。
@Transactional void delete(Category category) throws SQLException{ this.categoryDao.delete(category.getId); int a=2/0; throw new SQLException("xxx"); //抛出异常也不会回滚 }
解决办法:
使用RollbackFor属 添加 要捕获的异常类型,这样除了RuntimeException和Error类型的异常,遇到Exception以及它的子类的异常,也会发生回滚。
@Transactional(rollbackFor = Exception.class) void delete(Category category) throws SQLException{ this.categoryDao.delete(category.getId); int a=2/0; throw new SQLException("xxx"); //这下就回滚了 }
结论:使用RollbackFor属性添加要捕获的异常类型
4. 子线程内异常
删除操作新开一个线程执行,并且执行中发生异常。结果是删除回滚,插入没回滚。
多线程环境下,内外层是两个事务,事务具有隔离性,事务之间不会互相干扰。
//外层 @Transactional(rollbackFor = java.lang.Exception.class) public void update(Category category) { Thread thread=new Thread(new Runnable() { @Override public void run() { delete(category);//删除 } }); this.categoryDao.insert(category);//插入 } //内层 @Transactional(rollbackFor = java.lang.Exception.class) void delete(Category category) { this.categoryDao.delete(category.getId); int a=2/0; //异常 }
解决办法:
使用Thread.UncaughtExceptionHandler接口捕获线程异常,主线程发现了异常,也跟着回滚。
注:事务还是多个事务
- 创建一个实现了Thread.UncaughtExceptionHandler接口的异常处理器类,该类将负责捕获未被捕获的(没加try-catch的)线程异常并进行处理:
public class CustomUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { // 在此可以处理未被捕获的线程异常 System.out.println("线程 " + t.getName() + " 发生了异常: " + e.getMessage()); } }
- 在主线程或创建的子线程中,设置自定义的异常处理器:
@Transactional(rollbackFor = java.lang.Exception.class) public void updateById(Category category){ Thread.setDefaultUncaughtExceptionHandler(new CustomUncaughtExceptionHandler());//加上这句 Thread thread=new Thread(new Runnable() { @Override public void run() { deleteById(category.getCid());//删除 } }); thread.start(); this.categoryDao.insert(category);//插入 }
结论:子线程异常抛给主线程,两者一起回滚。
5. 事务方法是private、static、final的
方法不能被重写,就不能生产代理类。
结论:java实现的动态代理的原理是代理类实现被代理类的相同接口,重写相同方法
6. 数据库不支持事务
总结
spring相当多的功能都用到了动态代理,还需要对这方面知识做个总结,学无止境啊。