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 ...
随机推荐
- Java NIO 学习笔记(一)----概述,Channel/Buffer
目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...
- appcan中evaluateScript、evaluatePopoverScript的使用
1. 如果要在某个主窗体中执行JS,使用 appcan.window.evaluateScript(name,scriptContent) eg: appcan.window.evaluateScri ...
- 3D echarts 点位报表展示
一,准备工作1)获取Echarts 以下为官网推荐的几种获取 ECharts方式: 从官网下载界面选择你需要的版本下载,根据开发者功能和体积上的需求,我们提供了不同打包的下载,如果你在体积上没有要求, ...
- docker 获取镜像
之前提到过,Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像. 从 Docker 镜像仓库获取镜像的命令是 docker pull.其命令格式为: docker p ...
- zabbix报警信息设置
报警信息 默认标题: 服务器监控报警 告警主机:{HOST.NAME} 主机IP: {HOST.IP} 告警级别:{TRIGGER.SEVERITY} 告警信息:{TRIGGER.NAME} 问题详情 ...
- cur.execute(sql,args)和cur.execute(sql)的区别
轉:https://blog.csdn.net/mjjyszazc/article/details/88932664 方式一: userid = “123”sql = “select id,name ...
- 【无线安全实践入门】网络扫描和ARP欺骗
文中可能存在错误操作或错误理解,望大家不吝指正. 同时也希望可以帮助到想要学习接触此方面.或兴趣使然的你,让你有个大概的印象. !阅前须知! 本文是基于我几年前的一本笔记本,上面记录了我学习网络基础时 ...
- SpringMVC——MVC
一.了解MVC mvc这种设计模式,分为三个基本部分:模型(Model).视图(View)和控制器(Controller),不光运用于Web领域,而且也能用于非Web领域:可以特指一种表现层设计模式, ...
- Android 开发你需要了解的那些事
本文微信公众号「AndroidTraveler」首发. 背景 最近部门有新入职员工,作为规划技术路线的导师,这边给新员工安排了学习路线. 除了基本的学习路线之外,每次沟通,我都留了一个小问题,让小伙伴 ...
- Golang 高效实践之并发实践
前言 在我前面一篇文章Golang受欢迎的原因中已经提到,Golang是在语言层面(runtime)就支持了并发模型.那么作为编程人员,我们在实践Golang的并发编程时,又有什么需要注意的点呢?下面 ...