springboot中的声明式事务管理及编程式事务管理
这⼏天在做⼀个功能,具体的情况是这样的:
项⽬中原有的⼏个功能模块中有数据上报的功能,现在需要在这⼏个功能模块的上报之后⽣成⼀条消息记录,然后⼊库,在写个接⼝供前台来拉取消息记录。
看到这个需求,⾸先想到的是使⽤AOP来实现了,然后,我去看了下现有功能模块中的代码,发现了问题,这些模块中的业务逻辑并没有放在service层来处理,直接在controller中处理了,controller中包含了两个甚⾄多个service处理,这样是不能保证事务安全的,既然这样,那么我们如何实现能保证事务安全呢。我想直接在controller中定义切⼊点,然后before中⼿动开启事务,在afterReturn之后根据需要来提交或者回滚事务。
然后趁着这个机会就查了下spring boot中的事务这块,就从最基础的说起。
1.spring boot中声明式事务的使⽤
想要在spring boot中使⽤声明式事务,有两种⽅式,⼀种是在各个service层中添加注解,还有⼀种是使⽤AOP配置全局的声明式事务管理
先来说第⼀种,需要⽤到两个注解就,⼀个是@EnableTransactionManagement⽤来开启注解事务管理,等同于xml配置⽅式的 <tx:annotation-driven />,另⼀个
是@Transactional
具体代码如下:
ample.demo;
2
3import org.springframework.boot.SpringApplication;
4import org.springframework.boot.autoconfigure.SpringBootApplication;
5import ansaction.annotation.EnableTransactionManagement;
6
7// @SpringBootApplication是Sprnig Boot项⽬的核⼼注解,主要⽬的是开启⾃动配置
10 @SpringBootApplication
11 @EnableTransactionManagement // 启注解事务管理,等同于xml配置⽅式的 <tx:annotation-driven />
12public class DemoApplication {
14public static void main(String[] args) {
16 SpringApplication.run(DemoApplication.class, args);
18 }
19
20 }
然后,注解@Transactional直接加在service层就可以了,放两个service⽤来验证事务是否按预期回滚
ample.demo.service;
ample.demo.bean.ResUser;
import ansaction.annotation.Transactional;
import java.util.List;
/**
* 注解加在接⼝上表名接⼝的所有⽅法都⽀持事务;
* 如果加在⽅法上,则只有该⽅法⽀持事务
* 可以根据需要在CUD操作上加注解
**/
@Transactional
public interface IUserService {
int insertUser(ResUser resUser);
int updateUser(ResUser resUser);
List<ResUser> getResUserList();
}
ample.demo.service;
2
ample.demo.bean.ResPartner;
4import ansaction.annotation.Transactional;
5
6import java.util.List;
7import java.util.Map;
8
9 @Transactional
10public interface IPartnerService {
11
12int add(ResPartner resPartner);
13
14int deleteByIds(String ids);
15
16int update(ResPartner resPartner);
17
18 ResPartner queryById(int id);
19
20 List<ResPartner> queryList(Map<String, Object> params);
21
22 }
实现类
ample.demo.service.impl;
2
ample.demo.bean.ResPartner;
ample.demo.dao.PartnerMapperXml;
ample.demo.service.IPartnerService;
6import org.slf4j.Logger;
7import org.slf4j.LoggerFactory;
8import org.springframework.beans.factory.annotation.Autowired;
9import org.springframework.stereotype.Component;
10
11import java.util.List;
12import java.util.Map;
13
14 @Component("partnerService")
15public class PartnerServiceImpl implements IPartnerService {
16
17private Logger logger = Class());
18 @Autowired
19private PartnerMapperXml partnerMapperXml;
20
21 @Override
22public int add(ResPartner resPartner) {
23 StringBuilder sbStr = new StringBuilder();
24 sbStr.append("id = ").Id())
25 .append(", name = ").Name())
26 .append(", city = ").City())
27 .append(", displayName = ").DisplayName());
28this.logger.String());
29return this.partnerMapperXml.add(resPartner);
30 }
31 }
ample.demo.service.impl;
2
ample.demo.bean.ResPartner;
ample.demo.bean.ResUser;
ample.demo.dao.PartnerMapperXml;
ample.demo.dao.ResUserMapper;
ample.demo.service.IUserService;
8import org.springframework.beans.factory.annotation.Autowired;
9import org.springframework.stereotype.Component;
10
11import java.util.List;
12
13 @Component("userService")
14public class UserServiceImpl implements IUserService {
15
16 @Autowired
17private ResUserMapper resUserMapper;
18 @Autowired
19private PartnerMapperXml partnerMapperXml;
20
21 @Override
22public int insertUser(ResUser resUser) {
23
24int i = resUserMapper.insert(resUser);
25// ResPartner partner = new ResPartner();
26// partner.Id());
27// partner.Name());
28// partner.Login());
29//
30// if (true) // ⽤来验证异常,使事务回滚
31// throw new RuntimeException("xxxxxxxxxxxxxxx");
32// int a = 1/0;
33// partnerMapperXml.add(partner);
34
35return i;
36 }
37
38 }
controller代码,JSONMsg是⼀个⾃定义类,就三个属性code,msg,data⽤来给前台返回数据。 llers;
2
3import com.alibaba.fastjson.JSONObject;
ample.demo.bean.JSONMsg;
ample.demo.bean.ResPartner;
ample.demo.bean.ResUser;
ample.demo.service.IPartnerService;
ample.demo.service.IUserService;
9import org.springframework.beans.factory.annotation.Autowired;
10import org.springframework.jdbc.datasource.DataSourceTransactionManager;
11import ansaction.PlatformTransactionManager;
12import ansaction.TransactionDefinition;
13import ansaction.TransactionStatus;
14import org.springframework.web.bind.annotation.*;
15
16import java.util.List;
17
18 @RestController
19 @RequestMapping("/users")
20public class UserController {
21
22 @Autowired
23private IUserService userService;
24 @Autowired
25private IPartnerService partnerService;
26 @Autowired
27private PlatformTransactionManager platformTransactionManager;
28 @Autowired
29private TransactionDefinition transactionDefinition;
30
31 @RequestMapping(value = "/insert", method = RequestMethod.POST)
32 @ResponseBody
33public 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);
40if (!ainsKey("data")){
41return jsonMsg;
42 }
43
44 ResUser user = JSONObject.("data").toString(), ResUser.class);
45int i = userService.insertUser(user);
46
47 System.out.println(i);
48if (i!=0){
49 jsonMsg.setCode(200);
50 jsonMsg.setMsg("成功");
51 jsonMsg.setData(user);
52 }
53
54return jsonMsg;
55 }
56
57// 该⽅法中的代码⽤来验证⼿动控制事务时使⽤
58// @RequestMapping(value = "/insert", method = RequestMethod.POST)
59// @ResponseBody
60// public JSONMsg insertUser(@RequestBody String data){
61// TransactionStatus transactionStatus = Transaction(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 (!ainsKey("data")){
74// return jsonMsg;
75// }
76//
77// ResUser user = JSONObject.("data").toString(), ResUser.class);
78// int i = userService.insertUser(user);
79//
80// i= 1/0;
81//
82// ResPartner partner = new ResPartner();
83// partner.Id());
84// partner.Name());
85// partner.Login());
86// partnerService.add(partner);
87//
88// if (i!=0){
89// jsonMsg.setCode(200);
90// jsonMsg.setMsg("成功");
91// jsonMsg.setData(user);
92// }
93//
94// platformTransactionManagermit(transactionStatus);
95// System.out.println("提交事务");
96// }catch (Exception e){
97// e.printStackTrace();
98// llback(transactionStatus);
99// System.out.println("回滚事务");
100// }finally {
101//
102// }
103// return jsonMsg;
104// }
105 }
接下来说下spring boot中配置全局的声明式事务,定义⼀个configure类,具体代码如下
figs;
2
3import org.aspectj.lang.annotation.Aspect;
4import org.springframework.aop.Advisor;
5import org.springframework.aop.aspectj.AspectJExpressionPointcut;
6import org.springframework.aop.support.DefaultPointcutAdvisor;
7import org.springframework.beans.factory.annotation.Autowired;
8import t.annotation.Bean;
9import t.annotation.Configuration;
10import ansaction.PlatformTransactionManager;
11import ansaction.TransactionDefinition;
12import ansaction.interceptor.DefaultTransactionAttribute;
13import ansaction.interceptor.NameMatchTransactionAttributeSource;
14import ansaction.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
38public class GlobalTransactionAdviceConfig {
39private static final String AOP_POINTCUT_EXPRESSION = "execution (* ample.demo.service..*.*(..))";
40
41 @Autowired
42private PlatformTransactionManager transactionManager;
43
44 @Bean
45public 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.PROPAGATI
ON_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);
69return new TransactionInterceptor(transactionManager, source);
70 }
71
72 @Bean
73public Advisor txAdviceAdvisor() {
74 AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
75 pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
76return new DefaultPointcutAdvisor(pointcut, txAdvice());
77 }
78 }
添加这个类,根据知⼰需要修改切⼊点,然后放到能被spring boot扫描到的包下即可,如果出现事务失败的情况,请查看下addTransactionalMethod是否配置正确,我当初就是⽤的insert*,⽽没有添加导致失败。
2.切⼊点为controller时,如何使⽤编程式事务管理控制事务
figs;
2
ample.demo.bean.JSONMsg;
ample.demo.bean.ResPartner;
ample.demo.bean.ResUser;
ample.demo.service.IPartnerService;
7import org.aspectj.lang.JoinPoint;
8import org.aspectj.lang.annotation.AfterReturning;
9import org.aspectj.lang.annotation.Aspect;
10import org.aspectj.lang.annotation.Before;
11import org.aspectj.lang.annotation.Pointcut;
12import org.springframework.beans.factory.annotation.Autowired;
13import org.springframework.stereotype.Component;
14import ansaction.PlatformTransactionManager;
15import ansaction.TransactionDefinition;
16import ansaction.TransactionStatus;
17import ansaction.annotation.Transactional;
18
19 @Component
20 @Aspect
21public class ResUserAspect {
22
23 @Autowired
24private IPartnerService partnerService;
25 @Autowired
26private PlatformTransactionManager platformTransactionManager;
27 @Autowired
28private TransactionDefinition transactionDefinition;
29
30private TransactionStatus transactionStatus;
31
32 @Pointcut("execution(public * llers.UserController.insertUser(..))")
33// @Pointcut("execution(public * ample.demo.service.IUserService.insertUser(..))") // 验证切⼊点为service时,AOP编程中的事务问题
34private void insertUser(){}
35
36 @Before(value = "insertUser()")
37public void before(){
38//在切⼊点程序执⾏之前⼿动开启事务 - 必须的操作
39 transactionStatus = Transaction(transactionDefinition);
40 }
41 // 验证切⼊点为service时,AOP编程中的事务问题
42// @AfterReturning(pointcut = "insertUser()", returning = "result")
43// public void afterReturning(JoinPoint joinPoint, Object result){
44//
45// Object[] args = Args();
46// System.out.println(args[0]);
47// if (((Integer)result) != 0){
48// ResPartner partner = new ResPartner();
49// ResUser user = (ResUser) args[0];
50// partner.Id());
51// partner.Name());
52// partner.Login());
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")
63public void afterReturning(JoinPoint joinPoint, Object result){
64
65if (!(result instanceof JSONMsg)){
66 System.out.Class());
67return;
68 }
69 JSONMsg jsonMsg = (JSONMsg) result;
70try{
71
72if (Code() == 200){
73 ResPartner partner = new ResPartner();
74 ResUser user = (ResUser) Data();
75 partner.Id());
76 partner.Name());
77 partner.Login());
78
79int a = 1/0;
80int i = partnerService.add(partner);
81
82 System.out.println(i);
83 }
84
85 platformTransactionManagermit(transactionStatus); // ⼿动提交事务
86 System.out.println("提交事务");
87 }catch (Exception e){
88 llback(transactionStatus); // 出现异常,回滚事务
89 System.out.println("回滚事务");
90 System.out.Message());
91
92//修改返回数据
93 jsonMsg.setCode(400);
94 jsonMsg.Message());
95 }
96
97 }
98 }
⽤到的实体bean
1// ResUser.java中的属性
springboot aop2private Integer id;
3private String login;
4private String name;
5private Integer age;
6
7// ResPartner.java中的属性
8private int id;
9private String name;
10private String city;
11private 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,后续有时间在来看下分布式中的事务处理问题。
参考链接:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论