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 ...
随机推荐
- spring 5.x 系列第6篇 —— 整合 mybatis + druid 连接池 (代码配置方式)
源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...
- Docker 安装mysql容器数据卷挂载到宿主机
环境 Centos:7 Docker: 17.05-ce Mysql: 5.7 1. Mysql外部数据和配置文件路径 msyql配置文件路径:/etc/mysql mysql数据卷路径:/var/l ...
- CRISP-DM--数据挖掘标准流程
CRISP-DM--数据挖掘标准流程 在1996年的时候,SPSS,戴姆勒-克莱斯勒和NCR公司发起共同成立了一个兴趣小组,目的是为了建立数据挖掘方法和过程的标准.并在1999年正式提炼出了CRISP ...
- HDU 5775:Bubble Sort(树状数组)
http://acm.hdu.edu.cn/showproblem.php?pid=5775 Bubble Sort Problem Description P is a permutation ...
- iOS自动化探索(十)代码覆盖率统计
iOS APP代码覆盖率统计 今年Q3季度领导给加了个任务要做前后端代码覆盖率统计, 鉴于对iOS代码代码比较熟就选择先从iOS端入手,折腾一整天后终于初步把流程跑通了记录如下 覆盖率监测的原理 Xc ...
- 用PHP抓取百度贴吧邮箱数据
注:本程序可能非常适合那些做百度贴吧营销的朋友. 去逛百度贴吧的时候,经常会看到楼主分享一些资源,要求留下邮箱,楼主才给发. 对于一个热门的帖子,留下的邮箱数量是非常多的,楼主需要一个一个的去复制那些 ...
- ETL-kettle 核心执行逻辑
一.大数据下的ETL工具是否还使用Kettle kettle 作为通用的ETL工具,非常成熟,应用也很广泛,这里主要讲一下 目前我们如何使用kettle的? 在进行大数据处理时,ETL也是大数据处理的 ...
- 基于SpringBoot从零构建博客网站 - 设计可扩展上传模块和开发修改头像密码功能
上传模块在web开发中是很常见的功能也是很重要的功能,在web应用中需要上传的可以是图片.pdf.压缩包等其它类型的文件,同时对于图片可能需要回显,对于其它文件要能够支持下载等.在守望博客系统中对于上 ...
- Spring Cloud Alibaba | Nacos服务注册与发现
目录 Spring Cloud Alibaba | Nacos服务注册与发现 1. 服务提供者 1.1 pom.xml项目依赖 1.2 配置文件application.yml 1.3 启动类Produ ...
- Python 定义自己的常量类
在实际的程序开发中,我们通常会将一个不可变的变量声明为一个常量.在很多高级语言中都会提供常量的关键字来定义常量,如 C++ 中的 const , Java 中的 final 等,但是 Python 语 ...