个人认为, 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){ throw e; } return user; } ... }
|
然后我们使用的是UserServiceProxy
类, 所以就可以”免费”得到事务的支持:
1 2 3 4 5 6 7
| @Autowired private UserService userService;
private void test(){ userService.getUserByName("aa"); }
|
哪些情况下spring的事务管理会失效
private
方法, final
方法 和 static
方法不能添加事务
上面的东西并不难. 那么我们可以从上面知道些什么呢?
首先, 由于java继承时, 不能重写private
, final
, static
修饰的方法. 所以, 所有的private
方法, final
方法 和 static
方法
都无法直接添加spring的事务管理功能. 比如下面的代码(完整代码点击[这里][/downloads/code/2016/01/spring-transaction.zip]下载):
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
|
@Transactional public final void saveErrorFinal(User user1, User user2) { UserDao userDao = getUserDao(); userDao.save(user1); System.out.println(10 / 0); userDao.save(user2); }
@Transactional public static void saveErrorStatic(UserDao userDao, User user1, User user2) { userDao.save(user1); System.out.println(10 / 0); userDao.save(user2); }
@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
|
private void validateInsertSuccess() { User user = userService.getUserByName(user1.getName()); userService.deleteByName(user1.getName()); assertNotEquals("插入失败!", -1, user.getId().intValue()); }
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(); }
@Test public void testSaveErrorStatic() { try { UserService.saveErrorStatic(userDao, user1, user2); } catch (ArithmeticException e) {} validateInsertFail(); }
|
由于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
|
@Transactional public void saveError(User user1, User user2) { userDao.save(user1); System.out.println(10 / 0); userDao.save(user2); }
public void saveByCallMethod(User user1, User user2) { saveError(user1, user2); }
|
由于测试的代码基本上和上面一样, 所以这里我们就不贴测试的代码了. 再说一次, 点击这里下载完整代码).
实际上, 上面的saveByCallMethod
方法还是无法获得spring的事务支持. 因为它的调用堆栈如下图所示(从下向上):
最终结果就是spring的事务管理没有生效. 这是或许你会想了, 那为啥不直接给saveByCallMethod
方法添加事务支持呢? 所以我说这种情况的可能性也不大.
下面我们再看看事务管理和多线程缠在一起时的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@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
方法无法添加事务管理.
- 当绕过代理对象, 直接调用添加事务管理的方法时, 事务管理将无法生效.