开发者

Spring 中的切面与事务结合使用完整示例

目录
  •  一、前置知识:Spring AOP 与 事务的关系
    •  事务本质上就是一个“切面”
  • 二、核心组件
    • 三、完整示例:用切面实现事务控制
      • 场景:
      • 1. 添加依赖(pom.XML)
      • 2. 创建实体类
      • 3. 创建 Service 方法(标记自定义注解)
      • 4. 创建自定义注解(用于标记需要事务的方法)
      • 5. 编写切面:手动控制事务
      • 6. 测试 Controller
    • 四、测试验证
      • 1. 启动应用
    • 五、关键点解析
      • 六、注意事项

         一、前置知识:Spring AOP 与 事务的关系

         事务本质上就是一个“切面”

        • @Transactional 注解底层就是通过 AOP + 动态代理 实现的。
        • 当你在一个方法上加了 @Transactional,Spring 会:
          1. 创建代理对象
          2. 在方法执行前开启事务
          3. 方法成功返回时提交事务
          4. 方法抛出异常时回滚事务

        二、核心组件

        组件作用
        PlatformTransactionManager事务管理器,负责开启、提交、回滚事务
        TransactionDefinition事务定义(隔离级别、传播行为等)
        TransactionStatus当前事务状态
        @ASPect定义切面
        @Around环绕通知,控制方法执行全过程

        三、完整示例:用切面实现事务控制

        场景:

        我们有一个 UserService,其中 createUserWithOrder 方法需要:

        1. 保存用户
        2. 保存订单
        3. 如果任一步失败,整个操作回滚

        我们将不用 @Transactional,而是用一个切面来控制事务。

        1. 添加依赖(pom.xml)

        <dependencies>
            <dependency>
                <groupId&g编程客栈t;org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </编程客栈dependency>
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <scope>runtime</scope>
            </dependency>
        </dependencies>

        2. 创建实体类

        // User.Java
        public class User {
            private Long id;
            private String name;
        }
        // Order.java
        public class Order {
            private Long id;
            private String productName;
            private Long userId;
        }

        3. 创建 Service 方法(标记自定义注解)

        @Service
        public class UserService {
            @Autowired
            private JdbcTemplate jdbcTemplate;
            // 自定义注解,表示此方法需要事务
            @TransactionalAspect
            public void createUserWithOrder(String userName, String productName) {
                // 1. 保存用户
                String userSql = "INSERT INTO users(name) VALUES(?)";
                jdbcTemplate.update(userSql, userName);
                // 模拟异常:如果 productName 是 "fail",则抛出异常
                if ("fail".equals(productName)) {
                    throw new RuntimeException("订单创建失败!");
                }
                // 2. 保存订单
                String orderSql = "INSERT INTO orders(product_name, user_id) VALUES(?, ?)";
                // 假设用户ID是自增,这里简化为查出来
                Long userId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM users", Long.class);
                jdbcTemplate.update(orderSql, productName, userId);
            }
        }

        4. 创建自定义注解(用于标记需要事务的方法)

        // TransactionalAspect.java
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface TransactionalAspect {
        }

        5. 编写切面:手动控制事务

        @Aspect
        @Component
        public class TransactionAspect {
            @Autowired
            private PlatformTransactionManager transactionManager;
            @Around("@annotation(transactionalAspect)")
            public Object manageTransaction(ProceedingJoinPoint joinPoint, TransactionalAspect transactionalAspect) throws Throwable {
                // 1. 定义事务属性
                DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 默认传播行为
                // 2. 开启事务
                TransactionStatus status = transactionManager.getTransaction(def);
                try {
                    // 3. 执行目标方法
                    Object result = joinPoint.proceed();
                    // 4. 方法成功执行,提交事务
                    transactionManager.commit(status);
                    System.out.println("事务提交成功");
                    return result;
                } catch (Exception e) {
                    // 5. 发生异常,回滚事务
                    if (status != null && !status.isCompleted()) {
                        transactionManager.rollback(status);
                        System.out.println("事务已回滚,原因: " + e.getMessage());
                    }
                    throw e; // 重新抛出异常
                }
            }
        }

        6. 测试 Controller

        @RestController
        pjsublic class TestController {
            @Autowired
            private UsjserService userService;
            // 测试:成功场景
            @GetMapping("/test/success")
            public String testSuccess() {
                userService.createUserWithOrder("张三", "iPhone");
                return "用户和订单创建成功!";
            }
            // 测试:失败场景(应回滚)
            @GetMapping("/test/fail")
            public String testFail() {
                try {
                    userService.createUserWithOrder("李四", "fail");
                } catch (Exception e) {
                    return "创建失败,但事务已回滚!";
                }
                return "success";
            }
        }

        四、测试验证

        1. 启动应用

        访问:

        • http://localhost:8080/test/success
          • 结果:用户和订单都插入
        • http://localhost:8080/test/fail
          • 结果:抛出异常,用户也不会被保存(事务回滚)

        ✅ 验证成功:即使用户先插入,但由于异常,整个事务回滚。

        五、关键点解析

        问题解释
        为什么不用 @Transactional演示 AOP 如何手动控制事务,理解底层原理
        PlatformTransactionManager 从哪来?Spring Boot 自动配置(如 DataSourceTransactionManager
        @Around 是关键它能控制方法执行前后,决定提交或回滚
        异常必须 re-throw否则上层不知道失败,可能掩盖问题
        status.isCompleted()防止重复提交或回滚

        六、注意事项

        1. 切面失效问题
          • 不要在同一个类中调用被切面拦截的方法(内部调用不走代理)
        2. 事务传播行为
          • 当前例子是 REQUjsIRED,更复杂的场景需考虑 REQUIRES_NEW
        3. 性能
          • 事务越短越好,避免在事务中做耗时操作(如远程调用)
        4. 只读事务
          • 查询方法应使用 @Transactional(readOnly = true)

        到此这篇关于Spring 中的切面与事务结合使用完整示例的文章就介绍到这了,更多相关Spring切面与事务结合内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

        0

        上一篇:

        下一篇:没有了

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        最新开发

        开发排行榜