事务是⼀组操作的集合,是⼀个不可分割的操作.
事务这个词,在数据库学习后都会有了解,如它的概念:事务会把所有的操作作为一个整体,向数据库发起请求,要么都成功,要么一起失败。
事务的基本操作: 1. 开启事务 :start transaction/begin(一组操作前开启事务) 2. 提交事务:commit (操作全部成功,提交事务) 3. 回滚事务:rollback(一组操作中任何一个操作出现问题,整组回滚)
Spring 中的事务分为两类:
数据库:
-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
-- ⽤⼾表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
`id` INT NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR (128) NOT NULL,
`password` VARCHAR (128) NOT NULL,
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '⽤⼾表';
--操作日志表
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (
`id` INT PRIMARY KEY auto_increment,
`user_name` VARCHAR ( 128 ) NOT NULL,
`op` VARCHAR ( 256 ) NOT NULL,
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now()
) DEFAULT charset 'utf8mb4';同时引入Spring Web, Mybatis , mysql 等多种依赖 设置配置文件
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/trans_testcharacterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #配置驼峰⾃动转换实体类:
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Date createTime;
private Date updateTime;
}@Data
public class LogInfo {
private Integer id;
private String userName;
private String op;
private Date createTime;
private Date updateTime;
}Controller:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name,String password){
//用户注册(数据操作)
userService.registryUser(name,password);
log.info("用户注册成功");
return "注册成功";
}
}Service:
@Service
@Slf4j
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public Integer registryUser(String name,String password){
return userInfoMapper.insert(name,password);
}
}@Service
@Slf4j
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
public Integer insertLog(String name,String password){
return logInfoMapper.insertLog(name,"用户注册");
}
}Mapper:
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(`user_name`,`password`) value(#{name},#{password})")
Integer insert(String name,String password);
}@Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
Integer insertLog(String name,String op);
}Spring手动操作事务与MySQL类似,三个关键步骤:开启事务、提交事务、回滚事务。
Spring Boot内置的两个对象:
DataSourceTransactionManager 事务管理器:用来获取事务(开启事务),提交或者回滚事务。TransactionDefinition 是事务的属性,获取事务时将属性传进去从而获得一个事务的TransactionStatus@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;//事务管理器
@Autowired
private TransactionDefinition definition;//事务之前状态
@RequestMapping("/registry")
public String registry(String name,String password){
/**
* 1. 开启事务
* 2. 数据操作
* 3. 事务提交、回滚
*/
//开启事务
TransactionStatus transaction= dataSourceTransactionManager.getTransaction(definition);
//用户注册(数据操作)
userService.registryUser(name,password);
log.info("用户注册成功");
//事务提交
//dataSourceTransactionManager.commit(transaction);
//事务回滚
dataSourceTransactionManager.rollback(transaction);
return "注册成功";
}
}postman测试:提交事务

在需要事务的方法上添加@Transactional注解就可以实现,无需手动开启事务,方法执行完会自动提交事务,中途有异常自动回滚事务。
@Slf4j
@RequestMapping("/user2")
@RestController
public class UserController2 {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
userService.registryUser(name,password);
return "注册成功";
}
}运行此程序,发现数据插入成功,但是我们给代码添加异常,会发生什么呢?
@Slf4j
@RequestMapping("/user2")
@RestController
public class UserController2 {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
userService.registryUser(name,password);
int a=10/0;
return "注册成功";
}
}
虽然用户注册成功,但是去观察数据库发现没有新增数据,说明事务进行了回滚。
@Transcational可以修饰方法或类:
类\方法被@Transcational修饰后,在目标方法执行前就自动开启事务,方法执行完毕自动提交事务。注意:如果方法执行中有异常且未捕获,就进行事务回滚;否则继续提交事务。
针对异常被捕获还需要对事务进行回滚,有两种方法解决:
try {
//强制程序抛出异
int a = 10/0;
}catch (Exception e){
//将异常重新抛出去
throw e;
}try {
//强制程序抛出异
int a = 10/0;
}catch (Exception e){
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}@Transcational注解中常见有三个属性:rollbackFor、Isolation、propagation
rollbackFor:异常回滚属性。指定能够触发事务回滚类型的异常类型
@Transactional 默认只在遇到运⾏时异常和Error时才会回滚,⾮运⾏时异常不回滚. 如果需要所有异常都回滚,那配置@Transcational属性中rollbackFor,指定什么异常类型时进行回滚
@Transactional(rollbackFor = Exception.class)注意: 默认情况下,只有运行时异常RuntimeExpection和Error时才回滚。但是其他类型异常可以通过rollbackFor属性进行指定。
Isolation:事务隔离级别,默认是Isolation.DEFAULT
其实MySQL种有4种事务隔离级别:
而Spring中事务隔离级别有5种:
脏读:因为其他事务未提交的数据可能会发⽣回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据。
不可重复读:由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间的相同SQL查询可能会得到不同的结果
幻读:可重复读级别的事务正在执行,另一个事务成功插入某条数据,但是因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). 明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
读未提交 | √ | √ | √ |
读已提交 | × | √ | √ |
可重复读 | × | × | √ |
串行化 | × | × | × |