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

回到顶部