這幾天在作一個功能,具體的狀況是這樣的:java
項目中原有的幾個功能模塊中有數據上報的功能,如今須要在這幾個功能模塊的上報以後生成一條消息記錄,而後入庫,在寫個接口供前臺來拉取消息記錄。web
看到這個需求,首先想到的是使用AOP來實現了,而後,我去看了下現有功能模塊中的代碼,發現了問題,這些模塊中的業務邏輯並無放在service層來處理,直接在controller中處理了,controller中包含了兩個甚至多個service處理,這樣是不能保證事務安全的,既然這樣,那麼咱們如何實現能保證事務安全呢。我想直接在controller中定義切入點,而後before中手動開啓事務,在afterReturn以後根據須要來提交或者回滾事務。spring
而後趁着這個機會就查了下spring boot中的事務這塊,就從最基礎的提及。編程
1.spring boot中聲明式事務的使用json
想要在spring boot中使用聲明式事務,有兩種方式,一種是在各個service層中添加註解,還有一種是使用AOP配置全局的聲明式事務管理安全
先來講第一種,須要用到兩個註解就,一個是@EnableTransactionManagement用來開啓註解事務管理,等同於xml配置方式的 <tx:annotation-driven />,另外一個是@Transactionalapp
具體代碼以下:框架
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 }
實現類ide
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 同類方法調用)