zoukankan      html  css  js  c++  java
  • Spring 事务失效

      

    • 隔离级别

      在 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)
     如果当前存在事务,则挂起事务并开启一个新事务执行,新事务执行完毕后,唤醒之前的挂起的事务,则继续执行;如果当前不存在事务,则新建一个事务;
  • 相关阅读:
    SOUI更新到2.0
    第三十一篇:SOUI布局之相对于特定兄弟窗口
    SOUI与WTL
    在SOUI中非半透明窗口如何实现圆角窗口?
    拥抱ARM妹纸第二季 之 第一次 点亮太阳
    拥抱ARM妹子第二季 之 序:我和春天有个约会
    解决自定义控件窗体缩放时闪烁
    The 50 Most Essential Pieces of Classical Music
    嵌入式开发目录
    C中浮点数转字符串
  • 原文地址:https://www.cnblogs.com/coder-zyc/p/12181688.html
Copyright © 2011-2022 走看看