隔离级别
在 TransactionDefinition.java 接口中, 定义了 "四种" 的隔离级别枚举:
- /**
- * [Spring 独有] 使用后端数据库默认的隔离级别
- *
- * MySQL 默认采用的 REPEATABLE_READ 隔离级别
- * Oracle 默认采用的 READ_COMMITTED 隔离级别
- */
- int ISOLATION_DEFAULT = -1;
- /**
- * 最低的隔离级别, 允许读取尚未提交的数据变更, 可能会导致脏读, 幻读或不可重复读
- */
- int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
- /**
- * 允许读取并发事务已经提交的数据, 可以阻止脏读, 但是幻读或不可重复读仍有可能发生
- */
- int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
- /**
- * 对同一字段的多次读取结果都是一致的, 除非数据是被本身事务自己所修改, 可以阻止脏读和不可重复读, 但幻读仍有可能发生.
- */
- int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
- /**
- * 最高的隔离级别, 完全服从 ACID 的隔离级别. 所有的事务依次逐个执行, 这样事务之间就完全不可能产生干扰, 也就是说, 该级别可以防止脏读, 不可重复读以及幻读.
- *
- * 但是这将严重影响程序的性能. 通常情况下也不会用到该级别.
- */
- int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
事务的传播级别
事务的传播行为, 指的是当前带有事务配置的方法, 需要怎么处理事务; 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行;
需要注意, 事务的传播级别, 并不是数据库事务规范中的名词, 而是 Spring 自身所定义的. 通过事务的传播级别, Spring 才知道如何处理事务, 是创建一个新事务呢, 还是继续使用当前的事务;
在 TransactionDefinition.java 接口中, 定义了三类七种传播级别:
- // ========== 支持当前事务的情况 ==========
- /**
- * 如果当前存在事务, 则使用该事务.
- * 如果当前没有事务, 则创建一个新的事务.
- */
- int PROPAGATION_REQUIRED = 0;
- /**
- * 如果当前存在事务, 则使用该事务.
- * 如果当前没有事务, 则以非事务的方式继续运行.
- */
- int PROPAGATION_SUPPORTS = 1;
- /**
- * 如果当前存在事务, 则使用该事务.
- * 如果当前没有事务, 则抛出异常.
- */
- int PROPAGATION_MANDATORY = 2;
- // ========== 不支持当前事务的情况 ==========
- /**
- * 创建一个新的事务.
- * 如果当前存在事务, 则把当前事务挂起.
- */
- int PROPAGATION_REQUIRES_NEW = 3;
- /**
- * 以非事务方式运行.
- * 如果当前存在事务, 则把当前事务挂起.
- */
- int PROPAGATION_NOT_SUPPORTED = 4;
- /**
- * 以非事务方式运行.
- * 如果当前存在事务, 则抛出异常.
- */
- int PROPAGATION_NEVER = 5;
- // ========== 其他情况 ==========
- /**
- * 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行.
- * 如果当前没有事务, 则等价于 {@link TransactionDefinition#PROPAGATION_REQUIRED}
- */
- int PROPAGATION_NESTED = 6;
- @Transaction
@Transaction 注解是 Spring 的 tx 模块提供的, 使用 AOP 实现的事务控制, 即底层为动态代理;
@Transaction 可以作用于接口, 接口方法, 类以及类方法上; 当作用于类上时, 该类的所有 public 方法将都具有该类型的事务属性, 也可以在方法级别使用该标注来覆盖类级别的定义;
事务失效
1. 异常类型错误, 默认是 RuntimException 才会回滚
2. 异常被 catch 后没有抛出, 需要抛异常才能回滚
3. 是否发生自身调用的问题
4. 注解所在位置是否为 public 修饰
5. 数据源没有配置事务管理器
6. 不支持事务的引擎, 如 MyIsam
下面是一个事务失效的例子
- @Slf4j
- @Service
- public class OrderServiceImpl implements OrderService {
- @Autowired
- private OrderMapper orderMapper;
- @Transactional
- @Override
- public void parent() {
- try {
- this.child();
- } catch (Exception e) {
- log.error("插入异常", e);
- }
- Order order = new Order();
- order.setOrderNo("parent");
- order.setStatus("0");
- order.setTitle("parent");
- order.setAmount("1000");
- orderMapper.insert(order);
- }
- @Transactional
- @Override
- public void child() {
- Order order = new Order();
- order.setOrderNo("child");
- order.setStatus("0");
- order.setTitle("child");
- order.setAmount("2000");
- orderMapper.insert(order);
- throw new RuntimeException();
- }
- }
当调用 parent 方法时, 会调用 child 方法, 但执行完成后插入的记录有两条, 一条是 parent 的, 一条是 child 的;
这是因为上面的 parent 方法调用的 child 方法出现问题,@Transaction 是基于 AOP 的方式进行事务控制的 (CglibAopProxy.java 进行方法拦截, TransactionInterceptor.java 进行代理对象调用执行方法), 需要使用的是代理对象调用方法, 上面的代码使用的还是 this, 即当前实例化对象, 因此执行 this.child(), 方法不能被拦截增强;
将上面的 parent 方法修改如下
- @Autowired
- private ApplicationContext context;
- private OrderService orderService;
- @PostConstruct
- public void init() {
- orderService = context.getBean(OrderService.class);
- }
- @Transactional
- @Override
- public void parent() {
- try {
- // 获取代理对象, 通过代理对象调用 child()
- orderService.child();
- // 获取代理对象
- //OrderService orderService = (OrderService) AopContext.currentProxy();
- //orderService.child();
- } catch (Exception e) {
- log.error("插入异常", e);
- }
- Order order = new Order();
- order.setOrderNo("parent");
- order.setStatus("0");
- order.setTitle("parent");
- order.setAmount("1000");
- orderMapper.insert(order);
- }
执行 parent 方法, 当出现异常的时候, 事务会进行回滚;
如果想实现当调用 parent 方法时, 调用 child 方法发生异常, 只回滚 child 方法插入的数据, parent 方法插入的数据不回滚, 修改如下
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- @Override
- public void child() {
- Order order = new Order();
- order.setOrderNo("child");
- order.setStatus("0");
- order.setTitle("child");
- order.setAmount("2000");
- orderMapper.insert(order);
- throw new RuntimeException();
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
如果当前存在事务, 则挂起事务并开启一个新事务执行, 新事务执行完毕后, 唤醒之前的挂起的事务, 则继续执行; 如果当前不存在事务, 则新建一个事务;
来源: http://www.bubuko.com/infodetail-3376974.html