个人认为, spring的声明式事务是spring让人感觉用的最爽的功能之一. 可是在有些时候, 我们使用spring的声明式事务时却并没有效果. 是spring的问题吗? 下面我们先大致说明一下spring声明式事务的原理, 然后再分析在什么情况下, spring的声明式事务会失效.
代理模式
我们知道, spring的声明式事务是基于代理模式的. 那么说事务之前我们还是大致的介绍一下代理模式吧. 其实代理模式相当简单, 就是将另一个类包裹在我们的类外面, 在调用我们创建的方法之前, 先经过外面的方法, 进行一些处理, 返回之前, 再进行一些操作. 比如:
1
2
3
4
5
6
7
|
public class UserService{
...
public User getUserByName(String name) {
return userDao.getUserByName(name);
}
...
}
|
那么如果配置了事务, 就相当于又创建了一个类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class UserServiceProxy extends UserService{
private UserService userService;
...
public User getUserByName(String name){
User user = null;
try{
// 在这里开启事务
user = userService.getUserByName(name);
// 在这里提交事务
}
catch(Exception e){
// 在这里回滚事务
// 这块应该需要向外抛异常, 否则我们就无法获取异常信息了.
// 至于方法声明没有添加异常声明, 是因为覆写方法, 异常必须和父类声明的异常"兼容".
// 这块应该是利用的java虚拟机并不区分普通异常和运行时异常的特点.
throw e;
}
return user;
}
...
}
|
然后我们使用的是UserServiceProxy
类, 所以就可以"免费"得到事务的支持:
1
2
3
4
5
6
7
|
@Autowired
private UserService userService; // 这里spring注入的实际上是UserServiceProxy的对象
private void test(){
// 由于userService是UserServiceProxy的对象, 所以拥有了事务管理的能力
userService.getUserByName("aa");
}
|
哪些情况下spring的事务管理会失效
private
方法, final
方法 和 static
方法不能添加事务
上面的东西并不难. 那么我们可以从上面知道些什么呢? 首先, 由于java继承时, 不能重写private
, final
, static
修饰的方法. 所以, 所有的private
方法, final
方法 和 static
方法 都无法直接添加spring的事务管理功能. 比如下面的代码(完整代码点击这里下载):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* 保存两个user对象. 添加了spring事务注解
*/
@Transactional
public final void saveErrorFinal(User user1, User user2) {
UserDao userDao = getUserDao(); // 此处需要使用getUserDao方法. 不能直接使用userDao
userDao.save(user1);
System.out.println(10 / 0); // 引发异常
userDao.save(user2);
}
/**
* 静态方法. 添加了spring事务注解
*/
@Transactional
public static void saveErrorStatic(UserDao userDao, User user1, User user2) {
userDao.save(user1);
System.out.println(10 / 0); // 引发异常
userDao.save(user2);
}
/**
* 私有方法保存方法. 添加了spring事务注解
*/
@Transactional
private void saveErrorPrivate(User user1, User user2) {
userDao.save(user1);
System.out.println(10 / 0); // 引发异常
userDao.save(user2);
}
|
测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
/**
* 检验user1是否插入成功, 并尝试删除数据. 如果没有插入, 则报错, 并退出.
*/
private void validateInsertSuccess() {
User user = userService.getUserByName(user1.getName());
userService.deleteByName(user1.getName()); // 删除用户数据
assertNotEquals("插入失败!", -1, user.getId().intValue());
}
/**
* 检验user1是否插入失败. 如果插入成功, 则删除数据, 并报错.
*/
private void validateInsertFail() {
User user = userService.getUserByName(user1.getName());
userService.deleteByName(user1.getName()); // 删除用户数据
assertEquals("插入成功, 事务没有生效!" + user, -1, user.getId().intValue());
}
@Test
public void testSaveErrorFinal() {
try {
userService.saveErrorFinal(user1, user2);
}
catch (ArithmeticException e) {} // 除零异常, 直接忽略
validateInsertFail(); // 我们假设有事务支持, user1添加失败
}
@Test
public void testSaveErrorStatic() {
try {
UserService.saveErrorStatic(userDao, user1, user2);
}
catch (ArithmeticException e) {}
validateInsertFail(); // 我们假设有事务支持, user1添加失败
}
|
由于saveErrorPrivate
方法外面是无法调用的, 就暂时不去讨论了. 我们直接看testSaveErrorFinal
和testSaveErrorStatic
方法的运行结果:
很明显, 事务并没有生效. 也就是说private
方法, final
方法 和 static
方法都没有事务支持.
没有通过代理对象调用添加事务的方法
仔细看看代理模式中的代码, 就会发现不通过代理对象调用方法也会导致spring事务管理失效. 绕过代理对象最直接的方法就是自己new
一个对象, 虽然这种可能性非常小:
1
|
new UserService().save(user);
|
当然, 前面也说了, 这种可能性非常小. 那么我们看看第二种情况, 这种情况的可能性也不大:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/**
* 保存两个user对象, 中间产生异常. 验证spring的事务是否可以正常工作
*/
@Transactional
public void saveError(User user1, User user2) {
userDao.save(user1);
System.out.println(10 / 0); // 引发异常
userDao.save(user2);
}
/**
* 通过{@link #saveError(User, User)}保存数据, 该方法本身并没有添加事务注解.
* 而是通过{@link #saveError(User, User)}方法使用事务
*/
public void saveByCallMethod(User user1, User user2) {
//saveErrorPrivate(user1, user2); // 或者调用saveErrorPrivate方法
saveError(user1, user2);
}
|
由于测试的代码基本上和上面一样, 所以这里我们就不贴测试的代码了. 再说一次, 点击这里下载完整代码). 实际上, 上面的saveByCallMethod
方法还是无法获得spring的事务支持. 因为它的调用堆栈如下图所示(从下向上):
最终结果就是spring的事务管理没有生效. 这是或许你会想了, 那为啥不直接给saveByCallMethod
方法添加事务支持呢? 对啊, 所以我说这种情况的可能性也不大. 下面我们再看看事务管理和多线程缠在一起时的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/**
* 通过创建一个新的线程, 调用{@link #saveError(User, User)}方法来保存用户, 该方法和
* {@link #saveError(User, User)}方法都添加了事务注解
*/
@Transactional
public void saveByThread(User user1, User user2) {
new Thread(() -> {
try {
Thread.sleep(1000); //耗时操作
saveError(user1, user2);
}
catch (Exception e) {
e.printStackTrace();
}
System.out.println("保存完成");
}).start();
}
|
测试代码请参见我提供的完整代码. 这样的代码已经有可能了吧? 那么事务管理会生效吗? 我们再看看调用堆栈就知道了.
结果和我们想的一样, spring的事务管理并没有生效.
总结
好了, 现在我们来回顾一下, 在那些情况下spring的事务管理会失效:
-
private
方法无法添加事务管理.
-
final
方法无法添加事务管理.
-
static
方法无法添加事务管理.
- 当绕过代理对象, 直接调用添加事务管理的方法时, 事务管理将无法生效.
相关推荐
Spring声明式事务管理失效的原因,这里面是日常工作中踩过的坑,事务失效所产生的影响比较隐蔽,测试时易忽略,大多是在线上暴露,这可能是对Spring管理下的信任吧
Spring事务失效Spring事务失效
Synchronized锁在Spring事务管理下,导致线程不安全。
Spring综合实例 spring配置oracel数据库 事务管理 切面 业务描述:实现用户简单的注册登录功能。 用户注册时候需要输入用户名,密码,邮箱地址,点击注册后成功。 用户登录时可以选择使用用户名或者邮箱地址进行...
需要使用Spring切面,同时需要保证事务正常。可使用文档中的方法。一般情况,若不做特殊处理。使用切面,事务将会失效。
本资源讲解的是Spring事务的相关技术,需要可自行下载。 课程内容: 1.实战了解学习源码应有的姿势? 2.埋坑,从深坑中玩转事务; 3.迷一般的事务?我数据验证为啥失效? 4.透过源码学习事务隔离级别; 5.源码解读,...
什么是事务 是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;...在Spring中,事务就是将一组业务当做一个业务来执行,要么全部成功,要么全部失败,不能破坏数据的完整性。
详细整理Spring事务失效的具体场景及解决方案.docx
主要介绍了Spring+SpringMVC配置事务管理无效原因及解决办法详解,具有一定借鉴价值,需要的朋友可以参考下
spring事务_案例_PPT 一、事务传播机制的demo案例,内容包括: 1.业务代码列举7种事务传播机制的情况,每个测试方法都附带日志分析记录、使用场景和实际场景,小白也能看得懂!!! 2.在测试类Test包下,使用juniter...
Spring事务不生效,spring项目中常见事务不生效总结
1.1 Spring事务处理时自我调用的解决方案及一些实现方式的风险 1.2 我对AOP的理解 1.3 Spring开闭原则的表现-BeanPostProcessor的扩展点-1 1.4 我对IoC/DI的理解 1.5 SpringMVC + spring3.1.1 + hibernate4.1.0 集成...
Spring的事务10种常见失效场景总结.zip
主要介绍了Spring事务失效问题分析及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
事务失效、事务回滚、大事务问题、编程式事务
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。 隔离性(Isolation):可能有许多事务会同时...
一旦事务完成(不管成功还是失败),系统必须确保涉及的数据处于一致的状态,而不会是部分完成部分失败。数据不应该被破坏。 隔离性(Isolation) 并发环境下,一个事务的执行不能不被其他事务干扰。不同的事务并发...
4.1 spring事务入门---一个转账的例子 1 事务 事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。 1.1 事务的特性 原子性:强调事务的不可分割。 一致性:事务的执行的前后数据的完整性...
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务 一个典型的分布式事务过程:分布式事务处理过程的-ID+三组件模型 Transaction ID XID :全局唯一的事务ID ...
用springboot 集成mybatis多数据源,用aop实现的动态切换,支持事务,不会使aop动态切换失效。注意:此代码不涉及分布式事务,如果需要分布式事务 需要采取其他方案。