Spring 事务失效场景

1. 方法是 private 私有的

Spring AOP 只拦截 public 方法,所以 private 方法 AOP 并不会拦截

解决方案

方案1:方法改为 public

方案2:事务的 AOP 的实现改为 AspectJ,AspectJ 可以拦截私有方法、构造方法、字段访问等

Spring 的 AOP 改为 AspectJ 和 事务管理器使用 AspectJ 这是两个不同的配置,下面是事务管理器使用 AspectJ 的示例

  1. 引入依赖

    <!-- AspectJ 事务支持 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>
    
  2. 配置事务管理器的代理模式为 AspectJ

    @Configuration
    @EnableTransactionManagement(mode = AdviceMode.ASPECTJ) // 关键!
    public class TransactionConfig {
    
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
  3. 阿斯蒂芬

    @Service
    public class UserService {
    
        @Transactional // 即使是 private 方法,AspectJ 也能生效!
        private void updatePrivate() {
            // 数据库操作
        }
    
        public void doSomething() {
            updatePrivate(); // 事务生效
        }
    }
    
  4. 配置织入方式(加载时织入 和 编译时织入 两种方式,这里是通过 maven 插件的编译时织入)

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.14.0</version>
        <configuration>
            <complianceLevel>11</complianceLevel>
            <source>11</source>
            <target>11</target>
            <showWeaveInfo>true</showWeaveInfo>
            <Xlint>ignore</Xlint>
            <encoding>UTF-8</encoding>
        </configuration>
        <executions>
            <execution>
                <goals>
                    <goal>compile</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    

2. 方法被 final 或 static 修饰

JDK 不允许子类重写,所以 AOP 会失效

3. 自调用(调用本类方法)

如下示例,事务会失效

public class UserService {
    public void updateUser() {
        this.updateUserStatus(); // 调用本来中带事务的方法
    }
    
    @Transactional
    public void updateUserStatus() {
        // ...
    }
}

// Controller
userService.updateUser(); // 事务失效

因为事务是通过 AOP 实现的,使用的时候注入的 UserService 其实是代理对象

代理对象只会重写 updateUserStatus() 方法,不会重写 updateUser() 方法

controller 中调用 updateUser() 方法,会调用目标对象的方法(回到了目标对象),所以 updateUserStatus() 也会是目标对象的方法

解决方案

方案1:自我注入,不直接调用本类的方法,但这会形成循环依赖

public class UserService {

		@Resource
		private UserService self; // 自我注入
		
    public void updateUser() {
        self.updateUserStatus(); // 调用注入的 UserService 的方法
    }
    
    @Transactional
    public void updateUserStatus() {
        // ...
    }
}

方案2:AopContext.currentProxy() 获取代理对象

方案3:方法抽出来,抽到另一个类中,不要直接调用本类的方法

4. 异常处理不当

  • 捕获异常未重新抛出
  • 没有指定 rollBackFor,默认就是 RuntimeException,抛出的异常不是 RuntimeException 或其子类
@Transactional
public void update() {
    try {
        // 数据库操作
    } catch (Exception e) {
        // 捕获异常未抛出,事务不会回滚
    }
}

解决方案:确保异常是 RuntimeException 类型(或者配置 rollBackFor 指定异常),如果捕获了一场,要抛出来

5. 数据库不支持事务

这个比较少见,也算一种场景,比如 Mysql 的 MyISAM 存储引擎就不支持事务

6. 事务传播行为配置不当

比如使用 Propagation.NEVER 传播方式

7. 事务超时时间不够

posted @ 2024-11-02 20:41  CyrusHuang  阅读(31)  评论(0)    收藏  举报