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 同類方法調用)

  

  

  

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM