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 事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;// 定义事务属性@Autowiredprivate TransactionDefinition transactionDefinition;@Autowiredprivate 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")@SneakyThrowspublic 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 {@Autowiredprivate UserInfoService userService;@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate UserInfoService userService;@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate UserInfoService userService;@Autowiredprivate 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 {@Autowiredprivate 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不会有影响.