Spring事务和事务传播机制
一、概念介绍
1.什么是事务
事务是一组操作的集合,是一组不可分割的操作。这组操作要么同时成功,要么同时失败。
2.事务的操作
事务的操作主要有三步:
1.开启事务,start transaction。
2.提交事务,commit。
3.回滚事务,rollback。
二、Spring中的事务实现
Spring中的事务操作分为两类:
1.编程式事务。(手动写代码操作事务)
2.声明式事务。(利用注解自动开启和提交事务)
事务实现:
1.创建数据库
首先创建数据库trans_test,然后创建用户表和操作表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (`id` INT NOT NULL AUTO_INCREMENT,`user_name` VARCHAR (128) NOT NULL,`password` VARCHAR (128) NOT NULL,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '⽤⼾表';
-- 操作⽇志表
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (`id` INT PRIMARY KEY auto_increment,`user_name` VARCHAR ( 128 ) NOT NULL,`op` VARCHAR ( 256 ) NOT NULL,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now()
) DEFAULT charset 'utf8mb4';
2.项目创建
创建项目,引入依赖。(略)
3.创建实体类
@Data
public class UserInfo {private Integer id;private String userName;private String password;private Date createTime;private Date updateTime;
}
@Data
public class LogInfo {private Integer id;private String userName;private String op;private Data createTime;private Data updateTime;
}
4.创建Mapper
@Mapper
public interface LogInfoMapper {@Insert("insert into log_info(user_name,op) values (#{name},{#op})")Integer insert(String userName, String op);
}
@Mapper
public interface UserInfoMapper {@Insert("insert into user_info(user_name,password) values(#{name},#{password})")Integer insert(String username, String password);
}
5.创建Service类
@Slf4j
@Service
public class LogInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;public void insertLog(String userName,String op) {logInfoMapper.insert(userName,"用户注册");}
}
@Slf4j
@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;public void registryUser(String username, String password) {userInfoMapper.insert(username, password);}
}
6.创建Controller类
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);return "注册成功";}
}
6.创建Controller类
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);return "注册成功";}
}
实现声明式事务@Transactional
1.先添加依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId>
</dependency>
2.在需要事务的方法上添加@Transactional注解,在进入方法时自动开启事务,方法执行完自动提交事务,如果发生了没有处理的异常会自动回滚事务。
例如:在registry上添加
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);return "注册成功";}
}
启动后正常插入数据:
修改程序,使之出现异常
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);log.info("数据插入成功");int a = 10 / 0;return "注册成功";}
}
运行后发现虽然显示成功,但是数据库并没有新增数据。
@Transactional的作用:
可以用来修饰方法或类:
修饰方法时:只有修饰public方法时才生效;
修饰类时:对@Transactional修饰的类中所以public方法都起效。
注意如果异常没有被捕获,则进行事务异常回滚;
如果异常被捕获了,则方法被认为是正常执行,依然会提交事务。
修改代码,对异常进行捕获:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/registry")public String registry(String userName, String password) {userService.registryUser(userName, password);log.info("添加数据成功");try {int a = 10 / 0;}catch (Exception e) {e.printStackTrace();}return "注册成功";}
}
这是成功添加数据:
但是有时我们希望在捕获到异常后仍然进行事务回滚,可以使用以下方式:
1.捕获异常后再使用throw重新抛出异常:
try {int a = 10 / 0;}catch (Exception e) {throw e;}
2.手动回滚事务
使用TransactionAspectSupport.currentTransactionStatus()得到当前的事务,并使用setRollbackOnly设置setRollbackOnly
log.info("添加数据成功");try {int a = 10 / 0;}catch (Exception e) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}
三、@Transactional详解
主要了解@Transactional注解中三个常见的属性:
1.rollbackFor:异常回滚属性,指定能够触发事务回滚的异常类型,可以指定多个异常类型
2.Isolation:事务的隔离级别,默认值为Isolation.DEFAULT
3.propagation:事务的传播机制,默认值为Propagation.REQUIRED
1.rollbackFor
@Transactional默认只在遇到异常和error时才会回滚,非运行时异常不回滚,即Exception的子类,除了RuntimeException及其子类。
在上面的案例中抛出的异常是Exception,如果改为IOException,就如下代码:
@Transactional@RequestMapping("/t1")public String t1(String userName, String password) throws IOException {userService.registryUser(userName, password);log.info("用户信息插入成功");if(true){throw new IOException();}return "t1";}
测试代码:
可以发现程序虽然抛出了异常,但是事务依然进行了提交。
如果希望在IOException的情况下仍然可以进行事务回滚,可以通过@Transactional注解中的rollbackFor属性,通过rollbackFor这个属性指定出现何种异常类型时进行事务回滚。
@Transactional(rollbackFor = Exception.class)
这样设置的话。Exception下面的子类就都会报错了。
总结:在Spring的事务管理中,默认只有遇到异常RuntimeException和Error时才会回滚;
如果需要指定回滚的异常类型,可以通过rollbackFor属性来指定。
2.Isolation
意为事务的隔离级别,首先了解一下Mysql事务的隔离级别:
1.读未提交
也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。
2.读提交
读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据。
3.可重复读
事务不会读到其他事务对已有数据的修改,即使其他事务已提交,也就可以确保同一事务多次查询的结果一致,但是其他事务新插入的数据是可以感知到的,这也就会引发幻读问题。可重复读,是Mysql的默认事务隔离级别
4.串行化
序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读,不可重复读和幻读问题,但因为执行效率低,所以使用场景并不多。
Spring事务的隔离级别
1.Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
2.Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准读未提交。
3.Isolation.READ_COMMITTED:读已提交,对应sql的读已提交。
4.Isolation.REPEATABLE_READ:可重复读,对应sql的可重复读。
5.Isolation.SERIALIZABLE:串行化,对应sql中的串行化。
查看源码:
Spring中的事务隔离级别可以通过@Transactional中的isolation属性进行设置。
@Transactional(isolation = Isolation.READ_COMMITTED)
3.propagation
propagation译为事务传播机制
所谓事务传播机制就是:多个事务方法存在调用关系时,事务是如何在这些方法间传播的。
解决的问题是一个事务在多个节点(方法)中传递的问题。
Spring的事务传播机制有以下7种:
1.Propagation.REQUIRED:默认的事务传播级别,如果存在当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。
2.Propagation.SUPPORTS:如果当前存在事务,则加入该事务,如果没有事务,则以非事务的方式进行。
3.Propagation.MANDATORY:强制性,如果存在事务,则加入该事务,如果没有事务,则抛出异常。
4.Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在一个事务,则将当前的事务挂起。
5.Propagation.NOT_SUPPORTED:以非事务的方式运行,如果存在事务,则将事务挂起。
6.Propagation.NEVER:以非事务方式运行,如果存在事务,则抛出异常。
7.Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则等价于Propagation.REQUIRED。
对于这些事务传播机制,重点理解REQUIRED和REQUIRES_NEW。
.Propagation.REQUIRED的实例:
将Controller和UserService和LogInfoService上都加上
@Transactional(propagation = Propagation.REQUIRED)
@Autowiredprivate UserService userService;@Autowiredprivate LogInfoService logInfoService;@Transactional(propagation = Propagation.REQUIRED)@RequestMapping("/t1")public String t1(String userName, String password) {userService.registryUser(userName, password);logInfoService.insertLog(userName,"用户注册");return "t1";}
@Slf4j
@Service
public class LogInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Transactional(propagation = Propagation.REQUIRED)public void insertLog(String userName,String op) {logInfoMapper.insert(userName,"用户注册");}
}
@Slf4j
@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.REQUIRED)public void registryUser(String userName, String password) {userInfoMapper.insert(userName, password);}
}
之后运行代码;
运行之后,发现并没有插入任何数据;
描述流程:
1.t1方法开始事务
2.UserService插入一条数据(执行成功,而且和t1使用同一个事务)。
3.LogInfoService也尝试插入一条数据,但是由于出现异常而且没有捕获,所以执行失败(和t1使用同一个事务)
4.由于步骤3出现异常,所以事务回滚,步骤2和3使用一个事务,所以步骤2的数据也回滚了。
REQUIRES_NEW示例:
首先将UserService和LogInfoService中的事务传播机制改为REQUIRES_NEW
@Slf4j
@Service
public class LogInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public void insertLog(String userName,String op) {int a = 10 / 0;logInfoMapper.insert(userName,"用户注册");}
}
@Slf4j
@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public void registryUser(String userName, String password) {userInfoMapper.insert(userName, password);}
}
运行代码,发现用户数据插入成功,但是日志表插入失败,可以看出LogInfoService方法中的事务不影响User Service中的事务。