从源码分析 Spring 基于注解的事务

在spring引入基于注解的事务(@Transactional)之前,我们一般都是如下这样进行拦截事务的配置:

    <!-- 拦截器方式配置事务 --><tx:advice id="transactionAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="add*" propagation="REQUIRED" /><tx:method name="append*" propagation="REQUIRED" /><tx:method name="insert*" propagation="REQUIRED" /><tx:method name="save*" propagation="REQUIRED" /><tx:method name="update*" propagation="REQUIRED" /><tx:method name="modify*" propagation="REQUIRED" /><tx:method name="edit*" propagation="REQUIRED" /><tx:method name="delete*" propagation="REQUIRED" /><tx:method name="remove*" propagation="REQUIRED" /><tx:method name="repair" propagation="REQUIRED" /><tx:method name="delAndRepair" propagation="REQUIRED" /><tx:method name="get*" propagation="SUPPORTS" /><tx:method name="find*" propagation="SUPPORTS" /><tx:method name="load*" propagation="SUPPORTS" /><tx:method name="search*" propagation="SUPPORTS" /><tx:method name="datagrid*" propagation="SUPPORTS" /><tx:method name="*" propagation="SUPPORTS" /></tx:attributes></tx:advice><aop:config><aop:pointcut id="transactionPointcut" expression="execution(* net.aazj.service..*Impl.*(..))" /><aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" /></aop:config>

这种方式明显的缺点是,不太容易理解,并且限定了service层的方法名称的前缀,没有模板的话写起来也很难,很容易写错。

因此在spring中引入了基于注解的事务配置方法之后,我们应该抛弃这种事务配置方法了。基于注解 @Transactional 的事务配置具有简单,灵活的优点。下面看一个例子:

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{@Autowiredprivate UserMapper userMapper;@Transactional (propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=true)public User getUser(int userId) {return userMapper.getUser(userId);}@Transactionalpublic void addUser(String username){userMapper.addUser(username);
//      int i = 1/0;    // 测试事务的回滚}@Transactional (rollbackFor = BaseBusinessException.class)public void addAndDeleteUser(String username, int id) throws BaseBusinessException{userMapper.addUser(username);        this.m1();        userMapper.deleteUserById(id);}private void m1() throws BaseBusinessException {throw new BaseBusinessException("xxx");}
}

首先在service类上声明了@Transactional,表明类中的所有方法都需要运行在事务中,然后在方法中可以指定具体的事务特性,方法中的@Transactional会覆盖类上的@Transactional。

下面我们从源码的角度(从源码的学习可以给我们实打实的比较深入理解,而且不会出错,二手资料总是会有时延的)来探究一下它们:

 public @interface Transactional

注解@Transactional的属性有:propagation, isolation, timeout, readOnly, rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName

propagation, isolation, timeout, readOnly都有默认值,而rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName默认值都是空的。

我们具体看下我们可能会用到的属性:propagation, isolation, readOnly, rollbackFor

1)propagation指定事务的传播属性

 public enum Propagation

propagation可以取如下的值:REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED

我们一般会只用:REQUIRED(默认值), SUPPORTS,其它的取值基本上不使用(具体可以参考上面源码中的注释,已经很详细了)。

propagation=Propagation.REQUIRED:表示该方法或类必须要事务的支持,如果已经是在一个事务中被调用,那么就使用该事务,如果没有在一个事务中,那么就新建一个事务。

propagation=Propagation.SUPPORTS:表示该方法或类支持事务,如果已经是在一个事务中被调用,那么就使用该事务,如果没有在一个事务中,也可以。

2)isolation指定事务的隔离级别

 public enum Isolation

isolation可以取如下的值:DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE

我们一般只使用:DEFAULT(默认值),因为我们一般是直接在数据库的层面上来设置事务的隔离级别,很少会在应用层来设置隔离基本。

isolation=Isolation.DEFAULT:表示使用下层数据库指定的隔离级别

3)readOnly为只读,利于数据库优化器进行优化。默认值为 false。所以对于只对数据库进行读取的方法,我们可以如下指定:

    @Transactional (propagation=Propagation.SUPPORTS)public User getUser(int userId) {return userMapper.getUser(userId);}

表示:有事务则使用当前事务,没有事务则不使用事务。最大限度的利于数据库的优化器进行优化。

如果一定要使用事务的话,也可以这样使用readOnly=true来优化:

    @Transactional (propagation=Propagation.REQUIRED,readOnly=true)public User getUser(int userId) {return userMapper.getUser(userId);}

这就是 readOnly 的作用。表示有事务则使用当前事务,如果没有事务,则新建一个只读事务。其实 上面的 propagation=Propagation.REQUIRED也是可以去掉的,因为他是默认值!

1

2

3

4

@Transactional (readOnly=true)

public User getUser(int userId) {

    return userMapper.getUser(userId);

}

表示强制事务,并且是 只读事务。readOnly 要注意有点的是,readOnly只有在 有事务时,才会生效,如果没有事务,那么 readOnly 是会被忽略的。比如:

@Transactional(propagation=Propagation.SUPPORTS, readonly=true)

表示的就是 没有事务 也是可以的,有事务的话,就一定是 只读事务。

4)rollbackFor,其实该属性也很少使用,而且经常被误用。表示抛出什么异常时,会回滚事务。异常分为受检异常(必须进行处理或者重新抛出)和非受检异常(可以不进行处理)。在遇到非受检异常时,事务是一定会进行回滚的。rollbackFor用于指定对于何种受检异常发生时,进行回滚。因为受检异常,我们必须进行处理或者重新抛出,所以只有一种情况下我们要使用rollbackFor来指定,就是我们不处理异常,直接抛出受检异常,并且我们需要方法在抛出该异常时,进行回滚,如上面的例子:

    @Transactional (rollbackFor = BaseBusinessException.class)public void addAndDeleteUser(String username, int id) throws BaseBusinessException{userMapper.addUser(username);this.m1();userMapper.deleteUserById(id);}private void m1() throws BaseBusinessException {throw new BaseBusinessException("xxx");}

因为 m1 方法抛出受检异常,我们在 addAndDeleteUser 方法中不对该异常进行处理,而是直接抛出,如果我们希望 userMapper.addUser(username) 和 userMapper.deleteUserById(id) 要么都成功,要么都失败,此时我们则应该指定:rollbackFor = BaseBusinessException.class ,进行回滚。

所以只有我们的方法声明要抛出一个受检异常时,我们才应该使用 rollbackFor 属性来进行处理。如果我们在 addAndDeleteUser  方法中对 m1 方法的受检异常进行了处理,那么就没有必要使用 rollbackFor 了:

    public void addAndDeleteUser(String username, int id){userMapper.addUser(username);try{this.m1();}catch(BaseBusinessException e){// 处理异常,比如记录进日志文件等}userMapper.deleteUserById(id);}

因为我们处理了 m1 方法的异常,那么就不会有受检异常导致 userMapper.addUser(username) 和 userMapper.deleteUserById(id) 这两个方法一个执行成功,一个没有执行。而非受检异常默认就会回滚。

受检异常是必须进行处理或者重新声明抛出的。只有声明重新抛出受检异常时,才会需要使用 rollbackFor 属性。所以下面的方式就属于滥用 rollbackFor 了:

    @Transactional (rollbackFor=Exception.class)public void addAndDeleteUser2(String username, int id){userMapper.addUser(username);userMapper.deleteUserById(id);}

因为:受检异常是必须进行处理或者重新声明抛出,而我们既没有进行处理,也没有重新抛出,就说明他绝对不可能会抛出受检异常了。而只会抛出未受检异常,而未受检异常,默认就会回滚,所以上面的 @Transactional (rollbackFor=Exception.class) 完全是多余的。

总结

1)@Transactional 的默认值为:@Transactional (propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false,

timeout=TransactionDefinition.TIMEOUT_DEFAULT),默认值已经适合绝大多数情况,所以我们一般使用 @Transactional 进行注解就够了。

2)只有当默认值不符合我们的需要时才给@Transactional的属性指定值,一般也就指定:propagation=Propagation.SUPPORTS 和 readOnly=true,其它的属性和值一般很少使用非默认值。所以我们前面的UserServiceImpl类可以重构如下:

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{@Autowiredprivate UserMapper userMapper;@Transactional (readOnly=true)public User getUser(int userId) {return userMapper.getUser(userId);}public void addUser(String username){userMapper.addUser(username);int i = 1/0;    // 测试事务的回滚}public void deleteUser(int id){userMapper.deleteUserById(id);
//      int i = 1/0;    // 测试事务的回滚}@Transactional (rollbackFor = BaseBusinessException.class)public void addAndDeleteUser(String username, int id) throws BaseBusinessException{userMapper.addUser(username);this.m1();userMapper.deleteUserById(id);}private void m1() throws BaseBusinessException {throw new BaseBusinessException("xxx");}
}

addUser 和 deleteUser 因为会继承上的 @Transactional ,所以无需另外指定了,只有当类上指定的 @Transactional 不适合时,才需要另外在方法上进行指定。

3)所以我们只实际情况中,我们只需要使用下面三者来进行注解事务的配置:

@Transactional,

@Transactional (readOnly=true),

@Transactional (propagation=Propagation.SUPPORTS, readOnly=true),

@Transactional (rollbackFor = xxException.class),其它都可以保持默认值,其它的非默认值极少使用。

另外

要使用@Transactional来进行注解事务配置,必须要在spring的配置文件中加入下面的配置说明,启用基于注解的配置:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

补充说明

也许你会觉得奇怪,userMapper.getUser(userId); 这些涉及到数据库访问的方法,为什么不会抛出 SQLException 受检异常呢?如果手动注册驱动,然后获取链接,进行数据库操作时,是会有许多的受检异常要处理的:

        try {Class.forName("com.mysql.jdbc.Driver");} catch (ClassNotFoundException e) {// ...}String url="jdbc:mysql://localhost:3306/databasename";try {Connection conn = (Connection) DriverManager.getConnection(url,"username","password");} catch (SQLException e) {// ...} 

哪为什么在这里userMapper.getUser(userId); 就不需要处理受检异常呢?其实这是spring的功能,spring为了将我们从那些十分麻烦的受检异常比如SQLException中解救处理,将所有的数据库访问层的受检异常转嫁到 Spring 的 非受检异常RuntimeException 体系中来——DataAccessException

转载来源:http://www.cnblogs.com/digdeep/p/4458067.html 感谢原作者的文章贡献,如有侵权,请及时联系,以便删除

从源码分析 Spring 基于注解的事务相关推荐

  1. 源码分析 - Spring Security OAuth2 生成 token 的执行流程

    说明 本文内容全部基于 Spring Security OAuth2(2.3.5.RELEASE). OAuth2.0 有四种授权模式, 本文会以 密码模式 来举例讲解源码. 阅读前, 需要对 OAu ...

  2. Spring 源码分析衍生篇十三 :事务扩展机制 TransactionSynchronization

    文章目录 一.前言 二.TransactionSynchronization 1. TransactionSynchronization 1.1 TransactionSynchronization ...

  3. 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  4. 源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  5. PHP扩展编写、PHP扩展调试、VLD源码分析、基于嵌入式Embed SAPI实现opcode查看

    catalogue 1. 编译PHP源码 2. 扩展结构.优缺点 3. 使用PHP原生扩展框架wizard ext_skel编写扩展 4. 编译安装VLD 5. Debug调试VLD 6. VLD源码 ...

  6. spring源码分析之cache注解

    Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache 或者 OSCache),而是一个对缓存使用的抽象 ...

  7. Spring源码分析-Spring事务源码分析

    导语      在配置Spring事务管理的时候会用到一个类TransactionInterceptor,从下面的类关系图中可以看到TransactionInterceptor继承了MethodInt ...

  8. 通过@MapperScan源码了解Spring自定义注解扫描器

    我们在使用springboot 整合MyBatis时,需要在启动类上添加上@MapperScan注解,并写入mapper接口的包路径,然后我们就能通过从spring IOC容器中取对应的mapper的 ...

  9. ArrayList源码分析(基于JDK1.6)

    不积跬步,无以至千里:不积小流,无以成江海.从基础做起,一点点积累,加油! <Java集合类>中讲述了ArrayList的基础使用,本文将深入剖析ArrayList的内部结构及实现原理,以 ...

最新文章

  1. Windows 2003 标准版_企业版_SP1_SP2_R2的区别
  2. python throw_Python 生成器与它的 send,throw,close 方法(转帖以及记录)
  3. JUL执行原理和流程
  4. 前端学习(2650):composition组件
  5. 【CodeForces - 1038A 】Equality (思维水题,预处理字符串)
  6. mysql-多表查询-内联查询左查询
  7. sklearn模型使用贝叶斯优化调参(以随机森林为例)
  8. hbase 协处理器 部署_hbase协处理器概念及知识点总结
  9. hessian无法获取连接_PPPoE拨号设置完成后无法上网解决方法【详解】
  10. BZOJ4237 稻草人
  11. 模糊数学(一):模糊集及其表示
  12. git 内网搭建_Gitlab搭建内网服务器
  13. 桌面点击鼠标右键一直显示转圈卡住如何解决
  14. php sphinx配置,sphinx的配置和管理
  15. 2021计算机考研科目时间表,2021计算机考研科目408有哪些?
  16. 定时任务二:No thread-bound request found
  17. Openstack_Nova部署(t版)
  18. 车辆跟踪 matlab,汽车路径跟踪matlab程序
  19. 如何用人工智能高效选研究题目?
  20. UC何小鹏:移动互联网创业需警惕五大“不靠谱

热门文章

  1. 深入理解JavaScript系列:闭包(Closures)
  2. UITableView 系列一 :基本使用方法 (显示,删除,添加图片,添加样式等) (实例)...
  3. 认识Javascript数组
  4. WordPress 三个必须的安全措施
  5. jQuery 页面载入进度条 (必有一款适合你----综合搜集版)
  6. 关于安装TortoiseSVN.msi 报错2203错误
  7. ubuntu编辑器terminator与vim
  8. LeetCode 31. Next Permutation
  9. 【C++基础学习】const限定修饰符详解
  10. sqlserver无法连接到服务器的常见原因和解决办法