一、理论
**概念:**事务是一组操作的集合,会把所有的操作作为一个整体一起向系统提交或者撤销操作,要么同时成功、要么同时失败
。
- 开启事务:(一组操作开始前,开启事务)
start transaction/begin
- 提交事务:(这组操作全部成功后,提交事务)
commit
- 回滚事务:(中间任何一步出现异常,回滚事务)
rollback
Spring
支持编程式事务和声明式事务。
- 编程式事务:使用
Transaction Template
,需要显示执行事务。(实际开发中不常用) - 声明式事务:使用基于
@Transaction
的注解方式,实质是通过AOP
实现,对方法前后进行拦截,在目标方法开始之前开启一个事务,执行完目标方法之后根据执行情况提交或者回滚事务。(常用)
# 声明式事务
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserService userService;
@Override
@Transactional
public void deleteRole(Integer roleId) {
roleMapper.deleteById(roleId);
userService.deleteUserByRoleId(roleId);
}
}
事务管理:
- 注解:
@Transaction
- 位置:
service
层的方法上、类上、接口上。 - 作用: 将当前方法交给
Spring
进行事务管理,方法执行前,开启事务;成功执行后,提交事务;出现异常,回滚事务;
事务属性:
rollback:
当出现异常时,回滚事务,默认情况下,只有出现运行时(RuntimeException)
异常才回滚事务,出现编译时异常,不回滚。
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserService userService;
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteRole(Integer roleId) {
roleMapper.deleteById(roleId);
userService.deleteUserByRoleId(roleId);
}
}
二、Spring
事务中有哪几种事务传播行为
事务传播行为:指当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
属性值 | 含义 |
REQUIRE | 默认值,需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
SUPPORTS | 支持事务,有则加入,无则在独立的连接中运行SQL |
NOT_SUPPORTS | 不支持事务,不加入,在独立的连接中运行SQL |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须没事务,否则抛异常 |
NESTED | 嵌套事务 |
前两个REQUIRE、REQUIRE_NEW
在项目中经常用到,其他不常用。这些事务传播行为在@Transaction
注解中设置 propagation
属性进行指定。
2.1、REQUIRE
默认的事务传播行为,保证多个嵌套的事务方法在同一个事务内执行,并且同时提交,或者出现异常时,同时回滚。(常用)
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserService userService;
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteRole(Integer roleId) {
roleMapper.deleteById(roleId);
userService.deleteUserByRoleId(roleId);
}
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void deleteUserByRoleId(Integer roleId) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getRole_id,roleId);
userMapper.delete(queryWrapper);
}
}
调用链路:开启事务—>执行deleteRole
方法 —>执行deleteUserByRoleId
方法 —> 提交事务
当方法deleteUserByRoleId
执行时抛出了Exception
异常,事务是怎么执行的。
开启事务—>执行deleteRole
方法 —>执行deleteUserByRoleId
方法抛出异常 —> 回滚事务
当事务的传播性设置为REQUIRED
,在整个事务的调用链上,任何一个环节抛出的异常都会导致全局回滚。
2.2、REQUIRE_NEW
每次都开启一 个新的事务
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void deleteUserByRoleId(Integer roleId) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getRole_id,roleId);
userMapper.delete(queryWrapper);
}
}
方法deleteUserByRoleId
的传播性设置为 REQUIRES_NEW
,方法deleteRole
仍然是REQUIRED
,当deleteRole
调用deleteUserByRoleId
时,调用链如下:
开启事务1—>执行deleteRole
方法—>挂起事务1 —>开启事务2 —>执行deleteUserByRoleId
方法 —> 提交事务2 —>提交事务1
当方法deleteUserByRoleId
执行时抛出了异常,事务怎么处理
开启事务1—>执行deleteRole
方法—>挂起事务1 —>开启事务2 —>执行deleteUserByRoleId
方法抛出异常—> 回滚事务2 —>提交事务1
三、Spring事务失效的场景有哪些
3.1、方法的访问权限不是public类型
package com.duan.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.mapper.RoleMapper;
import com.duan.pojo.Role;
import com.duan.service.RoleService;
import com.duan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserService userService;
@Override
@Transactional()
private void deleteRole(Integer roleId) {
roleMapper.deleteById(roleId);
userService.deleteUserByRoleId(roleId);
}
}
因为Spring
事务是由AOP
机制实现的,AOP
机制的本质就是动态代理,而代理的事务方法不是public
的话,computeTransactionAttribute()
就会返回null
,事务属性也就不存在了。
3.2、service类没有被Spring管理
package com.duan.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.mapper.RoleMapper;
import com.duan.pojo.Role;
import com.duan.service.RoleService;
import com.duan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
#@Service(注释了@service注解)
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserService userService;
@Override
@Transactional
public void deleteRole(Integer roleId) {
roleMapper.deleteById(roleId);
userService.deleteUserByRoleId(roleId);
}
}
@Service
注解注释之后,Spring
事务(@Transactional)
没有生效,因为Spring
事务是由AOP
机制实现的。
也就是说从Spring IOC
容器获取bean
时,Spring
会为目标类创建代理,来支持事务的。但是@Service
被注释后,你的service
类都不是Spring
管理的,没办法创建代理类来支持事务。
3.3、事务方法被final、static关键字修饰
package com.duan.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.mapper.RoleMapper;
import com.duan.pojo.Role;
import com.duan.service.RoleService;
import com.duan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserService userService;
@Override
@Transactional
public final void deleteRole(Integer roleId) {
roleMapper.deleteById(roleId);
userService.deleteUserByRoleId(roleId);
}
}
如果一个方法被声明为final
或者static
,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring
无法生成事务代理对象来管理事务。
3.4、同一个类中,方法内部调用
package com.duan.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.mapper.RoleMapper;
import com.duan.pojo.Role;
import com.duan.service.RoleService;
import com.duan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserService userService;
@Override
@Transactional
public void deleteRole(Integer roleId) {
roleMapper.deleteById(roleId);
Role roleByRoleId = getRoleByRoleId(roleId);
userService.deleteUserByRoleId(roleId);
}
@Override
public Role getRoleByRoleId(Integer roleId) {
Role role = roleMapper.selectById(roleId);
return role;
}
}
事务是通过Spring AOP
代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用
3.5、数据库的存储引擎不支持事务
Spring
事务的底层,还是依赖于数据库本身的事务支持。在MySQL
中,MyISAM
存储引擎是不支持事务的,InnoDB
引擎才支持事务。因此开发阶段设计表的时候,确认你的选择的存储引擎是支持事务的
3.6、异常没有被抛出
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Transactional
public void update(Role role) {
try{
// update role
}catch{
}
}
}
这个方法把异常给捕获了,但没有抛出来,所以事务不会回滚,只有捕捉到异常事务才会生效。
3.7、异常类型不匹配
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Transactional
public void update(Role role) {
try{
// update role
}catch{
throw new Exception("更新失败");
}
}
}
Spring
默认回滚的是RuntimeException
异常,和上面程序抛出的 Exception
异常不匹配,所以事务也是不生效的。如果要触发默认RuntimeException
之外异常的回滚,则需要在@Transactiona
事务注解上指定异常类,示例如下:
@Transactional(rollbackFor = Exception.class)
以上场景是常见的Spring
事务失效的常见场景,如果在项目中出现@Transactional
事务不生效,可以根据具体情况排查一下,如:自身调用、异常不匹配、异常被捕获等。
代码地址:https://gitee.com/duan138/practice-code/tree/dev/transaction
三、总结
在日常开发时,Spring
事务也是非常重要的知识点,同时也是面试官着重会问的问题,特别是事务传播行为有哪些以及碰没碰到过事务失效的场景等等,所以这部分内容还是要花点时间好好准备准备。
下篇文章来学习学习项目中非常重要的知识点AOP
。
改变你能改变的,接受你不能改变的,关注公众号:程序员康康,一起成长,共同进步。