
一、理论
**概念:**事务是一组操作的集合,会把所有的操作作为一个整体一起向系统提交或者撤销操作,要么同时成功、要么同时失败。
- 开启事务:(一组操作开始前,开启事务)
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。
改变你能改变的,接受你不能改变的,关注公众号:程序员康康,一起成长,共同进步。

5147

被折叠的 条评论
为什么被折叠?



