Spring事务
Spring事务
1.事务的传播行为
1.1.Propagation.REQUIRED
定义:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值(default)
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@GetMapping("/test-required")
public void testRequired() {
Transaction transaction = new Transaction();
transaction.setName("zyf");
transaction.setAge(20);
try {
transactionMapper.insert(transaction);
// 在此处抛出异常,数据回滚
int i = 1 / 0;
} catch (MathArithmeticException e) {
e.printStackTrace();
}
}
1.2.Propagation.REQUIRES_NEW
定义:创建一个新的事务,如果当前存在事务,则把当前事务挂起(开启独立事务)
讲人话呢就是,不管外部事务是否存在,内部都会创建一个新事务并独立运行
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@GetMapping("/test-requires-new")
public void testRequiresNew() {
Transaction transaction = new Transaction();
transaction.setName("zyf");
transaction.setAge(20);
// 内部方法的调用是无法开启新的事务的,因为@Transactional是基于AOP实现的
AspectTest o = (AspectTest)AopContext.currentProxy();
o.independentTransaction();
// 如果是调用外部方法(非当前类),则可以直接进行调用并且事物生效(xxxService为注入bean)
xxxService.independentTransaction();
try {
int i = 1 / 0;
transactionMapper.insert(transaction);
} catch (MathArithmeticException e) {
e.printStackTrace();
}
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void independentTransaction() {
Transaction transaction = new Transaction();
transaction.setName("ls");
transaction.setAge(32);
try {
transactionMapper.insert(transaction);
} catch (MathArithmeticException e) {
e.printStackTrace();
}
}
1.3.Propagation.SUPPORTS
定义:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式运行
@Transactional(rollbackFor = Exception.class)
@GetMapping("/test-supports")
public void testSupports() {
Transaction transaction = new Transaction();
transaction.setName("supports");
transaction.setAge(20);
transactionMapper.insert(transaction);
outerService.testSupports();
try {
int res = ONE / ZERO;
System.out.println("一切正常...");
} catch (MathArithmeticException e) {
e.printStackTrace();
}
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public void testSupports() {
TRANSACTION.setName("supports - outer");
TRANSACTION.setAge(20);
transactionManager.insert(TRANSACTION);
try {
int res = ONE / ZERO;
System.out.println("内部一切正常...");
} catch (MathArithmeticException e) {
e.printStackTrace();
}
}
1.4.Propagation.NOT_SUPPORTED
定义:以非事务方式运行,如果当前存在事务,则把当前事务挂起
(讲人话呢就是,不管外部是否存在事务,内部都以非事务方式运行)
@Transactional(rollbackFor = Exception.class)
@GetMapping("/test-not-supported")
public void testSupports() {
Transaction transaction = new Transaction();
transaction.setName("not - support");
transaction.setAge(20);
transactionMapper.insert(transaction);
outerService.testNotSupported();
try {
int res = ONE / ZERO;
System.out.println("一切正常...");
} catch (MathArithmeticException e) {
e.printStackTrace();
}
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public void testNotSupported() {
TRANSACTION.setName("not_supported - outer");
TRANSACTION.setAge(90);
transactionManager.insert(TRANSACTION);
try {
int res = ONE / ZERO;
} catch (MathArithmeticException e) {
System.out.println("内部永远以非事务运行...");
e.printStackTrace();
}
}
1.5.Propagation.NEVER
定义:以非事务方式运行,如果当前存在事务,则抛出异常
@Transactional(rollbackFor = Exception.class)
@GetMapping("/test-never")
public void testSupports() {
Transaction transaction = new Transaction();
transaction.setName("never");
transaction.setAge(20);
transactionMapper.insert(transaction);
outerService.testNever();
try {
System.out.println("一切正常...");
} catch (MathArithmeticException e) {
e.printStackTrace();
}
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
public void testNever() {
TRANSACTION.setName("never - outer");
TRANSACTION.setAge(66);
transactionManager.insert(TRANSACTION);
try {
System.out.println("内部一切正常...");
} catch (MathArithmeticException e) {
System.out.println("内部永远以非事务运行...");
e.printStackTrace();
}
}
// 执行程序将抛出异常IllegalTransactionStateException
// Existing transaction found for transaction marked with propagation 'never'
1.6.Propagation.MANDATORY
定义:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常(好理解,不再举例说明)
1.7.Propagation.NESTED
定义:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED
2.事务失效的场景
2.1. 抛出检查异常
比如你的事务控制代码如下:
java复制代码@Transactional
public void transactionTest() throws IOException{
User user = new User();
UserService.insert(user);
throw new IOException();
}
如果@Transactional
没有特别指定,Spring 只会在遇到运行时异常RuntimeException或者error时进行回滚,而IOException
等检查异常
不会影响回滚
解决方案
知道原因后,解决方法也很简单。配置rollbackFor
属性,例如@Transactional(rollbackFor = Exception.class)
2.2 业务方法本身捕获了异常
@Transactional(rollbackFor = Exception.class)
public void transactionTest() {
try {
User user = new User();
UserService.insert(user);
int i = 1 / 0;
}catch (Exception e) {
e.printStackTrace();
}
}
这种场景下,事务失败的原因也很简单,Spring
是否进行回滚是根据你是否抛出异常决定的,所以如果你自己捕获了异常,Spring
也无能为力
解决方案
抛出异常throw new RuntimeException(e);
2.3 同一类中的方法调用
@Service
public class DefaultTransactionService implement Service {
public void saveUser() throws Exception {
//do something
doInsert();
}
@Transactional(rollbackFor = Exception.class)
public void doInsert() throws IOException {
User user = new User();
UserService.insert(user);
throw new IOException();
}
}
这也是一个容易出错的场景。事务失败的原因也很简单,因为Spring
的事务管理功能是通过动态代理实现的,而Spring
默认使用JDK
动态代理,而JDK
动态代理采用接口实现的方式,通过反射调用目标类。简单理解,就是saveUser()
方法中调用this.doInsert()
,这里的this
是被真实对象,所以会直接走doInsert
的业务逻辑,而不会走切面逻辑,所以事务失败
解决方案
可以把方法doInsert放到另外一个service或者dao,然后把这个server或者dao通过@Autowired注入到方法A的bean里面,这样即使方法A没用事务,方法B也可以执行自己的事务了
2.4 方法使用 final 或 static关键字
如果Spring
使用了Cglib
代理实现(比如你的代理类没有实现接口),而你的业务方法恰好使用了final
或者static
关键字,那么事务也会失败。更具体地说,它应该抛出异常,因为Cglib
使用字节码增强技术生成被代理类的子类并重写被代理类的方法来实现代理。如果被代理的方法的方法使用final
或static
关键字,则子类不能重写被代理的方法。
如果Spring
使用JDK
动态代理实现,JDK
动态代理是基于接口实现的,那么final
和static
修饰的方法也就无法被代理。
总而言之,方法连代理都没有,那么肯定无法实现事务回滚了。
解决方案
想办法去掉final或者static关键字
2.5 方法不是public
如果方法不是public
,Spring
事务也会失败,因为Spring
的事务管理源码AbstractFallbackTransactionAttributeSource
中有判断computeTransactionAttribute()。
如果目标方法不是公共的,则TransactionAttribute
返回null
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
解决方案
是将当前方法访问级别更改为public
2.6 错误使用传播机制
具体看以上的7种事物传播行为
2.7 没有被Spring管理
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
如果此时把 @Service
注解注释掉,这个类就不会被加载成一个 Bean
,那这个类就不会被 Spring
管理了,事务自然就失效了
解决方案
需要保证每个事务注解的每个Bean被Spring管理
2.8 多线程
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
try {
test();
} catch (Exception e) {
roleService.doOtherThing();
}
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
try {
int i = 1/0;
System.out.println("保存role表数据");
}catch (Exception e) {
throw new RuntimeException();
}
}
}
add()
方法中调用了 doOtherThing()
方法,而 doOtherThing()
方法在一个新的线程中执行。由于每个线程都拥有自己的数据库连接,因此这两个方法实际上是在不同的事务中执行的
这意味着如果 doOtherThing()
方法中抛出异常,它会导致该线程的事务回滚,但不会影响 add()
方法中的事务。因为它们不共享相同的数据库连接,所以它们也不共享相同的事务上下文
解决方案
(分布式事物)
如果希望在 doOtherThing()
方法中抛出异常时回滚 add()
方法中的事务,可以通过其他手段来实现,例如将这两个方法合并在同一个事务内执行,或者通过其他同步机制来确保两个方法的事务性一致性
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!