`

Spring事务管理失效的原因

 
阅读更多

Spring事务管理失效的原因

2016年01月11日 3:06 PM

个人认为, 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继承时, 不能重写privatefinalstatic修饰的方法. 所以, 所有的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方法外面是无法调用的, 就暂时不去讨论了. 我们直接看testSaveErrorFinaltestSaveErrorStatic方法的运行结果:
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的事务支持. 因为它的调用堆栈如下图所示(从下向上):
saveByCallMethod的调用堆栈
最终结果就是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();
}

测试代码请参见我提供的完整代码. 这样的代码已经有可能了吧? 那么事务管理会生效吗? 我们再看看调用堆栈就知道了. saveByThread的调用堆栈
结果和我们想的一样, spring的事务管理并没有生效.

总结

好了, 现在我们来回顾一下, 在那些情况下spring的事务管理会失效:

  • private方法无法添加事务管理.
  • final方法无法添加事务管理.
  • static方法无法添加事务管理.
  • 当绕过代理对象, 直接调用添加事务管理的方法时, 事务管理将无法生效.
分享到:
评论

相关推荐

    Spring事务管理失效原因汇总

    标题“Spring事务管理失效原因汇总”指出了本文的核心内容是分析在使用Spring框架进行事务管理时可能遇到的问题及其原因。描述部分进一步说明了事务失效的后果往往不明显,容易在测试环节被忽略,但在生产环境中出现...

    Spring事务管理Demo

    在压缩包中的Spring事务管理练习,你可以尝试创建一个简单的示例,例如模拟两个银行账户转账的过程,通过开启事务确保转账的原子性,即转账操作要么全部成功,要么全部失败。这样可以帮助你更好地理解Spring事务管理...

    spring 事务管理的理解

    Spring 框架是Java开发中...理解并熟练掌握Spring事务管理,对于提升应用程序的稳定性和可靠性至关重要。在实际开发中,结合声明式事务管理、事务传播行为、隔离级别和回滚规则,可以有效地确保数据的完整性和一致性。

    spring事务管理5种方法

    本篇文章将深入探讨Spring事务管理的五种方法,旨在帮助开发者更好地理解和运用这一核心特性。 首先,我们来了解什么是事务。在数据库操作中,事务是一组逻辑操作,这些操作要么全部成功,要么全部失败,确保数据的...

    spring事务管理

    ### Spring事务管理详解 #### 一、事务管理基础概念 在深入探讨Spring事务管理之前,我们需要先理解什么是事务。事务可以被定义为一系列的操作集合,这些操作作为一个整体被提交或回滚。简单来说,事务就是一个不...

    Synchronized锁在Spring事务管理下线程不安全

    Synchronized锁在Spring事务管理下,导致线程不安全。

    Spring的事务管理小案例

    在本文中,我们将深入探讨Spring框架中的事务管理。Spring是一个广泛应用的Java企业级应用开发框架,它提供...如果你想要深入了解,可以参考提供的博客链接或其他相关资料,进一步学习Spring事务管理的细节和最佳实践。

    spring3.0两种事务管理配置

    Spring 3.0 提供了两种事务管理配置方法:基于 XML 的事务管理和基于 @Transactional 的事务管理,这两种方法都是为了实现事务管理的目标,分别具有不同的配置方式和优缺点。 基于 XML 的事务管理 这种方法不需要...

    详细介绍Spring事务管理

    ### Spring事务管理详解 #### 一、Spring事务管理的重要性及必要性 在现代软件开发中,事务管理是一项至关重要的技术,特别是在涉及数据库操作时。事务能够确保一系列操作要么全部成功,要么全部失败,这对于保持...

    Spring事务原理、Spring事务配置的五种方式

    Spring事务原理是指Spring框架中的一种机制,用于管理事务,并提供了多种配置方式。事务是指一系列的操作,作为一个整体执行,如果其中某个操作失败,整个事务将回滚。Spring事务原理围绕着两个核心:...

    Spring事务处理-ThreadLocal的使用

    首先,了解Spring事务管理的基本概念。在多线程环境中,事务管理是至关重要的,它负责确保一组数据库操作要么全部成功,要么全部失败。Spring提供了两种主要的事务管理方式:编程式事务管理和声明式事务管理。声明式...

    spring事务管理.rar

    Spring事务管理是Spring框架的核心特性之一,它提供了一种强大且灵活的方式来管理应用程序中的事务边界。在企业级Java应用中,事务处理是确保数据一致性、完整性和可靠性的关键部分。本篇文章将深入探讨Spring的事务...

    SPRING事务机制DEMO

    Spring事务管理提供了一种声明式的方式来控制事务边界,使得开发者无需显式地调用开始、提交或回滚事务。 在Spring中,有以下两种事务管理方式: 1. **编程式事务管理**:开发者需要手动调用`...

    spring 注解事务管理

    Spring事务管理主要分为两种方式:编程式事务管理和声明式事务管理。编程式事务管理是通过编写代码来控制事务的开始、提交、回滚等操作,而声明式事务管理则是通过配置或注解来定义事务边界,更加直观和易于使用。 ...

    Spring事务管理A方法内部调用B方法的回滚问题测试代码

    在Spring框架中,事务管理是核心特性之一,用于确保数据操作的一致性和完整性。当一个方法(A方法)内部调用另一个方法(B方法)时,可能会遇到事务控制...这个示例代码对于理解和调试Spring事务管理的问题非常有帮助。

    Spring事务详解

    Spring事务管理提供了两种主要的模式:编程式事务管理和声明式事务管理。编程式事务管理通过TransactionTemplate或PlatformTransactionManager接口直接控制事务的开始、提交、回滚等操作,适用于对事务控制有特殊...

    Spring事务管理配置文件问题排查

    当出现像描述中那样的问题——SQL语句执行出错但事务未回滚时,我们需要深入理解Spring事务管理的配置和机制。以下是一些关键知识点: 1. **Spring事务管理类型**: - **编程式事务管理**:通过`...

    Spring事务优缺点及使用详解.docx

    Spring事务管理提供了统一的事务处理模型,使得开发者无需关注具体数据库访问技术的事务细节,简化了事务控制代码,提高了代码的可读性和可维护性。无论是使用注解还是AOP配置,都能有效地管理和协调事务,确保应用...

    Spring2.5实现事务管理(本地事务、分布式事务).doc

    事务管理是指在多个操作中维持一致性的机制,它可以确保在多个操作中,如果某个操作失败,则整个事务回滚,保证数据的一致性。 在 Spring 2.5 中,事务管理可以分为两种:本地事务和分布式事务。本地事务是指在同一...

    spring 事务基于注解模式

    Spring事务管理分为编程式和声明式两种。编程式事务管理通过编程的方式(如使用`TransactionTemplate`或直接调用`PlatformTransactionManager`)来控制事务的开始、提交、回滚等操作。而声明式事务管理则是在配置...

Global site tag (gtag.js) - Google Analytics