在学习MySQL时我们学习过事务,而到了Spring的学习时,同样会学习到事务,两个东西的事务的定义都是一样的:将一组操作封装成一个执行单元,要么全部成功,要么全部失败
在Spring中使用事务有两种方式
一种是使用编程式事务
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//编程式事务
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;//事务管理器 -> 所有的事务都是通过这个事务管理器来进行管理的
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/del")
public int del(Integer id){
if(id == null || id < 0){
return -1;
}
//开启事务
TransactionStatus transactionStatus =
dataSourceTransactionManager.getTransaction(transactionDefinition);
int result = userService.del(id);
System.out.println("删除: " + result);
// dataSourceTransactionManager.commit(transactionStatus);//
dataSourceTransactionManager.rollback(transactionStatus);//为了不污染数据库,所以使用rollback就可以回滚事务
return result;
}
}
一种是使用声明式事务
@RestController
@RequestMapping("/user3")
public class UserControllerTransaction {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/add")
public int add(String username,String password){
if (null == username || null == password || username.equals(" ") || password.equals(" ")){
return 0;
}
User user = new User();
user.setUsername("wangwu");
user.setPassword("123456");
int result = userService.add(user);
return result;
}
}
两者的区别在于一种是使用编写大量代码的方式启动事务,另一种是使用注解的方式启动事务
很明显使用注解的方式启动事务的声明式事务更加简单好用,但是使用声明式事务也需要一定的注意:声明式事务只能作用于public修饰的方法上,如果作用在public修饰的类上那么该类的所有public方法都会被添加事务
我们使用声明式事务时,如果方法内出现了异常但是异常被捕获了,那么此时我们的事务不会进行回滚操作
@RequestMapping("/del")
@Transactional
public int del(Integer id){
if (id == null || id <= 0){
return 0;
}
try{
int num = 10 / 0;
}catch (Exception e){
System.out.println(e);
}
return userService.del(id);
}
可以看到,当我们的代码内部出现了异常但是事务并没有出现回滚操作
原因在于在代码里面自己把异常处理了,所以代码里面不会抛出异常
当Transactional没有检测到异常的出现的时候那么就会认为代码式正常执行的
所以事务不会进行回滚操作
解决方案:1.不要添加try catch或者抛出异常让框架Transactional来感知异常,自动回滚
try{
int num = 10 / 0;
}catch (Exception e){
throw e;
}
2.自己手动添加回滚操作
try{
int num = 10 / 0;
}catch (Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
事务的四大特性:原子性,隔离性,持久性,一致性,其中Spring事务的隔离性与学习MySQL时遇到的隔离性几乎是完全一样的:
Spring 中事务隔离级别包含以下 5 种:
1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读
3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重 复读
4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级 别)
5. Isolation.SERIALIZABLE:串行化,可以解决所有并发问题但是性能太低
可以看到在Spring中只有Isolation.DEFAULT这个隔离级别是特有的,其余隔离级别与MySQL相同
事务传播机制级别分为七个
1.Propagation.REQUIRED:Spring默认的事务传播级别,如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就创建一个事务
2.Propagation.SUPPORTS:如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就以非事务的方式运行
3.Propagation.MANDATORY:如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就抛出异常
4.Propagation.REQUIRES_NEW:如果当前存在事务那么就挂起当前事务,重新开启自己的事务,也就是说无论当前是否存在事务,都会新开一个事务,开启的事务相互独立互不干扰
5.Propagation.NOT_SUPPORTED:如果当前存在事务那么就挂起当前事务,以非事务的方式进行运行
6.Propagation.NEVER:以非事务的方式进行运行,如果存在事务那么就抛出异常
7.Propagation.NESTED:如果当前存在事务,那么就创建一个事务作为当前事务的嵌套事务进行运行,如果当前没有事务就等价于Propagation.REQUIRED
这里重点说一下Propagation.NESTED,什么叫做嵌套事务呢?说白了就是记录一个保存点,当事务回滚的时候回滚到保存点的位置,使用Propagation.NESTED可以实现部分事务回滚的效果,因此之前的事务是不受影响的
下面使用代码的方式演示部分事务传播机制级别
Propagation.REQUIRED
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插⼊⽤户操作
userService.add(user);
// 插⼊⽇志
logService.add("⽤户插⼊:" + user.getUsername());
return true;
}
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRES)
public int add(String content){
int i = 10 / 0;
Log log = new Log();
log.setId(1);
log.setMessage(content);
return logMapper.add(log);
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRES)
public int add(User user){
int result = userMapper.add(user);
return result;
}
}
当我们的代码里面有错误时,整个事务都会被回滚
1.UserService程序执行完成
2. LogService 保存⽇志程序报错,因为使⽤的是 Controller 中的事务,所以整个事务回滚
3. 数据库中没有插⼊任何数据,也就是步骤 1 中的⽤户插⼊⽅法也回滚了
REQUIRES_NEW
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插⼊⽤户操作
userService.add(user);
// 插⼊⽇志
logService.add("⽤户插⼊:" + user.getUsername());
return true;
}
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(String content){
int i = 10 / 0;
Log log = new Log();
log.setId(1);
log.setMessage(content);
return logMapper.add(log);
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(User user){
int result = userMapper.add(user);
return result;
}
}
执行结果:UserSevice顺利插入数据,LogService插入数据失败,但两者都没有影响UserController的执行
Propagation.NEVER
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插⼊⽤户操作
userService.add(user);
return true;
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.NEVER)
public int add(User user){
int result = userMapper.add(user);
return result;
}
}
程序直接报错,没有插入任何数据
Propagation.NESTED
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插⼊⽤户操作
userService.add(user);
return true;
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.NESTED)
public int add(User user){
int result = userMapper.add(user);
// 插⼊⽇志
logService.add("⽤户插⼊:" + user.getUsername());
return result;
}
}
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
@Transactional(propagation = Propagation.NESTED)
public int add(String content){
int i = 10 / 0;
Log log = new Log();
log.setId(1);
log.setMessage(content);
return logMapper.add(log);
}
}
执行结果:User和Log都没有插入数据
原因在于:UserController调用了UserService,UserService执行完毕插入数据调用LogService,LogService出现异常,由于是嵌套事务,执行回滚操作,回滚到调用该方法的位置,回滚到UserService,由于代码中出现异常,所以UserService也需要进行回滚到调用该方法的位置,所以两个数据并没有插入
但是如果说LogService出现异常的时候处理了异常,那么此时只有LogService会出现回滚而UserService可以不用回滚
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
@Transactional(propagation = Propagation.NESTED)
public int add(String content){
try{
int num = 10 / 0;
}catch (Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
Log log = new Log();
log.setId(1);
log.setMessage(content);
return logMapper.add(log);
}
}
从实现了部分回滚的操作
Require和Nested两种事务传播机制级别的区别
Require是将当前事务加入到整个事务当中,假如出现了回滚那么整个事务都需要进行回滚
Nested是将当前事务嵌套到整个事务当中,假如出现了回滚那么可以实现部分事务回滚的操作,部分事务回滚到保存点(也就是调用该方法的代码处),不会影响上一个方法的执行结果