🍎一.Spring事务定义
🍒1.1 什么是事务
事务:将⼀组操作封装成⼀个执行单元(封装到⼀起),要么全部成功,要么全部失败
🍒1.2 事务的作用
⽐如转账分为两个操作:
第⼀步操作:A 账户 -100 元
第⼆步操作:B 账户 +100 元
如果没有事务,第⼀步执⾏成功了,第⼆步执⾏失败了,那么 A 账户平⽩⽆故的 100 元就“⼈间蒸发”了。⽽如果使⽤事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败
🍎二.Spring 中事务的实现
Spring 中事务的实现有两种:
⭕ 编程式事务(手动写代码操作事务)
⭕ 声明式事务(利用注解自动开启和提交事务)
🍒2.1 Spring事务实现之前的准备
🍉2.1.1 创建项目时框架的选择
🍉2.1.2 application.yml配置文件
# 配置数据库连接 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: jj1432644716 driver-class-name: com.mysql.cj.jdbc.Driver # 设置 Mybatis 的 xml 保存路径 mybatis: mapper-locations: classpath:mapper/**Mapper.xml configuration: # 配置打印 MyBatis 执行的 SQL log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置日志打印 MyBatis 执行的 SQL logging: level: com: example: demo: debug
🍉2.1.3 MyBatis的配置
在配置文件目录下创建一个mapper包,在创建一个关于Mybatis.xml文件
com.example.demo.mapper.UserMapper这取决于你配置文件目录下的包和文件名
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.UserMapper"> </mapper>
🍉2.1.4 创建对象和实现类
创建数据库内对象:
package com.example.demo.model; import lombok.Data; @Data public class UserInfo { private int id; private String username; private String password; private String photo; private String createtime; private String updatetime; private int state; }
创建 Mapper接口:
package com.example.demo.mapper; import com.example.demo.model.UserInfo; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper { public int add(UserInfo userInfo); }
创建Service:
Service public class UserService { @Resource private UserMapper userMapper; @Transactional(propagation = Propagation.REQUIRED) public int add(UserInfo userInfo){ return userMapper.add(userInfo); } }
创建Conctroller:
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; // 在此方法使用编程式事务 @RequestMapping("/add") public int add(UserInfo userInfo){ // 要验证用户名和密码的{不为空} if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) return 0; int result = userService.add(userInfo); return result; }
🍒2.2 编程式事务(了解)
Spring 中的编程式事务的实现:
- 此方式包含了3个重要的操作:
● 获取事务(开启事务)
● 提交事务
● 回滚事务
- 依赖两个重要的对象:
● DataSourceTransactionManager
● TransactionDefinition
以下代码实现:
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Autowired private DataSourceTransactionManager transactionManager; @Autowired private TransactionDefinition transactionDefinition; // 在此方法使用编程式事务 @RequestMapping("/add") public int add(UserInfo userInfo){ // 要验证用户名和密码的{不为空} if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) return 0; // 开启事务 TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); int result = userService.add(userInfo); System.out.println("add 受影响的行数:" + result); // 提交事务 / 回滚事务 // transactionManager.rollback(transactionStatus); // 回滚业务 transactionManager.commit(transactionStatus); // 提交事务 return result; }
🍒2.3 声明式事务(@Transactional )
声明式事务的实现很简单,只需要在需要的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务,具体实现代码如下:
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; // @Transactional注解只能在 public方法上生效 @Transactional() //在方法之前,自动开启事务,方法执行完之后自动提交事务,如果出现异常,则自动进行事务回滚 @RequestMapping("/add2") // 要验证用户名和密码的{不为空} public int add2(UserInfo userInfo){ if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) return 0; int result = userService.add(userInfo); return result; }
🍉2.3.1 @Transactional 的作用域
@Transactional 可以⽤来修饰⽅法或类:
● 修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法
● 修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效
🍉2.3.2 @Transactional 注解内的参数
参数 | 作用 |
value | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器 |
transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器 |
propagation | 事务的传播行为,默认值为Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为Isolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务. |
readOnly | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为true. |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
noRollbackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
noRollbackForClassName | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
🍉2.3.3 @Transactional 在异常被捕获的情况下
@Transactional 在异常被捕获的情况下,不会进⾏事务⾃动回滚,验证以下代码是否会发⽣事务回滚:
// @Transactional注解只能在 public方法上生效 @Transactional(isolation = Isolation.DEFAULT,timeout = 1) //在方法之前,自动开启事务,方法执行完之后自动提交事务,如果出现异常,则自动进行事务回滚 @RequestMapping("/add2") // 要验证用户名和密码的{不为空} public int add2(UserInfo userInfo){ if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) return 0; int result = userService.add(userInfo); System.out.println("add 受影响的行数:" + result); try { // 执⾏了异常代码(0不能做除数) int i = 10 / 0; } catch (Exception e) { System.out.println(e.getMessage()); } return result; }
我们发现确实是进行了异常处理后,到那时数据库还是有新增数据,没有进行回滚
🍈2.3.3.1 解决方案一(异常重新抛出)
// @Transactional注解只能在 public方法上生效 @Transactional(isolation = Isolation.DEFAULT,timeout = 1) //在方法之前,自动开启事务,方法执行完之后自动提交事务,如果出现异常,则自动进行事务回滚 @RequestMapping("/add2") // 要验证用户名和密码的{不为空} public int add2(UserInfo userInfo){ if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) return 0; int result = userService.add(userInfo); System.out.println("add 受影响的行数:" + result); try { // 执⾏了异常代码(0不能做除数) int i = 10 / 0; } catch (Exception e) { System.out.println(e.getMessage()); // 重新抛出异常 throw e; } return result; }
🍈2.3.3.2 解决方案二(⼿动回滚事务)
使用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
;方法
// @Transactional注解只能在 public方法上生效 @Transactional(isolation = Isolation.DEFAULT,timeout = 1) //在方法之前,自动开启事务,方法执行完之后自动提交事务,如果出现异常,则自动进行事务回滚 @RequestMapping("/add2") // 要验证用户名和密码的{不为空} public int add2(UserInfo userInfo){ if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) return 0; int result = userService.add(userInfo); System.out.println("add 受影响的行数:" + result); try { // 执⾏了异常代码(0不能做除数) int i = 10 / 0; } catch (Exception e) { System.out.println(e.getMessage()); // ⼿动回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return result; }
我们在数据库中查询看到,虽然没有报错,但是事务回滚了
🍉2.3.4 @Transactional ⼯作原理(默认是 JDK动态代理)
@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理
@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务
@Transactional 实现思路预览: