这几天在作项目的时候遇到了spring事务管理引起的问题,同一段代码通过了不一样的服务层调用,缺出现了不同的结果,最后发现是spring事务管理引起的问题。在网上搜了一篇博客,里面对spring的事务机制作了解释,顿悟啊!!!留下此博客但愿和你们分享。原文以下:java
2016年01月11日 3:06 PMspring
我的认为, 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); } ... } |
那么若是配置了事务, 就至关于又建立了一个类:spa
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"); } |
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); } |
测试代码以下:code
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
方法没法添加事务管理.