spring boot中的声明式事务管理及编程式事务管理
本文内容纲要: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 同类方法调用)
本文内容总结:spring boot中的声明式事务管理及编程式事务管理
原文链接:https://www.cnblogs.com/edi-kai/p/11289701.html
以上是 spring boot中的声明式事务管理及编程式事务管理 的全部内容, 来源链接: utcz.com/z/362776.html