• spring boot中的声明式事务管理及编程式事务管理


    这几天在做一个功能,具体的情况是这样的:

      项目中原有的几个功能模块中有数据上报的功能,现在需要在这几个功能模块的上报之后生成一条消息记录,然后入库,在写个接口供前台来拉取消息记录。

      看到这个需求,首先想到的是使用AOP来实现了,然后,我去看了下现有功能模块中的代码,发现了问题,这些模块中的业务逻辑并没有放在service层来处理,直接在controller中处理了,controller中包含了两个甚至多个service处理,这样是不能保证事务安全的,既然这样,那么我们如何实现能保证事务安全呢。我想直接在controller中定义切入点,然后before中手动开启事务,在afterReturn之后根据需要来提交或者回滚事务。

      然后趁着这个机会就查了下spring boot中的事务这块,就从最基础的说起。

      1.spring boot中声明式事务的使用

      想要在spring boot中使用声明式事务,有两种方式,一种是在各个service层中添加注解,还有一种是使用AOP配置全局的声明式事务管理

      先来说第一种,需要用到两个注解就,一个是@EnableTransactionManagement用来开启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />,另一个是@Transactional

      具体代码如下:

     1 package com.example.demo;
     2 
     3 import org.springframework.boot.SpringApplication;
     4 import org.springframework.boot.autoconfigure.SpringBootApplication;
     5 import org.springframework.transaction.annotation.EnableTransactionManagement;
     6 
     7 // @SpringBootApplication是Sprnig Boot项目的核心注解,主要目的是开启自动配置
    10 @SpringBootApplication
    11 @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
    12 public class DemoApplication {
    14     public static void main(String[] args) {
    16         SpringApplication.run(DemoApplication.class, args);
    18     }
    19 
    20 }

      然后,注解@Transactional直接加在service层就可以了,放两个service用来验证事务是否按预期回滚

    package com.example.demo.service;
    
    import com.example.demo.bean.ResUser;
    import org.springframework.transaction.annotation.Transactional;
    import java.util.List;
    
    /**
    * 注解加在接口上表名接口的所有方法都支持事务;
    * 如果加在方法上,则只有该方法支持事务
    * 可以根据需要在CUD操作上加注解
    **/
    @Transactional 
    public interface IUserService {
    
        int insertUser(ResUser resUser);
    
        int updateUser(ResUser resUser);
    
        List<ResUser> getResUserList();
    
    }
     1 package com.example.demo.service;
     2 
     3 import com.example.demo.bean.ResPartner;
     4 import org.springframework.transaction.annotation.Transactional;
     5 
     6 import java.util.List;
     7 import java.util.Map;
     8 
     9 @Transactional
    10 public interface IPartnerService {
    11 
    12     int add(ResPartner resPartner);
    13 
    14     int deleteByIds(String ids);
    15 
    16     int update(ResPartner resPartner);
    17 
    18     ResPartner queryById(int id);
    19 
    20     List<ResPartner> queryList(Map<String, Object> params);
    21 
    22 }

      实现类

     1 package com.example.demo.service.impl;
     2 
     3 import com.example.demo.bean.ResPartner;
     4 import com.example.demo.dao.PartnerMapperXml;
     5 import com.example.demo.service.IPartnerService;
     6 import org.slf4j.Logger;
     7 import org.slf4j.LoggerFactory;
     8 import org.springframework.beans.factory.annotation.Autowired;
     9 import org.springframework.stereotype.Component;
    10 
    11 import java.util.List;
    12 import java.util.Map;
    13 
    14 @Component("partnerService")
    15 public class PartnerServiceImpl implements IPartnerService {
    16 
    17     private Logger logger = LoggerFactory.getLogger(this.getClass());
    18     @Autowired
    19     private PartnerMapperXml partnerMapperXml;
    20 
    21     @Override
    22     public int add(ResPartner resPartner) {
    23         StringBuilder sbStr = new StringBuilder();
    24         sbStr.append("id = ").append(resPartner.getId())
    25                 .append(", name = ").append(resPartner.getName())
    26                 .append(", city = ").append(resPartner.getCity())
    27                 .append(", displayName = ").append(resPartner.getDisplayName());
    28         this.logger.info(sbStr.toString());
    29         return this.partnerMapperXml.add(resPartner);
    30     }
    31 }
     1 package com.example.demo.service.impl;
     2 
     3 import com.example.demo.bean.ResPartner;
     4 import com.example.demo.bean.ResUser;
     5 import com.example.demo.dao.PartnerMapperXml;
     6 import com.example.demo.dao.ResUserMapper;
     7 import com.example.demo.service.IUserService;
     8 import org.springframework.beans.factory.annotation.Autowired;
     9 import org.springframework.stereotype.Component;
    10 
    11 import java.util.List;
    12 
    13 @Component("userService")
    14 public class UserServiceImpl implements IUserService {
    15 
    16     @Autowired
    17     private ResUserMapper resUserMapper;
    18     @Autowired
    19     private PartnerMapperXml partnerMapperXml;
    20 
    21     @Override
    22     public int insertUser(ResUser resUser) {
    23 
    24         int i = resUserMapper.insert(resUser);
    25 //        ResPartner partner = new ResPartner();
    26 //        partner.setId(resUser.getId());
    27 //        partner.setName(resUser.getName());
    28 //        partner.setDisplayName(resUser.getLogin());
    29 //
    30 //        if (true) // 用来验证异常,使事务回滚
    31 //            throw new RuntimeException("xxxxxxxxxxxxxxx");
    32 //        int a = 1/0;
    33 //        partnerMapperXml.add(partner);
    34 
    35         return i;
    36     }
    37 
    38 }

      controller代码,JSONMsg是一个自定义类,就三个属性code,msg,data用来给前台返回数据。

      1 package com.example.demo.controllers;
      2 
      3 import com.alibaba.fastjson.JSONObject;
      4 import com.example.demo.bean.JSONMsg;
      5 import com.example.demo.bean.ResPartner;
      6 import com.example.demo.bean.ResUser;
      7 import com.example.demo.service.IPartnerService;
      8 import com.example.demo.service.IUserService;
      9 import org.springframework.beans.factory.annotation.Autowired;
     10 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
     11 import org.springframework.transaction.PlatformTransactionManager;
     12 import org.springframework.transaction.TransactionDefinition;
     13 import org.springframework.transaction.TransactionStatus;
     14 import org.springframework.web.bind.annotation.*;
     15 
     16 import java.util.List;
     17 
     18 @RestController
     19 @RequestMapping("/users")
     20 public class UserController {
     21 
     22     @Autowired
     23     private IUserService userService;
     24     @Autowired
     25     private IPartnerService partnerService;
     26     @Autowired
     27     private PlatformTransactionManager platformTransactionManager;
     28     @Autowired
     29     private TransactionDefinition transactionDefinition;
     30 
     31     @RequestMapping(value = "/insert", method = RequestMethod.POST)
     32     @ResponseBody
     33     public JSONMsg insertUser(@RequestBody String data){
     34 
     35         JSONMsg jsonMsg = new JSONMsg();
     36         jsonMsg.setCode(400);
     37         jsonMsg.setMsg("error");
     38         System.out.println(data);
     39         JSONObject jsonObject = JSONObject.parseObject(data);
     40         if (!jsonObject.containsKey("data")){
     41             return jsonMsg;
     42         }
     43 
     44         ResUser user = JSONObject.parseObject(jsonObject.get("data").toString(), ResUser.class);
     45         int i = userService.insertUser(user);
     46 
     47         System.out.println(i);
     48         if (i!=0){
     49             jsonMsg.setCode(200);
     50             jsonMsg.setMsg("成功");
     51             jsonMsg.setData(user);
     52         }
     53 
     54         return jsonMsg;
     55     }
     56 
     57 //    该方法中的代码用来验证手动控制事务时使用
     58 //    @RequestMapping(value = "/insert", method = RequestMethod.POST)
     59 //    @ResponseBody
     60 //    public JSONMsg insertUser(@RequestBody String data){
     61 //        TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
     62 //
     63 //        System.out.println(transactionStatus.isCompleted());
     64 //        System.out.println(transactionStatus.isRollbackOnly());
     65 //
     66 //
     67 //        JSONMsg jsonMsg = new JSONMsg();
     68 //        jsonMsg.setCode(400);
     69 //        jsonMsg.setMsg("error");
     70 //        System.out.println(data);
     71 //        try{
     72 //            JSONObject jsonObject = JSONObject.parseObject(data);
     73 //            if (!jsonObject.containsKey("data")){
     74 //                return jsonMsg;
     75 //            }
     76 //
     77 //            ResUser user = JSONObject.parseObject(jsonObject.get("data").toString(), ResUser.class);
     78 //            int i = userService.insertUser(user);
     79 //
     80 //            i= 1/0;
     81 //
     82 //            ResPartner partner = new ResPartner();
     83 //            partner.setId(user.getId());
     84 //            partner.setName(user.getName());
     85 //            partner.setDisplayName(user.getLogin());
     86 //            partnerService.add(partner);
     87 //
     88 //            if (i!=0){
     89 //                jsonMsg.setCode(200);
     90 //                jsonMsg.setMsg("成功");
     91 //                jsonMsg.setData(user);
     92 //            }
     93 //
     94 //            platformTransactionManager.commit(transactionStatus);
     95 //            System.out.println("提交事务");
     96 //        }catch (Exception e){
     97 //            e.printStackTrace();
     98 //            platformTransactionManager.rollback(transactionStatus);
     99 //            System.out.println("回滚事务");
    100 //        }finally {
    101 //
    102 //        }
    103 //        return jsonMsg;
    104 //    }
    105 }

      接下来说下spring boot中配置全局的声明式事务,定义一个configure类,具体代码如下

     1 package com.example.demo.configs;
     2 
     3 import org.aspectj.lang.annotation.Aspect;
     4 import org.springframework.aop.Advisor;
     5 import org.springframework.aop.aspectj.AspectJExpressionPointcut;
     6 import org.springframework.aop.support.DefaultPointcutAdvisor;
     7 import org.springframework.beans.factory.annotation.Autowired;
     8 import org.springframework.context.annotation.Bean;
     9 import org.springframework.context.annotation.Configuration;
    10 import org.springframework.transaction.PlatformTransactionManager;
    11 import org.springframework.transaction.TransactionDefinition;
    12 import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
    13 import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
    14 import org.springframework.transaction.interceptor.TransactionInterceptor;
    15 
    16 /**
    17  * @ClassName: GlobalTransactionAdviceConfig
    18  * @Description: AOP全局事务管理配置
    19  *
    20  * 声明式事务说明:
    21  *      1.如果将业务逻辑放到service层面来处理,则能够保证事务安全,即便使用了AOP来切入service方法也能保证事务安全;
    22  *      2.如果多个service在controller层做业务逻辑(本身就是错误的),则不能保证事务安全。
    23  * 对于2中的情况,应该尽量避免,因为本身就是错误的;
    24  * 这种情况在面向切面编程中也有可能碰到,如,因为必要切入点为controller(应尽量避免,原则应切service),切面程序跟controller业务逻辑不同,
    25  *   service不同,会导致事务混乱;
    26  *
    27  * 如果出现上述情况,则可以使用编程式事务管理(也就是手动控制事务)
    28  *      在controller逻辑开始之前手动开启/获取事务,然后在controller逻辑结束后再根据需要提交或者回滚事务;
    29  *      在AOP中也是如此,在before中手动开启/获取事务(这一步是必须的),在after中处理切面逻辑,然后根据需要提交或者回滚事务,如果由于异常需要回滚事务,记得修改返回信息
    30  *
    31  * @Author: 
    32  * @Date: 2019-08-01
    33  * @Version: V2.0
    34  **/
    35 
    36 @Aspect
    37 @Configuration
    38 public class GlobalTransactionAdviceConfig {
    39     private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.example.demo.service..*.*(..))";
    40 
    41     @Autowired
    42     private PlatformTransactionManager transactionManager;
    43 
    44     @Bean
    45     public TransactionInterceptor txAdvice() {
    46         DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
    47         txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    48 
    49         DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
    50         txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    51         txAttr_REQUIRED_READONLY.setReadOnly(true);
    52 
    53         NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
    54         source.addTransactionalMethod("add*", txAttr_REQUIRED);
    55         source.addTransactionalMethod("insert*", txAttr_REQUIRED);
    56         source.addTransactionalMethod("save*", txAttr_REQUIRED);
    57         source.addTransactionalMethod("create*", txAttr_REQUIRED);
    58         source.addTransactionalMethod("delete*", txAttr_REQUIRED);
    59         source.addTransactionalMethod("update*", txAttr_REQUIRED);
    60         source.addTransactionalMethod("exec*", txAttr_REQUIRED);
    61         source.addTransactionalMethod("set*", txAttr_REQUIRED);
    62         source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
    63         source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
    64         source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
    65         source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
    66         source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
    67         source.addTransactionalMethod("is*", txAttr_REQUIRED_READONLY);
    68         source.addTransactionalMethod("select*", txAttr_REQUIRED_READONLY);
    69         return new TransactionInterceptor(transactionManager, source);
    70     }
    71 
    72     @Bean
    73     public Advisor txAdviceAdvisor() {
    74         AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    75         pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
    76         return new DefaultPointcutAdvisor(pointcut, txAdvice());
    77     }
    78 }

      添加这个类,根据知己需要修改切入点,然后放到能被spring boot扫描到的包下即可,如果出现事务失败的情况,请查看下addTransactionalMethod是否配置正确,我当初就是用的insert*,而没有添加导致失败。

      2.切入点为controller时,如何使用编程式事务管理控制事务

      

     1 package com.example.demo.configs;
     2 
     3 import com.example.demo.bean.JSONMsg;
     4 import com.example.demo.bean.ResPartner;
     5 import com.example.demo.bean.ResUser;
     6 import com.example.demo.service.IPartnerService;
     7 import org.aspectj.lang.JoinPoint;
     8 import org.aspectj.lang.annotation.AfterReturning;
     9 import org.aspectj.lang.annotation.Aspect;
    10 import org.aspectj.lang.annotation.Before;
    11 import org.aspectj.lang.annotation.Pointcut;
    12 import org.springframework.beans.factory.annotation.Autowired;
    13 import org.springframework.stereotype.Component;
    14 import org.springframework.transaction.PlatformTransactionManager;
    15 import org.springframework.transaction.TransactionDefinition;
    16 import org.springframework.transaction.TransactionStatus;
    17 import org.springframework.transaction.annotation.Transactional;
    18 
    19 @Component
    20 @Aspect
    21 public class ResUserAspect {
    22 
    23     @Autowired
    24     private IPartnerService partnerService;
    25     @Autowired
    26     private PlatformTransactionManager platformTransactionManager;
    27     @Autowired
    28     private TransactionDefinition transactionDefinition;
    29 
    30     private TransactionStatus transactionStatus;
    31 
    32     @Pointcut("execution(public * com.example.demo.controllers.UserController.insertUser(..))")
    33 //    @Pointcut("execution(public * com.example.demo.service.IUserService.insertUser(..))") // 验证切入点为service时,AOP编程中的事务问题
    34     private void insertUser(){}
    35 
    36     @Before(value = "insertUser()")
    37     public void before(){
    38         //在切入点程序执行之前手动开启事务 - 必须的操作
    39         transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
    40     }
    41     // 验证切入点为service时,AOP编程中的事务问题
    42 //    @AfterReturning(pointcut = "insertUser()", returning = "result") 
    43 //    public void afterReturning(JoinPoint joinPoint, Object result){
    44 //
    45 //        Object[] args = joinPoint.getArgs();
    46 //        System.out.println(args[0]);
    47 //        if (((Integer)result) != 0){
    48 //            ResPartner partner = new ResPartner();
    49 //            ResUser user = (ResUser) args[0];
    50 //            partner.setId(user.getId());
    51 //            partner.setName(user.getName());
    52 //            partner.setDisplayName(user.getLogin());
    53 //
    54 //            int a = 1/0;
    55 //            int i = partnerService.add(partner);
    56 //
    57 //            System.out.println(i);
    58 //        }
    59 //    }
    60    //切入点为controller时的事务验证
    61     @Transactional
    62     @AfterReturning(pointcut = "insertUser()", returning = "result")
    63     public void afterReturning(JoinPoint joinPoint, Object result){
    64 
    65         if (!(result instanceof JSONMsg)){
    66             System.out.println(result.getClass());
    67             return;
    68         }
    69         JSONMsg jsonMsg = (JSONMsg) result;
    70         try{
    71 
    72             if (jsonMsg.getCode() == 200){
    73                 ResPartner partner = new ResPartner();
    74                 ResUser user = (ResUser) jsonMsg.getData();
    75                 partner.setId(user.getId());
    76                 partner.setName(user.getName());
    77                 partner.setDisplayName(user.getLogin());
    78 
    79                 int a = 1/0;
    80                 int i = partnerService.add(partner);
    81 
    82                 System.out.println(i);
    83             }
    84 
    85             platformTransactionManager.commit(transactionStatus);  // 手动提交事务
    86             System.out.println("提交事务");
    87         }catch (Exception e){
    88             platformTransactionManager.rollback(transactionStatus); // 出现异常,回滚事务
    89             System.out.println("回滚事务");
    90             System.out.println(e.getMessage());
    91 
    92             //修改返回数据
    93             jsonMsg.setCode(400);
    94             jsonMsg.setMsg(e.getMessage());
    95         }
    96 
    97     }
    98 }

      用到的实体bean

     1 // ResUser.java中的属性
     2 private Integer id;
     3 private String login;
     4 private String name;
     5 private Integer age;
     6 
     7 // ResPartner.java中的属性
     8 private int id;
     9 private String name;
    10 private String city;
    11 private String displayName;

      最后总结我写在了GlobalTransactionAdviceConfig 类中,也就是如下

    * 声明式事务说明:
    * 1.如果将业务逻辑放到service层面来处理,则能够保证事务安全,即便使用了AOP来切入service方法也能保证事务安全;
    * 2.如果多个service在controller层做业务逻辑(本身就是错误的),则不能保证事务安全。
    * 对于2中的情况,应该尽量避免;
    * 这种情况在面向切面编程中也有可能碰到,如,因为必要切入点为controller(应尽量避免,原则应切service),切面程序跟controller业务逻辑不同,
    * service不同,会导致事务混乱;
    *
    * 如果出现上述情况,则可以使用编程式事务管理(也就是手动控制事务)
    * 在controller逻辑开始之前手动开启/获取事务,然后在controller逻辑结束后再根据需要提交或者回滚事务;
    * 在AOP中也是如此,在before中手动开启/获取事务(这一步是必须的),在after中处理切面逻辑,然后根据需要提交或者回滚事务,如果由于异常需要回滚事务,记得修改返回信息

      

      注:

        有时候项目中使用了分布式框架,比如dubbo,则可能存在service层跟controller层分布式部署的问题,这会导致这种方式在controller中获取不到transactionManager,后续有时间在来看下分布式中的事务处理问题。

    参考链接:

      Spring Boot 之 事务(声明式、编程式、自定义事务管理器、@EnableAspectJAutoProxy 同类方法调用)

      

      

      

      

  • 相关阅读:
    【Web】JavaScript 语法入门
    tar 和gzip 的区别
    状态码,好记
    PyCharm与git/GitHub取消关联
    在Ubuntu下安装deb包需要使用dpkg命令
    linux每日命令(4):解压命令
    Python之os.path.join()
    Python的JAVA胶水——jpype
    python之chardet验证编码格式
    python之arrow时间处理模块
  • 原文地址:https://www.cnblogs.com/edi-kai/p/11289701.html
Copyright © 2020-2023  润新知