JAVA-Web后端学习8 事务
へんなの へんなの,へんなの へんなの
事务介绍
就像我们在实战中对于员工数据的存储一样,有个问题我们需要注意:存储员工数据时会分别存储员工的个人基本信息和员工的工作经历,如果前者存储成功而后者存储失败该怎么办?
是的,这是在实际开发中不允许的。因为二者是一体,属于同一个业务操作,必须保证同时成功或者都失败才行,否则会造成数据库数据的不一致
事务:一套操作的集合,是一个不可分割的单位。事务会把所有的操作作为一个整体一起向系统提交或者撤销操作,也就是说,这些操作要么全部成功,要么全部失败
一般情况下,默认MySQL的事务是自动提交的,也就是说当执行一条DML语句,MySQL会隐式地自动提交事务
事务操作
MYSQL中的事务操作如下:
-- 开启事务操作
start transaction/begin;
-- 提交事务操作
commit;
-- 撤销事务操作,回滚 (只要有一项失败)
rollback;
事务控制
回看这段代码
@Override
public void insertNewEmpData(EmpData empData) {
//1.保存员工个人基本信息
empData.setCreateTime(LocalDateTime.now());
empData.setUpdateTime(LocalDateTime.now());
empData.setPassword("123456");
empMapper.insertNewEmp(empData);
//2.保存员工工作经历
List<EmpExprData> empExprDataList=empData.getExprList();
if(!CollectionUtils.isEmpty(empExprDataList)) {
for(EmpExprData empExprData : empExprDataList) {
empExprData.setEmpId(empData.getId());
}
System.out.println("Now we get It ! empExprMapper.insertNewEmpExpr!");
empExprMapper.insertNewEmpExpr(empExprDataList);
}
}
我们需要保证1和2要么都成功,要么都失败,也就是使用事务控制
Spring事务管理
Spring的事务管理封装在了@Transactional注解中,作用是将当前方法交给spring进行事务管理,方法执行前开启事务,执行完毕则提交事务,否则回滚事务
@Transactional注解的位置在逻辑业务层(Service)的方法上、类上、接口上
一般推荐@Transactional注解添加在对于数据库进行多次增删改查的方法上
在application.yml的配置文件中添加如下语句,用于输出Spring事务管理的底层日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
接下来,我们在之前的实战代码中添加上该注解
@Transactional
@Override
public void insertNewEmpData(EmpData empData) {
empData.setCreateTime(LocalDateTime.now());
empData.setUpdateTime(LocalDateTime.now());
empData.setPassword("123456");
empMapper.insertNewEmp(empData);
List<EmpExprData> empExprDataList=empData.getExprList();
if(!CollectionUtils.isEmpty(empExprDataList)) {
for(EmpExprData empExprData : empExprDataList) {
empExprData.setEmpId(empData.getId());
}
System.out.println("Now we get It ! empExprMapper.insertNewEmpExpr!");
empExprMapper.insertNewEmpExpr(empExprDataList);
}
}
控制台相关信息如下,可以看到相应的事务操作:

那么如果出现异常会怎么办,比如将代码修改为如下:
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
private EmpExprMapper empExprMapper;
@Transactional
@Override
public void insertNewEmpData(EmpData empData) {
empData.setCreateTime(LocalDateTime.now());
empData.setUpdateTime(LocalDateTime.now());
empData.setPassword("123456");
empMapper.insertNewEmp(empData);
List<EmpExprData> empExprDataList=empData.getExprList();
if(!CollectionUtils.isEmpty(empExprDataList)) {
for(EmpExprData empExprData : empExprDataList) {
empExprData.setEmpId(empData.getId());
}
System.out.println("Now we get It ! empExprMapper.insertNewEmpExpr!");
empExprMapper.insertNewEmpExpr(empExprDataList);
}
}
}
这是一个常见的错误,此时@Autowired只会给empMapper进行DI注入而没有给empExprMapper进行DI注入,从而导致后者没有实体对象
接下来执行,控制台输出信息如下:

可以看到只有员工基本数据表的插入操作执行了,而工作经历表的没有执行,而失败之后事务执行了回滚(rollback)操作。
这时去查看相关的数据表,发现都没有新数据注入


而如果没有添加相关的事务注解,再来执行同样的问题

可以看到有一句话Closing non transactional SqlSession 表示这是正在关闭一个非事务性 SqlSession
此时查看数据库


可以看到员工个人基本数据表被更新,而工作经历数据表没有被更新,这显然是不对的
Spring事务管理的进阶操作
rollbackFor
rollbackFor属性用于控制出现何种异常类型时进行事务回滚
@Transactional(rollbackFor={Exception.class})
propagation
事务控制中的propagation(传播机制)是指当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制的机制。
事务传播机制定义了在多个事务方法相互调用时,事务应该如何传播和管理。它解决了当一个事务方法调用另一个事务方法时,新方法的事务是加入现有事务、创建新事务,还是以非事务方式执行的问题。
乍一听有点抽象,不过没关系,使用案例代码介绍一下
还是之前的代码,这一次添加一个员工添加日志记录方法,不论添加该员工数据成功还是失败,都要调用该方法记录日志到一个日志数据库中
@Transactional
@Override
public void insertNewEmpData(EmpData empData) {
try {
empData.setCreateTime(LocalDateTime.now());
empData.setUpdateTime(LocalDateTime.now());
empData.setPassword("123456");
empMapper.insertNewEmp(empData);
List<EmpExprData> empExprDataList=empData.getExprList();
if(!CollectionUtils.isEmpty(empExprDataList)) {
for(EmpExprData empExprData : empExprDataList) {
empExprData.setEmpId(empData.getId());
}
System.out.println("Now we get It ! empExprMapper.insertNewEmpExpr!");
empExprMapper.insertNewEmpExpr(empExprDataList);
}
} finally {
EmpLogInsert();
}
}
//这是另一个类的方法
@Transactional
void EmpLogInsert(){
//记录日志
...
}
我们为了保证调用insertNewEmpData方法时无论插入员工数据成功与否都要调用EmpLogInsert方法,使用了try...finally关键字修饰,并且在后者也添加了@Transactional事务注解
但是如果运行的话就会发现EmpLogInsert方法在insertNewEmpData方法时无论插入员工数据出现异常时会执行,但是也会因为后者的回滚而导致无法完全记录日志到数据库中去
这因为事务控制的传播机制默认定义如下:
@Transaction(prapagation=Propagation.REQUIRED)
Propagation.REQUIRED是Spring默认的事务传播行为,意思是:如果当前存在事务,则加入该事务;如果当前不存在事务,则创建一个新的事务。
也就是说EmpLogInsert方法的事务在insertNewEmpData方法的调用中会加入后者的事务,而前者的事务则不会被创建,因此后者失败的话连带前者也会被回滚
所以需要定义如下:
@Transaction(prapagation=Propagation.REQUIRES_NEW)
Propagation.REQUIRES_NEW表示新事务与原有事务完全独立,互不影响。无论当前是否存在事务,都创建一个新的事务;如果当前存在事务,则挂起当前事务。
把上面的代码完整修改后如下:
@Transaction
@Override
public void insertNewEmpData(EmpData empData) {
try {
empData.setCreateTime(LocalDateTime.now());
empData.setUpdateTime(LocalDateTime.now());
empData.setPassword("123456");
empMapper.insertNewEmp(empData);
List<EmpExprData> empExprDataList=empData.getExprList();
if(!CollectionUtils.isEmpty(empExprDataList)) {
for(EmpExprData empExprData : empExprDataList) {
empExprData.setEmpId(empData.getId());
}
System.out.println("Now we get It ! empExprMapper.insertNewEmpExpr!");
empExprMapper.insertNewEmpExpr(empExprDataList);
}
} finally {
EmpLogInsert();
}
}
//这是另一个类的方法
@Transactional(prapagation=Propagation.REQUIRES_NEW)
void EmpLogInsert(){
//记录日志
...
}
除了REQUIRED和REQUIRES_NEW两种行为之外,一共有如下行为:

事务的四大特性
事务存在四大特性,称为ACID特性,详细如下:
原子性(Atomicity) 事务是不可分割的最小单元,要么全部成功,要么全部失败
一致性(Consistency) 事务完成时必须使所有数据都保持一致状态
隔离性(Isolation) 数据库系统提供的隔离机制,保证事务在不收外部并发操作的独立环境下运行
持久性(Durability) 事务一旦提交或者回滚,对于数据库造成的影响是永久的

浙公网安备 33010602011771号