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
具体代码如下:
- package com.example.demo;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.transaction.annotation.EnableTransactionManagement;
- // @SpringBootApplication是Sprnig Boot项目的核心注解,主要目的是开启自动配置
- @SpringBootApplication
- @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
- public class DemoApplication {
- public static void main(String[] args) {
- SpringApplication.run(DemoApplication.class, args);
- }
- }
然后,注解@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();
- }
- package com.example.demo.service;
- import com.example.demo.bean.ResPartner;
- import org.springframework.transaction.annotation.Transactional;
- import java.util.List;
- import java.util.Map;
- @Transactional
- public interface IPartnerService {
- int add(ResPartner resPartner);
- int deleteByIds(String ids);
- int update(ResPartner resPartner);
- ResPartner queryById(int id);
- List<ResPartner> queryList(Map<String, Object> params);
- }
实现类
- package com.example.demo.service.impl;
- import com.example.demo.bean.ResPartner;
- import com.example.demo.dao.PartnerMapperXml;
- import com.example.demo.service.IPartnerService;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import java.util.List;
- import java.util.Map;
- @Component("partnerService")
- public class PartnerServiceImpl implements IPartnerService {
- private Logger logger = LoggerFactory.getLogger(this.getClass());
- @Autowired
- private PartnerMapperXml partnerMapperXml;
- @Override
- public int add(ResPartner resPartner) {
- StringBuilder sbStr = new StringBuilder();
- sbStr.append("id = ").append(resPartner.getId())
- .append(", name = ").append(resPartner.getName())
- .append(", city = ").append(resPartner.getCity())
- .append(", displayName = ").append(resPartner.getDisplayName());
- this.logger.info(sbStr.toString());
- return this.partnerMapperXml.add(resPartner);
- }
- }
- package com.example.demo.service.impl;
- import com.example.demo.bean.ResPartner;
- import com.example.demo.bean.ResUser;
- import com.example.demo.dao.PartnerMapperXml;
- import com.example.demo.dao.ResUserMapper;
- import com.example.demo.service.IUserService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import java.util.List;
- @Component("userService")
- public class UserServiceImpl implements IUserService {
- @Autowired
- private ResUserMapper resUserMapper;
- @Autowired
- private PartnerMapperXml partnerMapperXml;
- @Override
- public int insertUser(ResUser resUser) {
- int i = resUserMapper.insert(resUser);
- // ResPartner partner = new ResPartner();
- // partner.setId(resUser.getId());
- // partner.setName(resUser.getName());
- // partner.setDisplayName(resUser.getLogin());
- //
- // if (true) // 用来验证异常,使事务回滚
- // throw new RuntimeException("xxxxxxxxxxxxxxx");
- // int a = 1/0;
- // partnerMapperXml.add(partner);
- return i;
- }
- }
controller代码,JSONMsg是一个自定义类,就三个属性code,msg,data用来给前台返回数据。
- package com.example.demo.controllers;
- import com.alibaba.fastjson.JSONObject;
- import com.example.demo.bean.JSONMsg;
- import com.example.demo.bean.ResPartner;
- import com.example.demo.bean.ResUser;
- import com.example.demo.service.IPartnerService;
- import com.example.demo.service.IUserService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.jdbc.datasource.DataSourceTransactionManager;
- import org.springframework.transaction.PlatformTransactionManager;
- import org.springframework.transaction.TransactionDefinition;
- import org.springframework.transaction.TransactionStatus;
- import org.springframework.web.bind.annotation.*;
- import java.util.List;
- @RestController
- @RequestMapping("/users")
- public class UserController {
- @Autowired
- private IUserService userService;
- @Autowired
- private IPartnerService partnerService;
- @Autowired
- private PlatformTransactionManager platformTransactionManager;
- @Autowired
- private TransactionDefinition transactionDefinition;
- @RequestMapping(value = "/insert", method = RequestMethod.POST)
- @ResponseBody
- public JSONMsg insertUser(@RequestBody String data){
- JSONMsg jsonMsg = new JSONMsg();
- jsonMsg.setCode(400);
- jsonMsg.setMsg("error");
- System.out.println(data);
- JSONObject jsonObject = JSONObject.parseObject(data);
- if (!jsonObject.containsKey("data")){
- return jsonMsg;
- }
- ResUser user = JSONObject.parseObject(jsonObject.get("data").toString(), ResUser.class);
- int i = userService.insertUser(user);
- System.out.println(i);
- if (i!=0){
- jsonMsg.setCode(200);
- jsonMsg.setMsg("成功");
- jsonMsg.setData(user);
- }
- return jsonMsg;
- }
- // 该方法中的代码用来验证手动控制事务时使用
- // @RequestMapping(value = "/insert", method = RequestMethod.POST)
- // @ResponseBody
- // public JSONMsg insertUser(@RequestBody String data){
- // TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
- //
- // System.out.println(transactionStatus.isCompleted());
- // System.out.println(transactionStatus.isRollbackOnly());
- //
- //
- // JSONMsg jsonMsg = new JSONMsg();
- // jsonMsg.setCode(400);
- // jsonMsg.setMsg("error");
- // System.out.println(data);
- // try{
- // JSONObject jsonObject = JSONObject.parseObject(data);
- // if (!jsonObject.containsKey("data")){
- // return jsonMsg;
- // }
- //
- // ResUser user = JSONObject.parseObject(jsonObject.get("data").toString(), ResUser.class);
- // int i = userService.insertUser(user);
- //
- // i= 1/0;
- //
- // ResPartner partner = new ResPartner();
- // partner.setId(user.getId());
- // partner.setName(user.getName());
- // partner.setDisplayName(user.getLogin());
- // partnerService.add(partner);
- //
- // if (i!=0){
- // jsonMsg.setCode(200);
- // jsonMsg.setMsg("成功");
- // jsonMsg.setData(user);
- // }
- //
- // platformTransactionManager.commit(transactionStatus);
- // System.out.println("提交事务");
- // }catch (Exception e){
- // e.printStackTrace();
- // platformTransactionManager.rollback(transactionStatus);
- // System.out.println("回滚事务");
- // }finally {
- //
- // }
- // return jsonMsg;
- // }
- }
接下来说下spring boot中配置全局的声明式事务,定义一个configure类,具体代码如下
- package com.example.demo.configs;
- import org.aspectj.lang.annotation.Aspect;
- import org.springframework.aop.Advisor;
- import org.springframework.aop.aspectj.AspectJExpressionPointcut;
- import org.springframework.aop.support.DefaultPointcutAdvisor;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.transaction.PlatformTransactionManager;
- import org.springframework.transaction.TransactionDefinition;
- import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
- import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
- import org.springframework.transaction.interceptor.TransactionInterceptor;
- /**
- * @ClassName: GlobalTransactionAdviceConfig
- * @Description: AOP全局事务管理配置
- *
- * 声明式事务说明:
- * 1.如果将业务逻辑放到service层面来处理,则能够保证事务安全,即便使用了AOP来切入service方法也能保证事务安全;
- * 2.如果多个service在controller层做业务逻辑(本身就是错误的),则不能保证事务安全。
- * 对于2中的情况,应该尽量避免,因为本身就是错误的;
- * 这种情况在面向切面编程中也有可能碰到,如,因为必要切入点为controller(应尽量避免,原则应切service),切面程序跟controller业务逻辑不同,
- * service不同,会导致事务混乱;
- *
- * 如果出现上述情况,则可以使用编程式事务管理(也就是手动控制事务)
- * 在controller逻辑开始之前手动开启/获取事务,然后在controller逻辑结束后再根据需要提交或者回滚事务;
- * 在AOP中也是如此,在before中手动开启/获取事务(这一步是必须的),在after中处理切面逻辑,然后根据需要提交或者回滚事务,如果由于异常需要回滚事务,记得修改返回信息
- *
- * @Author:
- * @Date: 2019-08-01
- * @Version: V2.0
- **/
- @Aspect
- @Configuration
- public class GlobalTransactionAdviceConfig {
- private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.example.demo.service..*.*(..))";
- @Autowired
- private PlatformTransactionManager transactionManager;
- @Bean
- public TransactionInterceptor txAdvice() {
- DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
- txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
- DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
- txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
- txAttr_REQUIRED_READONLY.setReadOnly(true);
- NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
- source.addTransactionalMethod("add*", txAttr_REQUIRED);
- source.addTransactionalMethod("insert*", txAttr_REQUIRED);
- source.addTransactionalMethod("save*", txAttr_REQUIRED);
- source.addTransactionalMethod("create*", txAttr_REQUIRED);
- source.addTransactionalMethod("delete*", txAttr_REQUIRED);
- source.addTransactionalMethod("update*", txAttr_REQUIRED);
- source.addTransactionalMethod("exec*", txAttr_REQUIRED);
- source.addTransactionalMethod("set*", txAttr_REQUIRED);
- source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
- source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
- source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
- source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
- source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
- source.addTransactionalMethod("is*", txAttr_REQUIRED_READONLY);
- source.addTransactionalMethod("select*", txAttr_REQUIRED_READONLY);
- return new TransactionInterceptor(transactionManager, source);
- }
- @Bean
- public Advisor txAdviceAdvisor() {
- AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
- pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
- return new DefaultPointcutAdvisor(pointcut, txAdvice());
- }
- }
添加这个类,根据知己需要修改切入点,然后放到能被spring boot扫描到的包下即可,如果出现事务失败的情况,请查看下addTransactionalMethod是否配置正确,我当初就是用的insert*,而没有添加导致失败。
2.切入点为controller时,如何使用编程式事务管理控制事务
- package com.example.demo.configs;
- import com.example.demo.bean.JSONMsg;
- import com.example.demo.bean.ResPartner;
- import com.example.demo.bean.ResUser;
- import com.example.demo.service.IPartnerService;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.annotation.AfterReturning;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import org.springframework.transaction.PlatformTransactionManager;
- import org.springframework.transaction.TransactionDefinition;
- import org.springframework.transaction.TransactionStatus;
- import org.springframework.transaction.annotation.Transactional;
- @Component
- @Aspect
- public class ResUserAspect {
- @Autowired
- private IPartnerService partnerService;
- @Autowired
- private PlatformTransactionManager platformTransactionManager;
- @Autowired
- private TransactionDefinition transactionDefinition;
- private TransactionStatus transactionStatus;
- @Pointcut("execution(public * com.example.demo.controllers.UserController.insertUser(..))")
- // @Pointcut("execution(public * com.example.demo.service.IUserService.insertUser(..))") // 验证切入点为service时,AOP编程中的事务问题
- private void insertUser(){}
- @Before(value = "insertUser()")
- public void before(){
- //在切入点程序执行之前手动开启事务 - 必须的操作
- transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
- }
- // 验证切入点为service时,AOP编程中的事务问题
- // @AfterReturning(pointcut = "insertUser()", returning = "result")
- // public void afterReturning(JoinPoint joinPoint, Object result){
- //
- // Object[] args = joinPoint.getArgs();
- // System.out.println(args[0]);
- // if (((Integer)result) != 0){
- // ResPartner partner = new ResPartner();
- // ResUser user = (ResUser) args[0];
- // partner.setId(user.getId());
- // partner.setName(user.getName());
- // partner.setDisplayName(user.getLogin());
- //
- // int a = 1/0;
- // int i = partnerService.add(partner);
- //
- // System.out.println(i);
- // }
- // }
- //切入点为controller时的事务验证
- @Transactional
- @AfterReturning(pointcut = "insertUser()", returning = "result")
- public void afterReturning(JoinPoint joinPoint, Object result){
- if (!(result instanceof JSONMsg)){
- System.out.println(result.getClass());
- return;
- }
- JSONMsg jsonMsg = (JSONMsg) result;
- try{
- if (jsonMsg.getCode() == 200){
- ResPartner partner = new ResPartner();
- ResUser user = (ResUser) jsonMsg.getData();
- partner.setId(user.getId());
- partner.setName(user.getName());
- partner.setDisplayName(user.getLogin());
- int a = 1/0;
- int i = partnerService.add(partner);
- System.out.println(i);
- }
- platformTransactionManager.commit(transactionStatus); // 手动提交事务
- System.out.println("提交事务");
- }catch (Exception e){
- platformTransactionManager.rollback(transactionStatus); // 出现异常,回滚事务
- System.out.println("回滚事务");
- System.out.println(e.getMessage());
- //修改返回数据
- jsonMsg.setCode(400);
- jsonMsg.setMsg(e.getMessage());
- }
- }
- }
用到的实体bean
- // ResUser.java中的属性
- private Integer id;
- private String login;
- private String name;
- private Integer age;
- // ResPartner.java中的属性
- private int id;
- private String name;
- private String city;
- 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中的声明式事务管理及编程式事务管理的更多相关文章
- Spring学习8-Spring事务管理(编程式事务管理)
一.Spring事务的相关知识 1.事务是指一系列独立的操作,但在概念上具有原子性. 比如转账:A账号-100, B账号+100,完成.这两个操作独立是没问题的. 但在逻辑上,要么全部完成,要么一 ...
- 事务之三:编程式事务、声明式事务(XML配置事务、注解实现事务)
Spring2.0框架的事务处理有两大类: JdbcTemplate操作采用的是JDBC默认的AutoCommit模式,也就是说我们还无法保证数据操作的原子性(要么全部生效,要么全部无效),如: Jd ...
- Spring事务管理之编程式事务管理
© 版权声明:本文为博主原创文章,转载请注明出处 案例:利用Spring的编程式事务管理模拟转账过程 数据库准备 -- 创建表 CREATE TABLE `account`( `id` INT NOT ...
- 阶段3 2.Spring_10.Spring中事务控制_10spring编程式事务控制2-了解
在业务层声明 transactionTemplate 并且声称一个set方法等着spring来注入 在需要事物控制的地方执行 execute.但是这个execute需要一个参数 需要的参数是Trans ...
- Spring事务管理的实现方式:编程式事务与声明式事务
1.上篇文章讲解了Spring事务的传播级别与隔离级别,以及分布式事务的简单配置,点击回看上篇文章 2.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理) Spring实现编程式事务,依赖 ...
- Spring事务管理的实现方式之编程式事务与声明式事务详解
原创说明:本博文为原创作品,绝非他处转载,转载请联系博主 1.上篇文章讲解了Spring事务的传播级别与隔离级别,以及分布式事务的简单配置,点击回看上篇文章 2.编程式事务:编码方式实现事务管理(代码 ...
- Spring事务:一种编程式事务,三种声明式事务
事务隔离级别 隔离级别是指若干个并发的事务之间的隔离程度.TransactionDefinition 接口中定义了五个表示隔离级别的常量: TransactionDefinition.ISOLATIO ...
- 八、Spring之深入理解声明式事务
Spring之深入理解声明式事务 何为事务? 事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用. 事务的四个属性: 1.原子性(atomicity) 事务是原子性操 ...
- spring的声明式事务和编程式事务
事务管理对于企业应用来说是至关重要的,当出现异常情况时,它可以保证数据的一致性. Spring事务管理的两种方式 1.编程式事务 使用Transaction Ttempleate或者直接使用底层的Pl ...
随机推荐
- vuex分模块4
Vuex下Store的模块化拆分实践 https://segmentfault.com/a/1190000007667542 vue.js vuex 猫切 2016年12月02日发布 赞 | 1 ...
- 一篇文章概括 Java Date Time 的使用
本文目的:掌握 Java 中日期和时间常用 API 的使用. 参考:Jakob Jenkov的英文教程Java Date Time Tutorial 和 JavaDoc 概览 Java 8 新增 AP ...
- 高性能微服务网关.NETCore客户端Kong.Net开源发布
前言 项目地址:https://github.com/lianggx/Kong.Net 你的支持使我们更加强大,请单击 star 让更多的 .NETCore 认识它. 拥抱开源的脚步,我们从来都是一直 ...
- 使用vue-print-nb插件页面空白以及打印没有样式问题
在使用vue-print-nb中遇到两个问题: 第一个问题:点击打印后,打印的内容是一片空白 vue-print-nb的原理大概是在你的页面上创建一个iframe,然后把你要打印的那一个div抓出来给 ...
- HDU 4444:Walk(思维建图+BFS)***
http://acm.hdu.edu.cn/showproblem.php?pid=4444 题意:给出一个起点一个终点,给出n个矩形的两个对立顶点,问最少需要拐多少次弯可以从起点到达终点,如果不能输 ...
- BZOJ 1483:[HNOI2009]梦幻布丁(链表启发式合并)
http://www.lydsy.com/JudgeOnline/problem.php?id=1483 题意:中文. 思路:对于每一种颜色,用一个链表串起来,一开始保存一个答案,后面颜色替换的时候再 ...
- html手机自适应屏幕
<meta name="viewport" content="height=device-width, initial-scale=1.0, maximum-sca ...
- ElementUI 源码简析——源码结构篇
ElementUI 作为当前运用的最广的 Vue PC 端组件库,很多 Vue 组件库的架构都是参照 ElementUI 做的.作为一个有梦想的前端(咸鱼),当然需要好好学习一番这套比较成熟的架构. ...
- 微信小程序开发--页面结构
一.微信小程序开发--页面文件组成 [page.name].js 页面逻辑文件,用于创建页面对象,以及处理页面生命周期控制和数据处理 [page.name].wxml wxml指的是Wei Xin M ...
- 设计模式——通用泛型单例(普通型和继承自MonoBehaviour)
单例模式是设计模式中最为常见的,不多解释了.但应该尽量避免使用,一般全局管理类才使用单例. 普通泛型单例: public abstract class Singleton<T> where ...