一.事务回顾
1.什么是事务
2.为什么要使用事务
还是用一个老生常谈的例子来说
张三给李四转账 :
张三账户-100;
李四账户+100;
如果在张三账户-100的时候发生异常,此时李四账户+100的操作还没有完成,张三账户的钱不就平白无故的消失了,而如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。
3.MySQL 中的事务使用
--开启事务start transaction;-- 业务执行-- 提交事务commit;-- 回滚事务rollback;
二.Spring中的事务
0.准备工作
1.先来创建数据库
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog default character set utf8mb4;
-- 使⽤数据数据
use mycnblog;
-- 创建表[⽤户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime datetime default now(),
updatetime datetime default now(),
`state` int default 1
) default charset 'utf8mb4';
-- 添加⼀个⽤户信息
insert into `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,
`createtime`, `updatetime`, `state`) values
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1)
;
-- 添加⼀个用户日志表
DROP TABLE IF EXISTS userlog;
CREATE TABLE userlog(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100) NOT NULL,
createtime DATETIME DEFAULT NOW( ),
updatetime DATETIME DEFAULT NOW()
) DEFAULT CHARSET 'utf8mb4';
2.实体类
@Data
public class User {
private Integer id;
private String username;
private String password;
private String photo;
private Date createtime;
private Date updatetime;
}
@Data
public class UserLog {
private Integer id;
private String username;
private Date cratetime;
private Date updatetime;
public UserLog() {
}
public UserLog(String username) {
this.username = username;
}
}
3.Mapper
@Mapper
public interface UserMapper {
@Select("select * from userinfo")
List<User> selectAll();
@Insert("insert into userinfo(username,password) values (#{username},#{password}))")
Integer insert(User user);
}
@Mapper
public interface UserLogMapper {
@Insert("insert into userlog(username) values (#{username})")
Integer insert(UserLog userLog);
}
4.Service
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public List<User> selectAll() {
return userMapper.selectAll();
}
public Integer addUser(User user) {
return userMapper.insert(user);
}
}
@Service
public class UserLogService {
@Autowired
UserLogMapper userLogMapper;
public Integer addUser(UserLog userLog) {
return userLogMapper.insert(userLog);
}
}
@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
@Autowired
UserService userService;
@RequestMapping("/add")
public Integer addUser() {
User user = new User();
user.setName("zxn123");
user.setPwd("123456");
return userService.addUser(user);
}
}
可以看到是添加成功的
1.手动
加入事务之后进行rollback
@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
@Autowired
private UserService userService;
//数据库事务管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
//事务属性
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/add")
public Integer addUser(String username, String password) {
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
User user = new User();
user.setName(username);
user.setPwd(password);
Integer insert = userService.addUser(user);
log.info("影响了" + insert + "行");
dataSourceTransactionManager.rollback(transaction);
return insert;
}
}
仍然执行上面的操作.数据库没有发生改变,说明数据进行了回滚
加入事务之后进行rollback日志
加入事务之后进行commit日志
2.注解
加入@Transactional注解
@RestController
@Slf4j
@RequestMapping("/trans2")
public class TransactionalController2 {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/add")
public Integer addUser(String username, String password) {
User user = new User();
user.setName(username);
user.setPwd(password);
Integer insert = userService.addUser(user);
log.info("影响了" + insert + "行");
return insert;
}
}
1.没有异常,自动提交
2.有异常,自动rollback
可以看到没有提交操作.
3.@Transactional 作用范围
4.@Transactional 参数说明
参数 | 作用 |
value | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器. |
transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器. |
propagation | 事务的传播行为,默认值Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为lsolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务. |
readOnly | 指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为 true. |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
noRollbackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
noRollbackForClassName | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
演示noRollbackFor
@Transactional(noRollbackFor = ArithmeticException.class)
@RequestMapping("/add")
public Integer addUser(String username, String password) {
User user = new User();
user.setName(username);
user.setPwd(password);
Integer insert = userService.addUser(user);
log.info("影响了" + insert + "行");
int a = 10 / 0;
return insert;
}
此时可以看到是有ArithmeticException错误的,我们添加数据
可以看到是添加成功了的
@Transactional默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚
即Exception的子类中,除了RuntimeException及其子类
因此我们可以自行设置,即可对所有的异常进行回滚
@Transactional(rollbackFor = Exception.class)
5.注解的注意事项
@Transactional 在异常被捕获的情况下,不会进行事务自动回滚
@RequestMapping("/add2")
@Transactional
public Integer add2(String username, String password) {
User user = new User();
user.setName(username);
user.setPwd(password);
// 插⼊数据库
int result = userService.addUser(user);
try {
// 执⾏了异常代码(0不能做除数)
int i = 10 / 0;
} catch (Exception e) {
log.info(e.getMessage());
}
return result;
}
此时可以看到仍然是添加成功的了
三.事务的隔离级别
1.事务的特性
-
原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
-
一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
-
隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。(具体见事务的隔离级别)
-
持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
2.事务的隔离级别
-
脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
-
不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
-
幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(READ UNCOMMITTED) | √ | √ | √ |
读已提交(READ COMMITTED) | x | √ | √ |
可重复读(REPEATABLE READ) | x | x | √ |
串行化(SERIALIZABLE) | x | x | x |
3.Spring事务的隔离级别
- Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
- Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
- Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
- Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
- Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。
@Transactional(isolation = Isolation.READ_COMMITTED)
四.Spring 事务传播机制
1.事务传播机制是什么
2.事务的传播机制
- Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的 方式继续运行。
- Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当 前没有事务,则抛出异常。
- Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
- Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED
以上事务传播机制,可分为三类
3.Spring 事务传播机制使用和各种场景演示
1.支持当前事务(REQUIRED)
没有错误的模拟
@Transactional(propagation = Propagation.REQUIRED)
public Integer addUser(User user) {
return userMapper.insert(user);
}
@Transactional(propagation = Propagation.REQUIRED)
public Integer addUserLog(UserLog userLog) {
return userLogMapper.insert(userLog);
}
@RestController
@Slf4j
@RequestMapping("/trans3")
public class TransactionalController3 {
@Autowired
private UserService userService;
@Autowired
private UserLogService userLogService;
@Transactional
@RequestMapping("/addUser")
public boolean addUser(String username, String password) {
User user = new User();
user.setName(username);
user.setPwd(password);
//插入用户表
userService.addUser(user);
UserLog userLog = new UserLog(username);
//插入日志表
userLogService.addUserLog(userLog);
return true;
}
}
错误发生时的模拟
@Transactional(propagation = Propagation.REQUIRED)
public Integer addUserLog(UserLog userLog) {
int i = 10 / 0;
return userLogMapper.insert(userLog);
}
可以看到数据并没有发生改变
2.不支持当前事务(REQUIRES_NEW)
修改隔离级别
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer addUser(User user) {
return userMapper.insert(user);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer addUserLog(UserLog userLog) {
int i = 10 / 0;
return userLogMapper.insert(userLog);
}
可以看到userinfo表示添加成功的了
3.不支持当前事务,NEVER 抛异常
@Transactional(propagation = Propagation.NEVER)
public Integer addUser(User user) {
return userMapper.insert(user);
}
@Transactional(propagation = Propagation.NEVER)
public Integer addUserLog(UserLog userLog) {
int i = 10 / 0;
return userLogMapper.insert(userLog);
}
执行,会出现以下报错日志
4.NESTED 嵌套事务
@Transactional(propagation = Propagation.NESTED)
public Integer addUser(User user) {
return userMapper.insert(user);
}
@Transactional(propagation = Propagation.NESTED)
public Integer addUserLog(UserLog userLog) {
int i = 10 / 0;
return userLogMapper.insert(userLog);
}
捕获异常
@Transactional(propagation = Propagation.NESTED)
public Integer addUserLog(UserLog userLog) {
userLogMapper.insert(userLog);
try {
int i = 10 / 0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return 1;
}
可以看到userinfo表示添加成功,而userlog表示添加失败的了.
这个只是回滚了部分事务,如果是required的话,会回滚所有的事务.
嵌套事务(NESTED)和加入事务(REQUIRED )的区别:
- 整个事务如果全部执行成功,二者的结果是一样的。
- 如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果。