从源码分析 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.mzph.cn/news/229329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue中插槽的使用

目录 一、默认插槽 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;代码展示 &#xff08;3&#xff09;后备内容 二、具名插槽 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;代码展示 三、作用域插槽 &#xff08;1&#xff09;概念 &#xff0…

Spring-整合MyBatis

依赖 <dependencies><!--提供数据源--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.9.RELEASE</version></dependency><!--提供sqlSessionFactory…

虚拟化之成本

虚拟化的成本取决于在 hypervisor 需要为虚拟机&#xff08;VM&#xff09;提供服务时&#xff0c;在虚拟机和 hypervisor 之间切换所需的时间。在 Arm 系统上&#xff0c;这样的成本的下限包括&#xff1a; 31 个 64 位通用寄存器&#xff08;X0 到 X30&#xff09;32 个 128…

一个 tomcat 下如何部署多个项目?附详细步骤

一个tomcat下如何部署多个项目&#xff1f;Linux跟windows系统下的步骤都差不多&#xff0c;以下linux系统下部署为例。windows系统下部署同理。 1 不修改端口&#xff0c;部署多个项目 清楚tomcat目录结构的应该都知道&#xff0c;项目包是放在webapps目录下的&#xff0c;那…

vue2 echarts不同角色多个类型数据的柱状图

前端代码&#xff1a; 先按照echarts插件。在页面里引用 import * as echarts from "echarts";设置div <div style"width:100%;height:250px;margin-top: 4px;" id"addressChart"></div>方法: addressEcharts() {const option {g…

RabbitMQ搭建集群环境、配置镜像集群、负载均衡

RabbitMQ集群搭建 Linux安装RabbitMQ下载安装基本操作命令开启管理界面及配置 RabbitMQ集群搭建确定rabbitmq安装目录启动第一个节点启动第二个节点停止命令创建集群查看集群集群管理 RabbitMQ镜像集群配置启用HA策略创建一个镜像队列测试镜像队列 负载均衡-HAProxy安装HAProxy…

【精选】计算机网络教程(第3章数据链路层)

目录 前言 第3章数据链路层 1、差错检测&#xff08;CRC&#xff09; 2、点对点协议&#xff08;了解应用场景&#xff09; 3、什么是碰撞域&#xff0c;什么是广播域 碰撞域&#xff08;Collision Domain&#xff09;&#xff1a; 广播域&#xff08;Broadcast Domain&a…

利用二叉树遍历的思想编写一个判断二叉树是否是平衡二叉树的算法

题目描述&#xff1a;利用二叉树遍历的思想编写一个判断二叉树是否是平衡二叉树的算法。 分析&#xff1a; 设置二叉树的平衡标记为balance&#xff0c;若 balance 为 1&#xff0c;则是平衡二叉树&#xff0c;若为0&#xff0c;则不是。 void Judge_AVL(BiTree T,int &b…

计算机组成原理学习(输入输出系统)

目录 输入输出系统&#xff08;i/o系统&#xff09; 一.现代计算机的结构 二.常见的&#xff08;I/O设备或者是外部设备&#xff09; ​ 三.主机如何与I/O设备进行交互 四.I/O控制方式简介 五.I/O系统的基本组成 输入输出系统&#xff08;i/o系统&#xff09; 一.现代计算…

PTA——L1-026 I Love GPLT

这道超级简单的题目没有任何输入。 你只需要把这句很重要的话 —— “I Love GPLT”——竖着输出就可以了。 所谓“竖着输出”&#xff0c;是指每个字符占一行&#xff08;包括空格&#xff09;&#xff0c;即每行只能有1个字符和回车。 输入样例&#xff1a; 无输出样例&#…

Java stream流实现List<对象>通过对象中的多个字段去重

遇到同时通过多个字段对list进行去重需求&#xff0c;记录一下 先创建一个实体类 Data public class User {private Long id;private String name;private String code;private String phone;private Integer age;public User(Long id, String name, String code, String phon…

android studio简易app实例

以下是一个使用Android Studio创建的简易App实例。这个App包括一个主页面&#xff0c;一个关于页面和一个设置页面。 步骤1&#xff1a;创建新的Android项目 打开Android Studio&#xff0c;点击"File" -> "New" -> "New Project"。在新…

一键安装下载3ds Max!别墅还是宫殿?3ds Max助你建造梦幻般的艺术建筑

不再浪费时间在网上寻找3ds Max的安装包了&#xff01;因为你所需要的一切都可以在这里找到&#xff01;作为一款全球领先的3D设计工具&#xff0c;3ds Max为创作者们带来了前所未有的便利和创作灵感。无论是建筑设计、影视特效还是游戏开发&#xff0c;3ds Max都能帮助你实现想…

C++——C++11(1)

时至今日&#xff0c;C标准已经到了C23&#xff0c;但是你要说哪一次提出的标准最经 典&#xff0c;那C11一定会被人提及&#xff0c;C11带来了数量可观的变化&#xff0c;其中包 含了约140个新特性&#xff0c;以及对C03标准中约600个缺陷的修正&#xff0c;这使得 C11更像是从…

一个神奇的Python库:Evidently,机器学习必备

Evidently 是一个面向数据科学家和机器学习工程师的开源 Python 库。它有助于评估、测试和监控从验证到生产的数据和 ML 模型。它适用于表格、文本数据和嵌入。 简介 Evidently 是一个开源的 Python 工具&#xff0c;旨在帮助构建对机器学习模型的监控&#xff0c;以确保它们的…

博客动态校验+静态校验二次开发方式

静态校验&#xff1a; 1&#xff1a;将需要静态校验的参数继承BaseReqeust类重写validate方法&#xff1a; 动态校验&#xff1a; 1&#xff1a;在需要校验的实现类上加&#xff1a; BizValidate注解 2&#xff1a;写一个校验类&#xff0c;方法命名规范为&#xff1a;需要校…

修改npm源码解决服务端渲染环境中localstorage报错read properties of undefined (reading getItem)

现象&#xff1a; 这个问题是直接指向了我使用的第三方库good-storage&#xff0c;这是一个对localStorage/sessionStorage做了简单封装的库&#xff0c;因为项目代码有一个缓存cache.ts有用到 原因分析&#xff1a; 从表象上看是storage对象找不到getItem方法&#xff0c; 但…

快速定位网页中元素在源代码中的位置

快速定位网页中元素在源代码中的位置参考本文

Android--Jetpack--数据库Room详解一

人生何须万种愁&#xff0c;千里云烟一笑收 一&#xff0c;定义 Room也是一个ORM框架&#xff0c;它在SQLite上提供了一个抽象层&#xff0c;屏蔽了部分底层的细节&#xff0c;使用对象对数据库进行操作&#xff0c;进行CRUD就像对象调用方法一样的简单。 二&#xff0c;角色介…

基于YOLOv8深度学习的路面标志线检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…