一、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:准备数据库表
create database spring_db character set utf8;
use spring_db;
create table tbl_account(
  id int primary key auto_increment,
  name varchar(35),
  money double
);
insert into tbl_account values(1,'Tom',1000);
insert into tbl_account values(2,'Jerry',1000);

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

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

<dependencies>
   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>5.2.10.RELEASE</version>
   </dependency>
   <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid</artifactId>
     <version>1.1.16</version>
   </dependency>

   <dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis</artifactId>
     <version>3.5.6</version>
   </dependency>

   <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>5.1.47</version>
   </dependency>

   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-jdbc</artifactId>
     <version>5.2.10.RELEASE</version>
   </dependency>

   <dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis-spring</artifactId>
     <version>1.3.0</version>
   </dependency>

   <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.12</version>
     <scope>test</scope>
   </dependency>

   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-test</artifactId>
     <version>5.2.10.RELEASE</version>
   </dependency>

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

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

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

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

@Service
public class AccountServiceImpl implements AccountService {

   @Autowired
   private AccountDao accountDao;

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

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

   @Bean
   public DataSource dataSource(){
       DruidDataSource ds = new DruidDataSource();
       ds.setDriverClassName(driver);
       ds.setUrl(url);
       ds.setUsername(userName);
       ds.setPassword(password);
       return ds;
  }
}
步骤8:创建MybatisConfig配置类
public class MybatisConfig {

   @Bean
   public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
       SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
       ssfb.setTypeAliasesPackage("com.itheima.domain");
       ssfb.setDataSource(dataSource);
       return ssfb;
  }

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

   @Autowired
   private AccountService accountService;

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

}

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

1.4 事务管理

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

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

@Service
public class AccountServiceImpl implements AccountService {

   @Autowired
   private AccountDao accountDao;

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

}

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

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

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

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

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

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

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

@Service
public class AccountServiceImpl implements AccountService {

   @Autowired
   private AccountDao accountDao;
@Transactional
   public void transfer(String out,String in ,Double money) {
       accountDao.outMoney(out,money);
       int i = 1/0;
       accountDao.inMoney(in,money);
  }

}

注意:

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

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

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

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

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

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

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

   @Bean
   public DataSource dataSource(){
       DruidDataSource ds = new DruidDataSource();
       ds.setDriverClassName(driver);
       ds.setUrl(url);
       ds.setUsername(userName);
       ds.setPassword(password);
       return ds;
  }

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

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

步骤3:开启事务注解

在SpringConfig的配置类中开启

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
步骤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. 【java并发编程】Lock & Condition 协调同步生产消费

    一.协调生产/消费的需求 本文内容主要想向大家介绍一下Lock结合Condition的使用方法,为了更好的理解Lock锁与Condition锁信号,我们来手写一个ArrayBlockingQueue. ...

  2. Spring Boot 配置 HikariCP

    HikariCP 是一个可靠的.高性能的 JDBC 连接池 本来用的 alibaba/druid,但实际并没有怎么用其内置的监控网页,然后多方调查,决定弃用 druid,替换为 HikariCP Sp ...

  3. Nginx报错收集

    在安装完成ngixn之后,访问页面显示空白,报错信息里面有这一条报错信息: tailf /usr/local/nginx/logs/error.log 2018/10/26 10:58:00 [err ...

  4. 群晖下 gitea+drone+harbor实现CI/CD 发布到云服务器

    常用命令 sudo -i然后输入密码登录root账户(群晖默认只能使用admin账号登陆) vim xxx编辑(编辑是进去之后按i,退出并保存是按esc,然后:wq!再回车) mkdir xx创建文件 ...

  5. ASCII&Base64

    ASCII https://zh.wikipedia.org/wiki/ASCII American Standard Code for Information Interchange,美国信息交换标 ...

  6. Centos7 安装 MPICH

    查看官网版本 https://www.mpich.org/downloads/ 最新的stable release是mpich 4.0.2,复制下载链接. 安装依赖 mpich需要系列依赖,如果不确定 ...

  7. socket套接字补充、操作系统发展史、进程

    目录 socket套接字之UDP协议 操作系统的发展史 手工操作 批处理系统 联机批处理系统 脱机批处理系统 多道技术 进程理论 并发与并行 同步与异步 阻塞与非阻塞 同步异步与阻塞非阻塞总结 soc ...

  8. Meaven静态资源过滤

    ` 点击查看代码 <build> <resources> <resource> <directory>src/main/java</directo ...

  9. 用STM32玩L298N(正反转、调速)

    目录 用STM32玩L298N(正反转.调速) 控制直流电机正反转 使用PWM调速 用STM32玩L298N(正反转.调速) 开发板:STM32F103ZET6(正点原子F103核心板)/STM32F ...

  10. 【Azure 应用服务】NodeJS Express + MSAL 实现API应用Token认证(AAD OAuth2 idToken)的认证实验 -- passport.authenticate('oauth-bearer', {session: false})

    问题描述 在前两篇博文中,对NodeJS Express应用 使用MSAL + AAD实现用户登录并获取用户信息,获取Authorization信息 ( ID Token, Access Token) ...