zoukankan      html  css  js  c++  java
  • Spring @Transactional 事务机制

    几个概念要清楚:事务的传播机制,事务的边界

     

    工作原理

    运行配置@Transactional注解的测试类的时候,具体会发生如下步骤

    1)事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]

    2)事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象

     

     

    @Transactional不生效的情况

    1. @Transactional  在private 方法是不生效
    2. 在同一个bean里,嵌套的public方法@Transactional 也不生效

    解决方法

          简单粗暴,有需要就在调用service中就开@Transactional,如果实在不行,像里面有调用第三方资源,唔想事务开得太久,可以考虑新建另外一个service去开@Transactional。

     

     

     

    那么@Transactional如何工作?

     

    实现了EntityManager接口的持久化上下文代理并不是声明式事务管理的唯一部分,事实上包含三个组成部分:

    1. EntityManager Proxy本身

    2. 事务的切面

    3. 事务管理器

     

    事务的切面

    事务的切面是一个“around(环绕)”切面,在注解的业务方法前后都可以被调用。实现切面的具体类是TransactionInterceptor。

     

    事务的切面有两个主要职责:

    1. 在’before’时,切面提供一个调用点,来决定被调用业务方法应该在正在进行事务的范围内运行,还是开始一个新的独立事务。

    2. 在’after’时,切面需要确定事务被提交,回滚或者继续运行。

    3. 在’before’时,事务切面自身不包含任何决策逻辑,是否开始新事务的决策委派给事务管理器完成。

     

     

    事务管理器

    事务管理器需要解决下面两个问题:

    新的Entity Manager是否应该被创建?

    是否应该开始新的事务?

     

    这些需要事务切面’before’逻辑被调用时决定。事务管理器的决策基于以下两点:

    1. 事务是否正在进行

    2. 事务方法的propagation属性(比如REQUIRES_NEW总要开始新事务)

     

    如果事务管理器确定要创建新事务,那么将:

    1. 创建一个新的entity manager

    2. entity manager绑定到当前线程

    3. 从数据库连接池中获取连接

    4. 将连接绑定到当前线程

     

    特点:

    1. 使用ThreadLocal变量将entity manager和数据库连接都绑定到当前线程。

    2. 事务运行时他们存储在线程中,当它们不再被使用时,事务管理器决定是否将他们清除。

    3. 程序的任何部分如果需要当前的entity manager和数据库连接都可以从线程中获取。

     

     

    EntityManager proxy

     

    EntityManager proxy(前面已经介绍过)就是谜题的最后一部分。当业务方法调用entityManager.persist()时,这不是由entity manager直接调用的。

    而是业务方法调用代理,代理从线程获取当前的entity manager,前面介绍过事务管理器将entity manager绑定到线程。

    了解了@Transactional机制的各个部分,我们来看一下实现它的常用Spring配置。

     

     

     

    根据上面所述,我们所使用的客户代码应该具有如下能力:

    1)每次执行数据库命令的时候

    如果在事务的上下文环境中,那么不直接创建新的connection对象,而是尝试从DataSource实例的某个与DataSourceTransactionManager相关的某处容器中获取connection对象;在非事务的上下文环境中,直接创建新的connection对象

    2)每次执行完数据库命令的时候

    如果在事务的上下文环境中,那么不直接关闭connection对象,因为在整个事务中都需要使用该connection对象,而只是释放本次数据库命令对该connection对象的持有;在非事务的上下文环境中,直接关闭该connection对象

     

     

    在service类前加上@Transactional,声明这个service所有方法需要事务管理。每一个业务方法开始时都会打开一个事务。error是一定会回滚的

    Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked

    如果遇到checked意外就不回滚。

    如何改变默认规则:

    1 让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)

    2 让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

    3 不需要事务管理的(只查询的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)

    4 如果不添加rollbackFor等属性,Spring碰到Unchecked Exceptions都会回滚,不仅是RuntimeException,也包括Error。

     

    注意:

    如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}。

     

     

    1.检查型异常(Checked Exception)

     

      个人理解:所谓检查(Checked)是指编译器要检查这类异常,检查的目的一方面是因为该类异常的发生难以避免,另一方面就是让开发者去解决掉这类异常,所以称为必须处理(try ...catch)的异常。如果不处理这类异常,集成开发环境中的编译器一般会给出错误提示。

     

      例如:一个读取文件的方法代码逻辑没有错误,但程序运行时可能会因为文件找不到而抛出FileNotFoundException,如果不处理这些异常,程序将来肯定会出错。所以编译器会提示你要去捕获并处理这种可能发生的异常,不处理就不能通过编译。

     

    2.非检查型异常(Unchecked Exception)

     

      个人理解:所谓非检查(Unchecked)是指编译器不会检查这类异常,不检查的则开发者在代码的编辑编译阶段就不是必须处理,这类异常一般可以避免,因此无需处理(try ...catch)。如果不处理这类异常,集成开发环境中的编译器也不会给出错误提示。

     

      例如:你的程序逻辑本身有问题,比如数组越界、访问null对象,这种错误你自己是可以避免的。编译器不会强制你检查这种异常。

     

     


     

     

    常见坑点

     

    使用事务注解@Transactional 之前,应该先了解它的相关属性,避免在实际项目中踩中各种各样的坑点。

     

    常见坑点1:遇到检测异常时,事务默认不回滚。

     

    例如下面这段代码,账户余额依旧增加成功,并没有因为后面遇到SQLException(检测异常)而进行事务回滚!!

      @Transactional
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //然后遇到故障
            throw new SQLException("发生异常了..");
        }

    原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException及其子类)和程序错误(Error)才会进行事务回滚,显然SQLException并不属于这个范围。如果想针对检测异常进行事务回滚,可以在@Transactional 注解里使用
    rollbackFor 属性明确指定异常。例如下面这样,就可以正常回滚:

    @Transactional(rollbackFor = Exception.class)
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //然后遇到故障
            throw new SQLException("发生异常了..");
        }

    常见坑点2: 在业务层捕捉异常后,发现事务不生效。

    这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。例如:下面这段代码直接导致增加余额的事务回滚没有生效。

     @Transactional
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //谨慎:尽量不要在业务层捕捉异常并处理
            try {
                throw new SQLException("发生异常了..");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    不要小瞧了这些细节,往前暴露异常很大程度上很能够帮我们快速定位问题,而不是经常在项目上线后出现问题,却无法刨根知道哪里报错。

    推荐做法:若非实际业务要求,则在业务层统一抛出异常,然后在控制层统一处理。

     @Transactional
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //推荐:在业务层将异常抛出
            throw new RuntimeException("发生异常了..");
        }
    • Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类火方法上使用 @Transactional 注解。
    • @Transactional 注解标识的方法,处理过程尽量的简单。尤其是带锁的事务方法,能不放在事务里面的最好不要放在事务里面。可以将常规的数据库查询操作放在事务前面进行,而事务内进行增、删、改、加锁查询等操作
    • @Transactional 注解的默认事务管理器bean是“transactionManager”,如果声明为其他名称的事务管理器,需要在方法上添加@Transational(“managerName”)。
    • @Transactional 注解标注的方法中不要出现网络调用、比较耗时的处理程序,因为,事务中数据库连接是不会释放的,如果每个事务的处理时间都非常长,那么宝贵的数据库连接资源将很快被耗尽。
  • 相关阅读:
    VS2005进行WAP开发中的控件排列问题
    WAP中图像列表的设计
    List分页存在的问题
    vs.net2005下的WAP开发之设备仿真器
    用户控件中RedirectToMobilePage的使用
    ASP.NET网站发布问题
    asp.net开发WAP时表单提交的问题及粗略的解决
    如何在objectlist上显示两个字段的连接??
    VS2008 快捷键大全
    [翻译]25招改善你的jQuery [2]
  • 原文地址:https://www.cnblogs.com/leeego-123/p/11498327.html
Copyright © 2011-2022 走看看