摘要:本文围绕事务管理展开,先回顾事务基本概念与操作,后深入探讨Spring事务管理。通过具体案例剖析事务管理在实际应用中的问题及解决方案,详细介绍@Transactional注解及其属性rollbackFor和propagation的使用。
关键词:事务管理;Spring框架;@Transactional注解;事务传播行为
参考资料:黑马程序员day13 完整项目请从第10天开始看
1 事务回顾
事务是一组不可分割的操作集合,作为一个整体向数据库提交或撤销请求,确保这组操作要么全部成功,要么全部失败。其具体操作包含以下三步:
- 开启事务:在一组操作开始前执行,指令为start transaction / begin。
- 提交事务:当所有操作成功完成后,使用commit指令提交。
- 回滚事务:若操作过程中出现异常,通过rollback指令回滚事务。
-- 设置当前会话的隔离级别为 READ COMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 开始事务
START TRANSACTION;
-- 执行事务操作
-- ...
-- 提交事务
COMMIT;2 Spring事务管理
2.1 案例
以解散部门为例,需求是删除部门信息的同时删除该部门下的所有员工数据。
- 代码实现:
DeptServiceImpl:
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
    @Autowired
    private EmpMapper empMapper;
    //根据部门id,删除部门信息及部门下的所有员工
    @Override
    public void delete(Integer id) {
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);
    }
}DeptMapper:
@Mapper
public interface DeptMapper {
    /**
     * 根据id删除部门信息
     * @param id   部门id
     */
    @Delete("delete from dept where id = #{id}")
    void deleteById(Integer id);
}EmpMapper:
@Mapper
public interface EmpMapper {
    //根据部门id删除部门下所有员工
    @Delete("delete from emp where dept_id=#{deptId}")
    public int deleteByDeptId(Integer deptId);
}- 测试情况:正常运行时,dept表和emp表中的相关数据均被删除。但当在DeptServiceImpl的delete方法中添加异常模拟代码(如int i = 1/0;)后,部门信息被删除,而部门下的员工数据未删除,导致数据不一致。
2.2 原因分析
上述问题产生的原因在于,删除部门操作先执行且成功,随后的异常导致后续删除员工数据的操作未执行。要保证数据一致性,需确保解散部门的两个业务操作要么同时成功,要么同时失败,这可通过事务来实现。在Spring框架中,可借助@Transactional注解简化事务控制代码。
2.3 @Transactional注解
@Transactional注解用于控制事务,在方法执行前开启事务,执行完毕后提交事务,若执行过程中出现异常则回滚事务。通常在业务层使用(spring的三层业务结构是:表现层cotroller、业务逻辑层service、数据访问层Dao),因为业务层的一个业务功能可能涉及多个数据访问操作,通过在业务层控制事务,可将这些操作纳入同一事务范围。
@Transactional注解可置于方法、类或接口上:
- 方法:仅当前方法由Spring进行事务管理。
- 类:当前类中的所有方法都由Spring管理事务。
- 接口:接口下所有实现类的所有方法均由Spring管理事务。
在DeptServiceImpl的delete方法上添加@Transactional注解后,再次测试,由于异常事务回滚,部门和员工数据均未被删除,保证了数据一致性。同时,可在application.yml配置文件中开启事务管理日志,以便查看事务相关信息:
#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug3 事务进阶
3.1 rollbackFor
在业务方法上添加@Transactional注解实现事务管理时,默认情况下,只有RuntimeException(运行时异常)会触发事务回滚。
例如:
- 当业务方法中抛出除0的算数运算异常(运行时异常)时,事务会回滚。
- 但当抛出Exception(编译时异常)时,事务不会回滚。
若希望所有异常都能触发事务回滚,可通过配置@Transactional注解的rollbackFor属性指定异常类型。如:
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
    @Autowired
    private EmpMapper empMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer id) {
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        //模拟:异常发生
        int num = id / 0;
        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);
    }
}重新启动服务测试删除部门操作,可发现由于异常事务回滚,部门未被删除。由此可见,在Spring事务管理中,默认仅运行时异常会回滚事务,若要回滚指定类型异常,可通过rollbackFor属性指定。
PS:rollbackFor参数用来指定哪些异常会触发事务回滚。 rollbackFor = Exception.class 表明所有 Exception 类型的异常都会触发回滚,这其中包含了受检查异常(Checked Exception)。
3.2 propagation
3.2.1 介绍
propagation属性用于配置事务的传播行为,即当一个事务方法调用另一个事务方法时,后者应如何进行事务控制。例如,有A和B两个事务方法,均添加@Transactional注解,若A方法调用B方法,事务传播行为决定B方法是加入A方法的事务,还是新建一个事务。
通过在@Transactional注解后指定propagation属性来控制事务传播行为,常见的事务传播行为如下:
| 属性值 | 含义 | 
| 
 | 【默认值】需要事务,有则加入,无则创建新事务 | 
| 
 | 需要新事务,无论有无,总是创建新事务 | 
| 
 | 支持事务,有则加入,无则在无事务状态中运行 | 
| 
 | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 | 
| 
 | 必须有事务,否则抛异常 | 
| 
 | 必须没事务,否则抛异常 | 
实际应用中,重点关注REQUIRED(默认值)和REQUIRES_NEW。
3.2.2 案例
以解散部门并记录操作日志为例,具体步骤如下:
- 准备工作:
创建数据库表dept_log:
create table dept_log(
    id int auto_increment comment '主键ID' primary key,
    create_time datetime null comment '操作时间',
    description varchar(300) null comment '操作描述'
)comment '部门操作日志表';引入实体类DeptLog、Mapper接口DeptLogMapper、业务接口DeptLogService及业务实现类DeptLogServiceImpl。
- 代码实现:
@Slf4j
@Service
//@Transactional //当前业务实现类中的所有的方法,都添加了spring事务管理机制
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
    @Autowired
    private EmpMapper empMapper;
    @Autowired
    private DeptLogService deptLogService;
    //根据部门id,删除部门信息及部门下的所有员工
    @Override
    @Log
    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer id) throws Exception {
        try {
            //根据部门id删除部门信息
            deptMapper.deleteById(id);
            //模拟:异常
            if (true) {
                throw new Exception("出现异常了~~~");
            }
            //删除部门下的所有员工信息
            empMapper.deleteByDeptId(id);
        } finally {
            //不论是否有异常,最终都要执行的代码:记录日志
            DeptLog deptLog = new DeptLog();
            deptLog.setCreateTime(LocalDateTime.now());
            deptLog.setDescription("执行了解散部门的操作,此时解散的是" + id + "号部门");
            //调用其他业务类中的方法
            deptLogService.insert(deptLog);
        }
    }
    //省略其他代码...
}- 测试与分析:
- 测试情况:重新启动SpringBoot服务,测试删除3号部门,程序发生Exception异常,执行事务回滚,dept_log表中未记录日志数据。
- 原因分析:delete操作开启一个事务,insert操作默认事务传播行为为REQUIRED,即加入delete操作的事务。由于同一事务中的操作要么同时成功,要么同时失败,异常发生时事务回滚,导致insert操作也被回滚。
- 解决方案:
 在DeptLogServiceImpl类的insert方法上添加@Transactional(propagation = Propagation.REQUIRES_NEW),表示无论是否存在事务,都创建新事务并独立运行。 重启服务再次测试删除3号部门,insert方法运行完毕后事务立即提交,即使外部delete方法所在事务出现异常,已提交的insert事务也不会回滚。
                
 
 
                     
            
        













 
                    

 
                 
                    