这几天在做一个功能,具体的情况是这样的:

  项目中原有的几个功能模块中有数据上报的功能,现在需要在这几个功能模块的上报之后生成一条消息记录,然后入库,在写个接口供前台来拉取消息记录。

  看到这个需求,首先想到的是使用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中的声明式事务管理及编程式事务管理的更多相关文章

  1. Spring学习8-Spring事务管理(编程式事务管理)

    一.Spring事务的相关知识   1.事务是指一系列独立的操作,但在概念上具有原子性. 比如转账:A账号-100, B账号+100,完成.这两个操作独立是没问题的. 但在逻辑上,要么全部完成,要么一 ...

  2. 事务之三:编程式事务、声明式事务(XML配置事务、注解实现事务)

    Spring2.0框架的事务处理有两大类: JdbcTemplate操作采用的是JDBC默认的AutoCommit模式,也就是说我们还无法保证数据操作的原子性(要么全部生效,要么全部无效),如: Jd ...

  3. Spring事务管理之编程式事务管理

    © 版权声明:本文为博主原创文章,转载请注明出处 案例:利用Spring的编程式事务管理模拟转账过程 数据库准备 -- 创建表 CREATE TABLE `account`( `id` INT NOT ...

  4. 阶段3 2.Spring_10.Spring中事务控制_10spring编程式事务控制2-了解

    在业务层声明 transactionTemplate 并且声称一个set方法等着spring来注入 在需要事物控制的地方执行 execute.但是这个execute需要一个参数 需要的参数是Trans ...

  5. Spring事务管理的实现方式:编程式事务与声明式事务

    1.上篇文章讲解了Spring事务的传播级别与隔离级别,以及分布式事务的简单配置,点击回看上篇文章 2.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理) Spring实现编程式事务,依赖 ...

  6. Spring事务管理的实现方式之编程式事务与声明式事务详解

    原创说明:本博文为原创作品,绝非他处转载,转载请联系博主 1.上篇文章讲解了Spring事务的传播级别与隔离级别,以及分布式事务的简单配置,点击回看上篇文章 2.编程式事务:编码方式实现事务管理(代码 ...

  7. Spring事务:一种编程式事务,三种声明式事务

    事务隔离级别 隔离级别是指若干个并发的事务之间的隔离程度.TransactionDefinition 接口中定义了五个表示隔离级别的常量: TransactionDefinition.ISOLATIO ...

  8. 八、Spring之深入理解声明式事务

    Spring之深入理解声明式事务 何为事务? 事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用. 事务的四个属性: 1.原子性(atomicity) 事务是原子性操 ...

  9. spring的声明式事务和编程式事务

    事务管理对于企业应用来说是至关重要的,当出现异常情况时,它可以保证数据的一致性. Spring事务管理的两种方式 1.编程式事务 使用Transaction Ttempleate或者直接使用底层的Pl ...

随机推荐

  1. 六种 主流ETL 工具的比较(DataPipeline,Kettle,Talend,Informatica,Datax ,Oracle Goldengate)

    六种 主流ETL 工具的比较(DataPipeline,Kettle,Talend,Informatica,Datax ,Oracle Goldengate) 比较维度\产品 DataPipeline ...

  2. Matplotlib快速入门

    Matplotlib 可能还有小伙伴不知道Matplotlib是什么,下面是维基百科的介绍. Matplotlib 是Python编程语言的一个绘图库及其数值数学扩展 NumPy.它为利用通用的图形用 ...

  3. Failed to start Docker Application Container Engine.

    [root@dockertest ~]# systemctl status docker.service● docker.service - Docker Application Container ...

  4. leadcode的Hot100系列--62. 不同路径--简单的动态规划

    题目比较清晰,简单来说就是: A B C D E F G H I J K L 只能往右或者往下,从A到L,能有几种走法. 这里使用动态规划的方法来做一下. 动态规划最重要的就是动态方程,这里简单说下这 ...

  5. 2019.6.5 NOIP2014 day2 t2 寻找道路

    我竟然一个人敲了NOIP提高组的t2? 题目描述 在有向图 G 中,每条边的长度均为 1,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件: 路径上的所有点的出边所指向的点都直 ...

  6. 机器学习读书笔记(二)使用k-近邻算法改进约会网站的配对效果

    一.背景 海伦女士一直使用在线约会网站寻找适合自己的约会对象.尽管约会网站会推荐不同的任选,但她并不是喜欢每一个人.经过一番总结,她发现自己交往过的人可以进行如下分类 不喜欢的人 魅力一般的人 极具魅 ...

  7. BZOJ 1061:志愿者招募(单纯型)

    题目链接 题意 中文题意. 思路 单纯型模板题. 单纯型用来解决线性规划问题. 留坑待填. 算法思路 好长 模板 论文 卿学姐视频 #include <bits/stdc++.h> usi ...

  8. FluentValidation:一个非常受欢迎的,用于构建强类型验证规则的.NET 库

    1. FluentValidation:一个非常受欢迎的,用于构建强类型验证规则的.NET 库 请求参数实体定义: FluentValidation 验证类定义: 过滤器:ActionFilter中O ...

  9. 18.linux基础优化

    1.linux系统的基础优化 (1)关闭selinux sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config 临时关闭 ...

  10. Git使用小技巧之免密登录

    想要获取更多文章可以访问我的博客 - 代码无止境. 小代同学在使用Git的过程中发现,每次向远程仓库推送代码的时候都需要输入账号密码.做为一个程序员,多多少少都会有偷懒的思维.那么如何才能避免每次都要 ...