1.事务的概念
1.1什么是事务
事务是一组操作的集合,是一个不可分割的操作
1.2 为什么需要事务?
我们在进⾏程序开发时,也会有事务的需求.⽐如转账操作:
第⼀步:A账⼾-100元.第⼆步:B账⼾+100元.
如果没有事务,第⼀步执⾏成功了,第⼆步执⾏失败了,那么A账⼾的100元就平⽩⽆故消失了.如果使⽤事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败.
⽐如秒杀系统,第⼀步:下单成功第⼆步:扣减库存
下单成功后,库存也需要同步减少.如果下单成功,库存扣减失败,那么就会造成下单超出的情况.所以就需要把这两步操作放在同⼀个事务中.要么⼀起成功,要么⼀起失败.
理解事务概念为主,实际企业开发时,并不是简单的通过事务来处理.
1.3 事务的操作
事务的操作主要有三步:
-
开启事starttransaction/begin(⼀组操作前开启事务)
-
提交事务:commit(这组操作全部成功,提交事务)
3.回滚事务:rollback(这组操作中间任何⼀个操作出现异常,回滚事务)
2.Spring中事务的实现
以上是数据库中事务的操作,那我们放到Spring中如何实现事务呢
Spring中事务分成两类
1.编程式事务
手动开启事务,手动提交事务
2.声明式事务 (更好实现)
通过注解来实现
我们先进行数据的准备
需要准备以下数据
//实体类
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Date createTime;
private Date updateTime;
}
import lombok.Data;
import java.util.Date;
@Data
public class LogInfo {
private Integer id;
private String userName;
private String op;
private Date createTime;
private Date updateTime;
}
//. Mapper
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(`user_name`,`password`)values(#{name},#{pa
Integer insert(String name,String password);
}
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@ Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
Integer insertLog(String name,String op);
}
//5. Service
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public void registryUser(String name,String password){
//插⼊⽤⼾信息
userInfoMapper.insert(name,password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
public void insertLog(String name,String op){
//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤⼾注册");
}
}
//6. Contrller
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
return "注册成功";
}
}
1.编程式事务
1.Spring⼿动操作事务和上⾯MySQL操作事务类似,有3个重要操作步骤:
• 开启事务(获取事务)
• 提交事务
• 回滚事务
2.SpringBoot内置了两个对象:
- DataSourceTransactionManager 事务管理器.⽤来获取事务(开启事务),提交或回滚事务
的
- TransactionDefinition 是事务的属性,在获取事务的时候需要将
TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus
我们先根据代码来学习事务,代码如下
package com.example.trans1.controller;
import com.example.trans1.service.UserService;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String userName,String passWord){
//开起事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
Integer result=userService.insertUser(userName,passWord);
//回滚事务
log.info("用户插入成功,result:"+result);
dataSourceTransactionManager.rollback(transaction);
return "注册成功";
}
}
我们运行程序,运行postman观察结果
我们执行成功了数据,但是点开数据库发现并没有数据,这种情况就是数据回滚了
我们再来试一下提交事务
运行程序,观察数据库
2.声明式事务
声明式事务的实现很简单,只需要在需要事务的⽅法上添加 @Transactional 注解就可以实现了.⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务。
我们先来看代码实现
package com.example.trans1.controller;
import com.example.trans1.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/trans")
@RestController
@Slf4j
public class TransController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String userName,String password){
Integer result=userService.insertUser(userName,password);
log.info("用户插入成功,result:"+result);
return "注册成功";
}
}
这里我们可以看到,只需要加上一个注解 @Transactional,就可以实现,我们观察运行结果,
1.发现成功的实现了提交,那我们怎么进行回滚呢,
这里情况就是没有抛出异常时,事务就进行提交,抛出了异常并且没有捕获或者捕获了没有处理,事务就进行回滚,如下
2.假如我们处理了异常,就会发现事物提交成功
3,假如我们捕获了异常,又重新抛出
我们发现程序执行出错,事务进行回滚
4.我们怎么在声明中进行手动回滚呢
我们需要加入以下代码
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
图如下
5.假如我们抛出了异常,但是抛的其他异常,
我们发现有些异常能回滚,有些异常会正常提交
因为@Transactional 默认只在遇到运⾏时异常和Error时才会回滚,⾮运⾏时异常不回 滚,即Exception的⼦类中,除了RuntimeException及其⼦类.
3.Transactional详解
1.rollbackFor
那我们该怎么对所有的异常都进行捕获呢,我们就需要这个rollbackfor
如果我们需要所有异常都回滚,需要来配置 @Transactional 注解当中的 rollbackFor 属性,通过 rollbackFor 这个属性指定出现何种异常类型时事务进⾏回滚
如下图
我们就加上rollbackfor注解,并且指定什么异常进行回滚
2.事务的隔离级别
1. sql标准定义了四种隔离级别
-
读未提交(READUNCOMMITTED):读未提交,也叫未提交读.该隔离级别的事务可以看到其他事务中未提交的数据.因为其他事务未提交的数据可能会发⽣回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据,这个问题称之为脏读.
-
读提交(READCOMMITTED):读已提交,也叫提交读.该隔离级别的事务能读取到已经提交事务的数据,**该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间的相同SQL查询可能会得到不同的结果,**这种现象叫做不可重复读
-
可重复读(REPEATABLEREAD):事务不会读到其他事务对已有数据的修改,即使其他事务已提交.也就可以确保同⼀事务多次查询的结果⼀致,但是其他事务新插⼊的数据,是可以感知到的.这也就引发了幻读问题.可重复读,是MySQL的默认事务隔离级别.
⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊了某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因).明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去,这个现象叫幻读.
- 串⾏化(SERIALIZABLE):序列化,事务最⾼隔离级别.它会强制事务排序,使之不会发⽣冲突,从⽽解决了脏读,不可重复读和幻读问题,但因为执⾏效率低,所以真正使⽤的场景并不多.
2.Spring的隔离级别有五种
-
Isolation.DEFAULT :以连接的数据库的事务隔离级别为主.
-
Isolation.READ_UNCOMMITTED :读未提交,对应SQL标准中 READ UNCOMMITTED
-
Isolation.READ_COMMITTED :读已提交,对应SQL标准中 READ COMMITTED
-
Isolation.REPEATABLE_READ :可重复读,对应SQL标准中 REPEATABLE READ
-
Isolation.SERIALIZABLE :串⾏化,对应SQL标准中 SERIALIZABLE
我们可以通过设置来设置级别
3.事务传播机制
事务传播机制就是:多个事务⽅法存在调⽤关系时,事务是如何在这些⽅法间进⾏传播的
⽐如有两个⽅法A,B都被 @Transactional 修饰,A⽅法调⽤B⽅法
A⽅法运⾏时,会开启⼀个事务.当A调⽤B时,B⽅法本⾝也有事务,此时B⽅法运⾏时,是加⼊A的事务,还是创建⼀个新的事务呢?这个就涉及到了事务的传播机制
1.事务的传播机制有哪些
特别注意
7.NESTED,当前存在多个事务,想当与它的主事务和附属事务,当一个事务出错时,只需要及时处理当前的事务,就不会影响其他事务的执行
4.总结
-
Spring中使⽤事务,有两种⽅式:编程式事务(⼿动操作)和声明式事务.其中声明式事务使⽤较多,在⽅法上添加 @Transactional 就可以实现了
-
通过 @Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离别.Spring中的事务隔离级别有5种
-
通过@Transactional(propagation=Propagation.REQUIRED)设置事务的传播机制,Spring中的事务传播级别有7种,重点关注 REQUIRED (默认值)和 REQUIRES_NEW