一、Spring事务简介

1.1 相关概念介绍

  • 事务作用:在数据层保障一系列的数据库操作同成功同失败

  • Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败

数据层有事务我们可以理解,为什么业务层也需要处理事务呢?

举个简单的例子,

  • 转账业务会有两次数据层的调用,一次是加钱一次是减钱

  • 把事务放在数据层,加钱和减钱就有两个事务

  • 没办法保证加钱和减钱同时成功或者同时失败

  • 这个时候就需要将事务放在业务层进行处理。

Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManager

commit是用来提交事务,rollback是用来回滚事务。

PlatformTransactionManager只是一个接口,Spring还为其提供了一个具体的实现:

从名称上可以看出,我们只需要给它一个DataSource对象,它就可以帮你去在业务层管理事务。其内部采用的是JDBC的事务。所以说如果你持久层采用的是JDBC相关的技术,就可以采用这个事务管理器来管理你的事务。而Mybatis内部采用的就是JDBC的事务。

1.2 转账案例-需求分析

接下来通过一个案例来分析下Spring是如何来管理事务的。

先来分析下需求:

需求: 实现任意两个账户间转账操作

需求微缩: A账户减钱,B账户加钱

为了实现上述的业务需求,我们可以按照下面步骤来实现下:

①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)

②:业务层提供转账操作(transfer),调用减钱与加钱的操作

③:提供2个账号和操作金额执行转账操作

1.3 转账案例-环境搭建

步骤1:准备数据库表
  1. create database spring_db character set utf8;
  2. use spring_db;
  3. create table tbl_account(
  4.   id int primary key auto_increment,
  5.   name varchar(35),
  6.   money double
  7. );
  8. insert into tbl_account values(1,'Tom',1000);
  9. insert into tbl_account values(2,'Jerry',1000);

步骤2:创建项目导入jar包

项目的pom.xml添加相关依赖

  1. <dependencies>
  2.    <dependency>
  3.      <groupId>org.springframework</groupId>
  4.      <artifactId>spring-context</artifactId>
  5.      <version>5.2.10.RELEASE</version>
  6.    </dependency>
  7.    <dependency>
  8.      <groupId>com.alibaba</groupId>
  9.      <artifactId>druid</artifactId>
  10.      <version>1.1.16</version>
  11.    </dependency>

  12.    <dependency>
  13.      <groupId>org.mybatis</groupId>
  14.      <artifactId>mybatis</artifactId>
  15.      <version>3.5.6</version>
  16.    </dependency>

  17.    <dependency>
  18.      <groupId>mysql</groupId>
  19.      <artifactId>mysql-connector-java</artifactId>
  20.      <version>5.1.47</version>
  21.    </dependency>

  22.    <dependency>
  23.      <groupId>org.springframework</groupId>
  24.      <artifactId>spring-jdbc</artifactId>
  25.      <version>5.2.10.RELEASE</version>
  26.    </dependency>

  27.    <dependency>
  28.      <groupId>org.mybatis</groupId>
  29.      <artifactId>mybatis-spring</artifactId>
  30.      <version>1.3.0</version>
  31.    </dependency>

  32.    <dependency>
  33.      <groupId>junit</groupId>
  34.      <artifactId>junit</artifactId>
  35.      <version>4.12</version>
  36.      <scope>test</scope>
  37.    </dependency>

  38.    <dependency>
  39.      <groupId>org.springframework</groupId>
  40.      <artifactId>spring-test</artifactId>
  41.      <version>5.2.10.RELEASE</version>
  42.    </dependency>

  43.  </dependencies>
步骤3:根据表创建模型类
  1. public class Account implements Serializable {

  2.    private Integer id;
  3.    private String name;
  4.    private Double money;
  5. //setter...getter...toString...方法略    
  6. }
步骤4:创建Dao接口
  1. public interface AccountDao {

  2.    @Update("update tbl_account set money = money + #{money} where name = #{name}")
  3.    void inMoney(@Param("name") String name, @Param("money") Double money);

  4.    @Update("update tbl_account set money = money - #{money} where name = #{name}")
  5.    void outMoney(@Param("name") String name, @Param("money") Double money);
  6. }
步骤5:创建Service接口和实现类
  1. public interface AccountService {
  2.    /**
  3.     * 转账操作
  4.     * @param out 传出方
  5.     * @param in 转入方
  6.     * @param money 金额
  7.     */
  8.    public void transfer(String out,String in ,Double money) ;
  9. }

  10. @Service
  11. public class AccountServiceImpl implements AccountService {

  12.    @Autowired
  13.    private AccountDao accountDao;

  14.    public void transfer(String out,String in ,Double money) {
  15.        accountDao.outMoney(out,money);
  16.        accountDao.inMoney(in,money);
  17.   }

  18. }
步骤6:添加jdbc.properties文件
  1. jdbc.driver=com.mysql.jdbc.Driver
  2. jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
  3. jdbc.username=root
  4. jdbc.password=root
步骤7:创建JdbcConfig配置类
  1. public class JdbcConfig {
  2.    @Value("${jdbc.driver}")
  3.    private String driver;
  4.    @Value("${jdbc.url}")
  5.    private String url;
  6.    @Value("${jdbc.username}")
  7.    private String userName;
  8.    @Value("${jdbc.password}")
  9.    private String password;

  10.    @Bean
  11.    public DataSource dataSource(){
  12.        DruidDataSource ds = new DruidDataSource();
  13.        ds.setDriverClassName(driver);
  14.        ds.setUrl(url);
  15.        ds.setUsername(userName);
  16.        ds.setPassword(password);
  17.        return ds;
  18.   }
  19. }
步骤8:创建MybatisConfig配置类
  1. public class MybatisConfig {

  2.    @Bean
  3.    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
  4.        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
  5.        ssfb.setTypeAliasesPackage("com.itheima.domain");
  6.        ssfb.setDataSource(dataSource);
  7.        return ssfb;
  8.   }

  9.    @Bean
  10.    public MapperScannerConfigurer mapperScannerConfigurer(){
  11.        MapperScannerConfigurer msc = new MapperScannerConfigurer();
  12.        msc.setBasePackage("com.itheima.dao");
  13.        return msc;
  14.   }
  15. }
步骤9:创建SpringConfig配置类
  1. @Configuration
  2. @ComponentScan("com.itheima")
  3. @PropertySource("classpath:jdbc.properties")
  4. @Import({JdbcConfig.class,MybatisConfig.class})
  5. public class SpringConfig {
  6. }
步骤10:编写测试类
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(classes = SpringConfig.class)
  3. public class AccountServiceTest {

  4.    @Autowired
  5.    private AccountService accountService;

  6.    @Test
  7.    public void testTransfer() throws IOException {
  8.        accountService.transfer("Tom","Jerry",100D);
  9.   }

  10. }

最终创建好的项目结构如下:

1.4 事务管理

上述环境,运行单元测试类,会执行转账操作,Tom的账户会减少100,Jerry的账户会加100。

这是正常情况下的运行结果,但是如果在转账的过程中出现了异常,如:

  1. @Service
  2. public class AccountServiceImpl implements AccountService {

  3.    @Autowired
  4.    private AccountDao accountDao;

  5.    public void transfer(String out,String in ,Double money) {
  6.        accountDao.outMoney(out,money);
  7.        int i = 1/0;
  8.        accountDao.inMoney(in,money);
  9.   }

  10. }

这个时候就模拟了转账过程中出现异常的情况,正确的操作应该是转账出问题了,Tom应该还是900,Jerry应该还是1100,但是真正运行后会发现,并没有像我们想象的那样,Tom账户为800而Jerry还是1100,100块钱凭空消息了,银行乐疯了。如果把转账换个顺序,银行就该哭了。

不管哪种情况,都是不允许出现的,对刚才的结果我们做一个分析:

①:程序正常执行时,账户金额A减B加,没有问题

②:程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败

当程序出问题后,我们需要让事务进行回滚,而且这个事务应该是加在业务层上,而Spring的事务管理就是用来解决这类问题的。

Spring事务管理具体的实现步骤为:

步骤1:在需要被事务管理的方法上添加注解
  1. public interface AccountService {
  2.    /**
  3.     * 转账操作
  4.     * @param out 传出方
  5.     * @param in 转入方
  6.     * @param money 金额
  7.     */
  8.    //配置当前接口方法具有事务
  9.    public void transfer(String out,String in ,Double money) ;
  10. }

  11. @Service
  12. public class AccountServiceImpl implements AccountService {

  13.    @Autowired
  14.    private AccountDao accountDao;
  15. @Transactional
  16.    public void transfer(String out,String in ,Double money) {
  17.        accountDao.outMoney(out,money);
  18.        int i = 1/0;
  19.        accountDao.inMoney(in,money);
  20.   }

  21. }

注意:

@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务

  • 写在接口方法上,该接口的所有实现类的该方法都会有事务

  • 写在实现类上,该类中的所有方法都会有事务

  • 写在实现类方法上,该方法上有事务

  • 建议写在接口类或接口类的方法上

步骤2:在JdbcConfig类中配置事务管理器
  1. public class JdbcConfig {
  2.    @Value("${jdbc.driver}")
  3.    private String driver;
  4.    @Value("${jdbc.url}")
  5.    private String url;
  6.    @Value("${jdbc.username}")
  7.    private String userName;
  8.    @Value("${jdbc.password}")
  9.    private String password;

  10.    @Bean
  11.    public DataSource dataSource(){
  12.        DruidDataSource ds = new DruidDataSource();
  13.        ds.setDriverClassName(driver);
  14.        ds.setUrl(url);
  15.        ds.setUsername(userName);
  16.        ds.setPassword(password);
  17.        return ds;
  18.   }

  19.    //配置事务管理器,mybatis使用的是jdbc事务
  20.    @Bean
  21.    public PlatformTransactionManager transactionManager(DataSource dataSource){
  22.        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
  23.        transactionManager.setDataSource(dataSource);
  24.        return transactionManager;
  25.   }
  26. }

注意:事务管理器要根据使用技术进行选择,Mybatis框架使用的是JDBC事务,可以直接使用DataSourceTransactionManager

步骤3:开启事务注解

在SpringConfig的配置类中开启

  1. @Configuration
  2. @ComponentScan("com.itheima")
  3. @PropertySource("classpath:jdbc.properties")
  4. @Import({JdbcConfig.class,MybatisConfig.class
  5. //开启注解式事务驱动
  6. @EnableTransactionManagement
  7. public class SpringConfig {
  8. }
步骤4:运行测试类

会发现在转换的业务出现错误后,事务就可以控制回顾,保证数据的正确性。

知识点1:@EnableTransactionManagement
名称 @EnableTransactionManagement
类型 配置类注解
位置 配置类定义上方
作用 设置当前Spring环境中开启注解式事务支持
知识点2:@Transactional
名称 @Transactional
类型 接口注解 类注解 方法注解
位置 业务层接口上方 业务层实现类上方 业务方法上方
作用 为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)

二、Spring事务角色

Spring不是一个数据库技术,那它是怎么控制事务的呢?

主要理解两个概念,分别是事务管理员事务协调员

  1. 未开启Spring事务之前:

  • AccountDao的outMoney因为是修改操作,会开启一个事务T1

  • AccountDao的inMoney因为是修改操作,会开启一个事务T2

  • AccountService的transfer没有事务,

    • 运行过程中如果没有抛出异常,则T1和T2都正常提交,数据正确

    • 如果在两个方法中间抛出异常,T1因为执行成功提交事务,T2因为抛异常不会被执行

    • 就会导致数据出现错误

  1. 开启Spring的事务管理后

  • transfer上添加了@Transactional注解,在该方法上就会有一个事务T

  • AccountDao的outMoney方法的事务T1加入到transfer的事务T中

  • AccountDao的inMoney方法的事务T2加入到transfer的事务T中

  • 这样就保证他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,保证数据的准确性。

通过上面例子的分析,我们就可以得到如下概念:

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法

  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

注意:

目前的事务管理是基于DataSourceTransactionManagerSqlSessionFactoryBean,他们使用的是同一个数据源。

Java开发学习(二十一)----Spring事务简介与事务角色解析的更多相关文章

  1. Java开发学习(二十三)----SpringMVC入门案例、工作流程解析及设置bean加载控制

    一.SpringMVC概述 SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装.SpringMVC是处于Web层的框架,所以其主要的作用就是用来 ...

  2. Java开发学习(二十二)----Spring事务属性、事务传播行为

    一.事务配置 上面这些属性都可以在@Transactional注解的参数上进行设置. readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true. timeout ...

  3. Java开发学习(二十七)----SpringMVC之Rest风格解析及快速开发

    一.REST简介 REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格 当我们想表示一个网络资源的时候,可以使用两种方式: 传统风格资源描 ...

  4. Java开发学习(二十四)----SpringMVC设置请求映射路径

    一.环境准备 创建一个Web的Maven项目 参考Java开发学习(二十三)----SpringMVC入门案例.工作流程解析及设置bean加载控制中环境准备 pom.xml添加Spring依赖 < ...

  5. Java开发学习(二十五)----使用PostMan完成不同类型参数传递

    一.请求参数 请求路径设置好后,只要确保页面发送请求地址和后台Controller类中配置的路径一致,就可以接收到前端的请求,接收到请求后,如何接收页面传递的参数? 关于请求参数的传递与接收是和请求方 ...

  6. Java开发学习(二十六)----SpringMVC返回响应结果

    SpringMVC接收到请求和数据后,进行了一些处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户. 比如:根据用户ID查 ...

  7. Java开发学习(二十八)----拦截器(Interceptor)详细解析

    一.拦截器概念 讲解拦截器的概念之前,我们先看一张图: (1)浏览器发送一个请求会先到Tomcat的web服务器 (2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源 (3)如 ...

  8. Java开发学习(四十一)----MyBatisPlus标准数据层(增删查改分页)开发

    一.标准CRUD使用 对于标准的CRUD功能都有哪些以及MyBatisPlus都提供了哪些方法可以使用呢? 我们先来看张图: 1.1 环境准备 这里用的环境就是Java开发学习(四十)----MyBa ...

  9. Java开发学习(一)----初识Spring及其核心概念

    一. Spring系统架构 1.1 系统架构图 Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基. Spring Framework的发展也经历了很多版本的变更,每 ...

随机推荐

  1. 2020级C++实验课-期末机考模拟考题解

    做这个题解的理由很简单,有很多同学想写但是不会写,凑巧我写了,所以搞个题解. 顺序就是题单里的顺序(界面左上角菜单切换文章,右上角目录方便查看) 1:黑马白马 题意: 随机得到一个数字,如果是偶数,则 ...

  2. python之数据类型的内置方法(set、tuple、dict)与简单认识垃圾回收机制

    目录 字典的内置方法 类型转换 字典取值 修改值 计算字典长度 成员运算 删除元素 获取元素 更新字典 快速生成字典 setdefault()方法 元组的内置方法 类型转换 索引与切片操作 统计长度 ...

  3. flask实现python方法转换服务

    一.flask安装 pip install flask 二.flask简介: flask是一个web框架,可以通过提供的装饰器@server.route()将普通函数转换为服务 flask是一个web ...

  4. centos6搭建mysql

    目前CentOS6.5及一下版本基本上被官方给放弃更新了,但是考虑到忠实粉丝迟迟不肯放手,所以还留了入口但是非常有限 1.搭建mysql 可参照:https://blog.csdn.net/huang ...

  5. Training loop Run Builder和namedtuple()函数

    namedtuple()函数见:https://www.runoob.com/note/25726和https://www.cnblogs.com/os-python/p/6809467.html n ...

  6. python常用标准库(压缩包模块zipfile和tarfile)

    常用的标准库 在我们常用的系统windows和Linux系统中有很多支持的压缩包格式,包括但不限于以下种类:rar.zip.tar,以下的标准库的作用就是用于压缩解压缩其中一些格式的压缩包. zip格 ...

  7. 新上线!3D单模型轻量化硬核升级,G级数据轻松拿捏!

    "3D模型体量过大.面数过多.传输展示困难",用户面对这样的3D数据,一定不由得皱起眉头.更便捷.快速处理三维数据,是每个3D用户对高效工作的向往. 在老子云最新上线的单模型轻量化 ...

  8. 一篇文章带你深入浅出Vuex

    在写Vuex之前,我们先用一个简单的例子来实现一个小demo 大家都知道Vue的父传子用在很多场景,比如像这样: 父组件: <template> <div id="app& ...

  9. 搭建zabbix及报错处理

    搭建ZABBIX服务器准备工作 1.需要服务器是LAMP 或 LNMP 环境 2.主机名和IP要写在HOST文件里 3.iptables 和 selinux 必须关闭 一.先用最简单的方式搭建lamp ...

  10. 数学公式 Latex 练习

    \[1+x+x^2+x^3+\cdots=\frac{1}{1-x}\quad x\in(-1, 1) \] 证明:设左边式子项数为 \(n\) 那么可以得到: \[\begin{split} S & ...