zoukankan      html  css  js  c++  java
  • 【springboot】事务处理

    转自:

      https://blog.csdn.net/cp026la/article/details/86496788

    扯淡:

      复杂的业务逻辑中一个请求可能需要多次操作数据库,要保证一个Service 方法中多个 dao 的操作同时成功(失败),事务的配置就很重要了。

    大概分三种情况:

      1、分布式事务:即多模块中事务,分布式事务建议是可以避免就避免,可以使用消息中间件处理,但也不能完全解决。
      2、多线程事务:参考:https://blog.csdn.net/kongkongyanan/article/details/81703415
      3、单模块中的事务,本章的重点,也是开发中遇到的最多的,这里给出两种配置方式。

      本章分别使用注解 @Transactional (springboot默认推荐)和 AOP 全局配置的方式:

    pom 依赖(延续上一章代码)增加aop的依赖:

    <!-- aop -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    

    一、事务演示:

    需要引入spring-boot-starter-jdbc依赖,但是我们集成了mybatis,已经包含了该依赖,不必重复引入。
    1、controller层代码:在UserController中添加测试的方法

     @GetMapping
     @RequestMapping(value = "add/{number}")
     public ApiResult add(@PathVariable("number") Integer number){
          userService.addUser(number);
          return ApiResult.ok();
     }

    2、service层代码:在UserService中添加测试方法,设置一个可控的除以0异常

        public void addUser(int number){
            User user = new User();
            user.setId(22);
            user.setName("xiaoming");
            user.setAge(12);
            mapper.insertSelective(user);
    
            int num = 10/number;
    
            User user2 = new User();
            user2.setId(23);
            user2.setName("xiaohong");
            user2.setAge(90);
            mapper.insertSelective(user2);
        }  

    3、测试
    3.1、请求接口:http://localhost:8080/user/add/10 数据库正常插入两条数据

    3.2、删除刚插入的两条数据。请求接口:http://localhost:8080/user/add/0 制造除以0 异常。
    现象:数据库此时只插入第一条数据(xiaoming)。
    分析:插入第二条数据(xiaohong)之前,出现除以0异常,造成程序中断。

    需求: 这种在同一个逻辑中的多个dao操作要么同时成功、要么同时失败,不允许只成功一个dao操作的情况出现。

    二、使用@Transactional 配置事务:

    1、在service层addUser 方法上添加@Transactional注解

        @Transactional(rollbackFor = Exception.class)
        public void addUser(int number){
           ...
        }
    

    2、测试:删除数据库中已经成功插入的数据。
    2.1、请求接口:http://localhost:8080/user/add/0 制造除以0 异常。
    2.2、结果:出现除以0 异常,数据库并未插入任何数据,即实现了多个dao操作同时成功,同时失败。

    3、注意点:

    1、spring 事务默认回滚的是运行时异常(RuntimeException)和错误(Error),非检测异常,例如SQLException 不会回滚。加上rollbackFor = Exception.class 可解决。
    2、在service中使用了try catch 捕获了异常,事务不会回滚,因为try catch 异常之后就相当于没有异常,建议异常在controller中统一处理。

    三、使用 AOP 全局处理事务:

    需要引入 aop 的依赖。

    1、新建全局事务配置类(个人放置到config包中,方便管理):

    /**
     * spring aop 配置全局事务
     */
    @Aspect
    @Configuration
    public class TransactionAdviceConfig {
    
        /*
         * 定义切入点
         * execution()是最常用的切点函数
         * execution (com.coolron.user.service.impl..*.*(..))
         * 1、execution(): 表达式主体。
         * 2、第一个*号:表示返回类型,*号表示所有的类型。
         * 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
         * 4、第二个*号:表示类名,*号表示所有的类。
         * 5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
         */
        private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.coolron.user.service.impl..*.*(..))";
    
        @Autowired
        private PlatformTransactionManager transactionManager;
    
        @Bean
        public TransactionInterceptor txAdvice() {
    
            DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
            txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    
            DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
            txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            txAttr_REQUIRED_READONLY.setReadOnly(true);
    
            NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
    
            // service 中方法前缀
            source.addTransactionalMethod("add*", txAttr_REQUIRED);
            source.addTransactionalMethod("save*", txAttr_REQUIRED);
            source.addTransactionalMethod("delete*", txAttr_REQUIRED);
            source.addTransactionalMethod("update*", txAttr_REQUIRED);
    
            return new TransactionInterceptor(transactionManager, source);
        }
    
        @Bean
        public Advisor txAdviceAdvisor() {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
            return new DefaultPointcutAdvisor(pointcut, txAdvice());
        }
    
    }  

    2、测试:
    将 Service 中addUser 方法上的 @Transactional 注解注释,删除数据。
    2.1、请求接口:http://localhost:8080/user/add/10 正常操作,数据库插入两条数据。
    2.2、删除刚插入的两条数据。请求接口:http://localhost:8080/user/add/0 制造除以0 异常。
    结果:出现异常,数据库并未插入数据。

    3、注意点:

    1、正确配置切入点的位置,本章配置的是:
    execution(* com.coolron.user.service.impl….(…)),也可将user换成 * 指定多个位置
    2、service 层中方法名应严格按照全局事务配置类中定义的规则来命名。
    3、try catch 同样也不要在service中使用。

    至此,单模块中的事务配置已完成,也是平时开发中最遇到的,对于分布式事务(即多模块),尽量将涉及到事务的业务逻辑放到一个模块中处理,使用消息中间件处理也不是最佳方案。

  • 相关阅读:
    windows服务的默认启动类型和登录帐户
    oracle通过sql随机取表中的10条记录
    oracle如何四舍五入?
    Sql Server数据库自增长字段标识列的插入或更新修改操作办法
    将一个表中的数据导入到另一张表中
    设计模式已经陨落了?
    LINQ架构简单描述
    Asp.Net 验证控件
    .Net 三层架构开发初步
    C++编程思想
  • 原文地址:https://www.cnblogs.com/wjqhuaxia/p/12148865.html
Copyright © 2011-2022 走看看