这几天在做一个功能,具体的情况是这样的:
项目中原有的几个功能模块中有数据上报的功能,现在需要在这几个功能模块的上报之后生成一条消息记录,然后入库,在写个接口供前台来拉取消息记录。
看到这个需求,首先想到的是使用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 同类方法调用)