1.什么是事务
事务就是把所有的操作一起视为一个整体,一起向数据库提交或者撤销操作请求.这组操作要么同时成功,要么同时失败
事务存在的作用:
比如转账:A给B转100块 此时A的账户余额:-100
B收到100块,此时B的账户余额:+100
如果没有事务,A此时转账是成功的,但是B可能是由于网络等因素的影响,他的接收是失败的,此时就会出现A少了100块,B并没有收到.这问题就很严重了.
1.1事务的操作
1.开始事务 start transaction/ begin (⼀组操作前开启事务)
2.提交事务:commit (这组操作全部成功, 提交事务)
3.回滚事务: rollback (这组操作中间任何⼀个操作出现异常, 回滚事务)
2.Spring 事务实现
实现事务有两种方式:
1.编程式事务(通过代码来实现)
2.声明式事务(通过注解来实现)
a.学习事务之前的准备:数据库两张表(user_info,log_info)
具体代码就不展示了,就是一些平常的SQL语句.
b.创建项目并且引入依赖,在application.yml配置文件中连接数据库
c.创建实体类,应用分层.
2.1编程式事务
做完准备工作之后,我们先用编程式来操作事务
package org.ioc.com.spring_trans.controller;
mport com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
// JDBC 事务管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
// 定义事务属性
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name, String password) {
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
//⽤⼾注册
userService.registryUser(name, password);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
//回滚事务
//dataSourceTransactionManager.rollback(transactionStatus);
return "注册成功";
}
}
//提交事务
dataSourceTransactionManager.
commit(transactionStatus);
//回滚事务
//dataSourceTransactionManager.rollback(transactionStatus);
return"注册成功";
我们观察发现编程式实现的代码量虽然不是很多,但是对于我们开发来说,也是浪费一些时间.所以Spring再次出手,利用注解省去了编程式的实现,我们只需要实现注解即可.
2.2 Spring 声明事务
使用@Transactional注解,只需要在想要声明事务的方法上加上这个注解即可.无需手动开启,提交,回滚.不要998,不要888.如果中途发生异常,@Transactional会自动回滚.
url:http://127.0.0.1:8080/user/register?userName=admin&password=admin
1.我们先看下执行程序运行正常的代码:
/*
程序正常执行,自动提交事务
*/
@Transactional
@RequestMapping("/register")
public Integer register(String userName,String password){
Integer result=userInfoService.insert(userName,password);
return result;
}
代码执行完成之后我们观察下运行结果
此时出现commiting就证明事务提交成功了.此时观察数据库也成功插入一条数据了
2.程序存在异常代码自动回滚
url:http://127.0.0.1:8080/user/r1?userName=admin&password=admin
/*
程序存在异常,自动回滚
*/
@Transactional
@RequestMapping("/r1")
public Integer r1(String userName,String password){
Integer result=userInfoService.insert(userName,password);
int a=10/0;
return result;
}
我们看到数据虽然已经提交给数据库,但是事务并没有提交,造成了回滚.
数据库并没有显示数据
3.使用try-catch捕获异常后,事务还是可以正常提交.因为我们在方法中已经对异常进行了处理,@Transactional就感知不到异常了.(类似明星绯闻,公关即使处理,外界知道的就少了)
/*
用try-catch捕获异常后
可以自动提交事务
try-catch捕获后,@Transactional感知不到有异常
*/
@Transactional
@RequestMapping("/r3")
public Integer r3 (String userName,String password){
Integer result=userInfoService.insert(userName,password);
try {
int a=10/0;
}catch (Exception e){
e.printStackTrace();
}
return result;
}
程序虽然有异常但是还是成功提交了
数据库也成功插入数据
4.重新抛出异常后,事务还是会回滚
/*
重新抛出异常或者手动抛出异常依旧会回滚
*/
@Transactional
@RequestMapping("/r4")
public Integer r4 (String userName,String password){
Integer result=userInfoService.insert(userName,password);
try {
int a=10/0;
}catch (Exception e){
//抛出异常
throw e;
}
return result;
}
没有出现commiting,提交事务未成功
此时数据库也没有结果
5.异常try-catch住后,依然可以通过手动回滚来回滚事务
使用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@Transactional
@RequestMapping("/r5")
public Integer r5 (String userName,String password){
Integer result=userInfoService.insert(userName,password);
try {
int a=10/0;
}catch (Exception e){
//手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
实现了回滚
数据库也是没有记录的
6.对于Exception异常不会处理,依然会提交事务
/*
事务对于异常的处理
//Exception不会回滚
*/
@Transactional
@SneakyThrows//这个注解也是通过try-catch捕获的异常
@RequestMapping("/r6")
public Integer r6 (String userName,String password){
Integer result=userInfoService.insert(userName,password);
if (true){
throw new Exception();
}
return result;
}
观察结果,事务提交成功.
7.既然Exception 异常可以提交事务我们试下RuntimeException异常
/*
事务对于异常的处理
//异常中RuntimeException和Error会自动回滚,其他的不会
*/
@Transactional
@RequestMapping("/r7")
public Integer r7 (String userName,String password){
Integer result=userInfoService.insert(userName,password);
if (true){
throw new RuntimeException();
}
return result;
}
这里 RuntimeException异常并没有提交事务,而是回滚了.因为规定RuntimeException和Error会自动回滚,但是其他的异常不会
8.如果想要其他异常也回滚,我们需要设置
@Transactional(rollbackFor = Exception.class)
/*
注解中指定所有异常都要回滚
*/
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r8")
@SneakyThrows
public Integer r8 (String userName,String password){
Integer result=userInfoService.insert(userName,password);
if (true){
throw new Exception();
}
return result;
}
指定回滚异常类型即可.
结果显示其他类型也实现了回滚.
3.事务隔离级别
3.1MySQL事务隔离
读未提交(脏读):这种隔离级别的事务可以看到其他事务中未提交事务的数据.
比如A事务只是刚开始事务,还未提交事务,B事务也可以阅读A事务未提交的数据.
读提交(不可重复读):该隔离级别的事务能读取到已经提交事务的数据
事务1开始,更新A和B的余额,但还没提交。这时事务2第一次读取B的余额,应该得到原来的100元,而不是事务1未提交的200元。然后事务1提交后,事务2再次读取,得到200元。这样两次读取结果不同,说明不可重复读,但避免了脏读。
可重复读 :可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也 就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别
串行化 :序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突, 从⽽解

3.2 Spring的事务隔离级别
3.3 事务传播机制
3.3.1什么是事务传播机制


3.3.2事务的传播机制
代码解释:
TestController调用UserInfoService和LogInfoService的两个方法,三个方法都是使用同一个事务.程序都正确执行的情况下事务都会提交.但是如果其中有一个抛出了异常,那么其余也会回滚
就是我们所谓的一荣俱荣,一损俱损.同年同月同日生,同年同月同日死.
TestController代码:
public class TestController {
@Autowired
private UserInfoService userService;
@Autowired
private LogInfoService logInfoService;
@Transactional
@RequestMapping("/register")
public Boolean register(String userName, String password){
/**
* 用户表的插入和日志表的插入, 理应在Service完成
* 为了方便, 咱们放在Controller里面完成
*/
Integer result = userService.insert(userName, password);
System.out.println("插入用户表, result: "+ result);
//插入日志表
Integer logResult = logInfoService.insert(userName, "用户注册");
System.out.println("插入日志表, result: "+ logResult);
return true;
}
}
LogInfoService方法(假如LogInfoService存在异常)
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public Integer insert(String userName, String op) {
Integer result = logInfoMapper.insertLog(userName, op);
int a = 10/0;
return result;
}
}
UserInfoService方法:(无异常)
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public Integer insert(String userName, String password) {
Integer result=userInfoMapper.queryInsert(userName,password);
return result;
}
}
上述代码中 由于哥三个都是使用的同一个事务,LogInfoService存在异常,那么UserInfoService也不会成功提交.
就像是兄弟二人共吃一块蛋糕,此时老二把蛋糕掉地上了,那么就谁也吃不上了.
就像是两人结婚后本来结婚前你有的房子我不要,我还要自己再买一套大平层来住,不稀罕小屋子
代码解释:
TestController代码:
public class TestController {
@Autowired
private UserInfoService userService;
@Autowired
private LogInfoService logInfoService;
@Transactional
@RequestMapping("/register")
public Boolean register(String userName, String password){
/**
* 用户表的插入和日志表的插入, 理应在Service完成
* 为了方便, 咱们放在Controller里面完成
*/
Integer result = userService.insert(userName, password);
System.out.println("插入用户表, result: "+ result);
//插入日志表
Integer logResult = logInfoService.insert(userName, "用户注册");
System.out.println("插入日志表, result: "+ logResult);
return true;
}
}
UserInfoService代码:
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer insert(String userName, String password) {
Integer result=userInfoMapper.queryInsert(userName,password);
return result;
}
}
LogInfoService代码:
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer insert(String userName, String op) {
Integer result = logInfoMapper.insertLog(userName, op);
int a = 10/0;
return result;
}
}
上述代码中即使 LogInfoService发生异常回滚事务,对于UserInfoService没有丝毫影响,因为他们两个是不同的事务.
6.Propagation.NEVER : 以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常.
婚后一直租房住,但是如果你有房子,我们立即离婚.
TestController代码
public class TestController {
@Autowired
private UserInfoService userService;
@Autowired
private LogInfoService logInfoService;
@Transactional
@RequestMapping("/register")
public Boolean register(String userName, String password){
/**
* 用户表的插入和日志表的插入, 理应在Service完成
* 为了方便, 咱们放在Controller里面完成
*/
Integer result = userService.insert(userName, password);
System.out.println("插入用户表, result: "+ result);
//插入日志表
Integer logResult = logInfoService.insert(userName, "用户注册");
System.out.println("插入日志表, result: "+ logResult);
return true;
}
}
UserInfoService代码:
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NEVER)
public Integer insert(String userName, String password) {
Integer result=userInfoMapper.queryInsert(userName,password);
return result;
}
}
TestController调用UserInfoService中的方法.此时TestController存在事务,那么UserInfoService就会抛出异常.
7.Propagation.NESTED : 如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏. 如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED .
如果没房,我们就一起攒钱买个新房,如果有新房,我们就以房子为根基去创业.
解释:
此时如果还是 LogInfoService存在异常,和.Propagation.REQUIRED方式不同.由于他们三个使用的是不同的事务.所以只需要存在异常的LogInfoService自己回滚即可,而对UserInfoService不会有影响.