Spring声明式事务以及事务传播行为

Spring声明式事务以及事务传播行为

  • Spring声明式事务
    • 1.编程式事务
    • 2.使用AOP改造编程式事务
    • 3.Spring声明式事务
  • 事务传播行为

如果对数据库事务不太熟悉,可以阅读上一篇博客简单回顾一下:MySQL事务以及并发访问隔离级别

Spring声明式事务

  1. 事务一般添加到JavaEE三层结构中的service层(业务逻辑层)

  2. 在Spring进行事务管理操作的两种方式

    • 编程式事务管理

    • 声明式事务管理

**转账案例代码准备:**按照以下代码配置完成之后可以进行数据库的操作,但不涉及事务。

image-20240404093723896

  1. 数据库

    create database spring_db;
    use spring_db;
    create table account(id int primary key auto_increment,name varchar(20),money double	
    );
    insert into account values(null,'jack',1000),(null,'rose',1000);
    
  2. 导入Maven依赖

    <dependencies><!--Spring核心包--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.10.RELEASE</version></dependency><!-- 切入点表达式 --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version></dependency><!--mybatis的包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><!--Druid数据库连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><!--数据库驱动包,我的MySQL版本是8.0.33--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><!--spring整合mybatis,需要下面两个jar包--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.10.RELEASE</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version></dependency><!-- 引入单元测试的jar包(需要在 4.12以上)  --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version></dependency><!-- Spring整合junit的jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.2.10.RELEASE</version></dependency>
    </dependencies>
    
  3. jdbc.properties配置文件

    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/spring_db
    jdbc.username=root
    jdbc.password=123456
    
  4. MyBatisConfig配置类

    public class MyBatisConfig {@Beanpublic SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource ds){SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();//设置pojo的包扫描factoryBean.setTypeAliasesPackage("top.codermao.domain");//设置连接池factoryBean.setDataSource(ds);return factoryBean;}@Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){MapperScannerConfigurer msc = new MapperScannerConfigurer();//设置dao层的接口扫描msc.setBasePackage("top.codermao.dao");return msc;}
    }
    
  5. Spring配置类

    @Configuration
    @ComponentScan("top.codermao")
    @PropertySource("classpath:jdbc.properties")
    @Import(MyBatisConfig.class)
    public class SpringConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Beanpublic DataSource getDataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(username);ds.setPassword(password);return ds;}}
    
  6. dao层接口

    public interface AccountDao {//转出@Update("update account set money = money - #{money} where id = #{outId}")int outMoney(@Param("outId") int outId, @Param("money")double money);//转入@Update("update account set money = money + #{money} where id = #{inId}")int inMoney(@Param("inId") int inId, @Param("money")double money);
    }
    
  7. domain包下实体类

    public class Account implements Serializable {private Integer id;private String name;private Double money;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"id=" + id +", name='" + name + '\'' +", money=" + money +'}';}}
    
  8. service层接口

    public interface AccountService {//转账业务void transfer(int outId,int inId,double money);
    }
    
  9. service层实现类

    @Service
    public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao dao;@Overridepublic void transfer(int outId, int inId, double money) {try {dao.outMoney(outId, money);//可能在转账过程中发生意外: 转出执行,转入还未执行
    //            int i = 1/0;dao.inMoney(inId, money);} catch (Exception e) {e.printStackTrace();}}
    }
    
  10. WebApp类充当Controller层,负责数据的发送和接收

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class WebApp {@Autowiredprivate AccountService service;@Testpublic void test01(){service.transfer(1, 2, 200);}
    }
    

1.编程式事务

在学习声明式事务之前我们需要了解编程式事务,因为声明式事务是Spring对编程式事务的封装。

  • 所谓编程式事务是指用Spring中事务相关的API用硬编码方式来实现事务
    • 缺点
      • 事务管理代码和业务代码耦合严重
      • 后续添加其他业务方法还要重新编写事务代码,冗余

所以学习编程式事务,就是学习Spring事务管理相关API。

步骤一:创建事务管理器

# PlatformTransactionManager(平台事务管理器)
1. 这是一个接口,以下是实现类1). - DataSourceTransactionManager (重点!!!)适用于Spring JDBC或MyBatis2). - HibernateTransactionManager 适用于Hibernate3.0及以上版本  3). - JpaTransactionManager适用于JPA (Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行)
2. 	此接口定义了事务的基本操作	1). 获取事务 :TransactionStatus getTransaction(TransactionDefinition definition)2). 提交事务 :void commit(TransactionStatus status) 3). 回滚事务 :void rollback(TransactionStatus status)

步骤二:定义事务属性TransactionDefinition

# TransactionDefinition(定义事务属性)
1. 实现类DefaultTransactionDefinition
2. 此接口定义了事务的基本信息
//2. 创建事务定义对象
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
/*设置事务隔离级别0). spring默认隔离级别是跟数据库软件一致1). mysql默认是REPEATABLE_READ2). oracle默认是READ_COMMITTED*/
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
/*设置是否只读1). false,表示读写均可(默认设置,适合增删改操作)2). true,表示只读(适合查,效率高)
*/
td.setReadOnly(false);
/*设置超时时间1). 默认值是-1, 表示永不超时2). 单位是秒
*/
td.setTimeout(10);
/*设置事务传播行为1. 一般增删改:REQUIRED (默认值)2. 一般查询  SUPPORTS
*/
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

步骤三:开启事务

# TransactionStatus(接口)
`public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException`
调用事务管理器的getTransaction方法,即可开启一个事务
这个方法会返回一个TransactionStatus表示事务状态的一个对象,通过TransactionStatus提供的一些方法可以用来控制事务的一些状态,比如事务最终是需要回滚还是需要提交。- 获取事务是否处于新开启事务状态- boolean isNewTransaction()
- 获取事务是否处于已完成状态- boolean isCompleted()
- 获取事务是否处于回滚状态- boolean isRollbackOnly()
- 刷新事务状态- void flush()
- 获取事务是否具有回滚存储点- boolean hasSavepoint()
- 设置事务处于回滚状态- void setRollbackOnly()

步骤四:执行业务操作

  • 书写业务逻辑代码

步骤五:提交 or 回滚

  • 无异常发生,提交

    dstm.commit(ts);
    
  • 有异常发生,回滚

    dstm.rollback(ts);
    

对service层AccountServiceImpl的transfer方法添加编程式事务

@Service
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao dao;@Autowiredprivate DataSource dataSource;@Overridepublic void transfer(int outId, int inId, double money) {//1.创建事务管理器DataSourceTransactionManager dstm = new DataSourceTransactionManager();//为事务管理器添加与数据层相同的数据源dstm.setDataSource(dataSource);//2.创建事务定义对象,设置隔离级别、传播特性、超时时间...DefaultTransactionDefinition td = new DefaultTransactionDefinition();/*设置事务隔离级别0). spring默认隔离级别是跟数据库软件一致 (ISOLATION_DEFAULT)1). mysql默认是REPEATABLE_READ2). oracle默认是READ_COMMITTED*/td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);/*设置是否为只读事务1). false,表示读写均可(默认设置,适合增删改操作)2). true,表示只读(适合查,效率高)*/td.setReadOnly(false);/*设置超时时间1). 默认值是-1, 表示永不超时2). 单位是秒*/td.setTimeout(10);/*设置事务传播行为1. 一般增删改:REQUIRED (默认值)2. 一般查询  SUPPORTS*/td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//3.创建事务状态对象,用于控制事务执行(了解)  -> 相当于开启事务TransactionStatus ts = dstm.getTransaction(td);try {dao.outMoney(outId, money);//可能在转账过程中发生意外: 转出执行,转入还未执行
//            int i = 10/0;dao.inMoney(inId, money);dstm.commit(ts);//成功,提交} catch (Exception e) {e.printStackTrace();dstm.rollback(ts);//失败,回滚}}
}

2.使用AOP改造编程式事务

硬编码方式添加事务耦合度比较高,AOP面向切面编程是动态增强方法,如果引入AOP则可以将业务代码和事务代码分开,实现解耦。

使用AOP处理编程式事务解决了耦合问题。。但是还是有些不够完美。。AOP处理不具备特例性,任何业务添加事务都是一样的操作,对某些事务可能对事务属性有一些独特的设置。

步骤一:在SpringConfig上添加开启AOP的注解

...
@EnableAspectJAutoProxy
public class SpringConfig {...
}

步骤二:AccountServiceImpl中transfer中只需要书写业务代码

  • 注意:注意: 在aop使用中,切入点方法transfer千万不能自己catch异常
@Service
public class AccountServiceImpl implements AccountService {/*注意: 在aop使用中,切入点方法千万不能自己catch异常原因: 如果切入点自己catch了异常,那么通知中是调用切入点的地方是不会感知到异常,就不会执行catch了(相当于异常通知失效)解决方案:A方案: 有异常直接抛出,不要catchB方案: 可以catch,但是再new一个异常抛出*/@Overridepublic void transfer(int outId, int inId, double money) {dao.outMoney(outId, money);//可能在转账过程中发生意外: 转出执行,转入还未执行//int i = 1/0;dao.inMoney(inId, money);}
}

步骤三:添加TxAdvice

  • 将编程式事务中对事务操作的代码抽取到TxAdvice切面类中
package top.codermao.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;import javax.sql.DataSource;@Component
@Aspect
public class TxAdvice {@Autowiredprivate DataSource dataSource;@Pointcut("execution(* top.codermao.service.*Service.transfer(..))")public void pt(){}@Around("pt()")public Object around(ProceedingJoinPoint pjp){Object result = null;//1. 创建事务管理器DataSourceTransactionManager dstm = new DataSourceTransactionManager();//为事务管理器设置与数据层相同的数据源!!!dstm.setDataSource(dataSource);//2. 创建事务定义对象 : 隔离级别/传播特性/超时时间...DefaultTransactionDefinition td = new DefaultTransactionDefinition();td.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);td.setReadOnly(false);td.setTimeout(10);td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//3.创建事务状态对象,用于控制事务执行(了解)  -> 相当于开启事务TransactionStatus ts = dstm.getTransaction(td);try{result = pjp.proceed();dstm.commit(ts);//成功,提交} catch (Throwable e) {e.printStackTrace();dstm.rollback(ts);//失败,回滚System.out.println("aop改造编程式事务");}return  result;}
}

3.Spring声明式事务

spring底层封装了事务切面类TxAdvice, 让开发者声明配置即可用

步骤一:在SpringConfig类上添加注解开启Spring事务管理支持

...
@EnableTransactionManagement
public class SpringConfig {...
}

步骤二:在SpringConfig类中配置事务管理器

...
@EnableTransactionManagement
public class SpringConfig {...@Beanpublic DataSourceTransactionManager getTxManager(DataSource dataSource){//这里使用DataSourceTransactionManager,因为使用的是MyBatisDataSourceTransactionManager manager = new DataSourceTransactionManager();manager.setDataSource(dataSource);return manager;}
}

步骤三:在需要添加事务的方法上添加@Transactional注解使其成为切入点

  • @Transactional注解属性

    • 属性都有默认值

    image-20240404110247388

  • @Transactional放置位置不同,效果不同

    • 如果放在类的方法上,说明当前方法是切入点
    • 如果放在类上,说明当前类的所有方法是切入点
    • 如果放在接口的方法上,说明此方法的所有重写方法是切入点 (常用)
    • 如果放在接口上,说明此接口的所有实现类的所有方法都是切入点 (常用)

事务传播行为

事务传播行为:指的就是当一个事务方法B被另一个事务方法A调用时,这个事务方法B应该对待A的事务态度。(B是自己开启一个新事务,还是融入A的事务,或者不添加事务,或者…)

Spring事务角色 事务管理员 + 事务协调员

  • 事务管理员一般是业务层,事务A

  • 事务协调员一般是数据层,事务B

再次翻译下事务传播行为:事务传播行为是指事务协调员对于事务管理员的态度@Transactional中的propagation属性

下图来源于:B站视频,点击进入,,我觉得讲的很好,,建议大家去瞅瞅。。

image-20240404111405347

案例:往面转账案例添加一个记录日志的功能,要求转账成功之后,要给account_log表插入谁向谁转了多少钱

  1. 添加一个数据表

    create table account_log(out_id int,in_id int,money double
    );
    
  2. dao层接口添加方法

    public interface AccountDao {//转出@Update("update account set money = money - #{money} where id = #{outId}")int outMoney(@Param("outId") int outId, @Param("money")double money);//转入@Update("update account set money = money + #{money} where id = #{inId}")int inMoney(@Param("inId") int inId, @Param("money")double money);//记录日志@Insert("insert into account_log values(#{outId},#{inId},#{money})")void insertLog(@Param("outId") int outId, @Param("inId") int inId,@Param("money") double money);
    }
    
  3. service层的AccountServiceImpl修改为

    @Service
    public class AccountServiceImpl02 implements AccountService {@Autowiredprivate AccountDao dao;@Autowiredprivate DataSource dataSource;/**转账事务*/@Transactional(propagation = Propagation.REQUIRED)public void s1(int outId,int inId,double money){dao.outMoney(outId,money);//可能在转账过程中发生意外: 转出执行,转入还未执行dao.inMoney(inId,money);}/**记录日志事务*/@Transactional(propagation = Propagation.REQUIRES_NEW)public void s2(int outId,int inId,double money){dao.insertLog(outId,inId,money);}/**业务逻辑:1). 如果转账S1操作失败了, S1需要回滚(S1的操作肯定需要事务)如果S1有事务,S1跟随即可, 如果S1没有事务,S1需要自己创建事务所以S1适合设置传播行为属性为REQUIRED2). 如果S1成功,S2向数据库中插入日志如果转账S1操作失败了, S2不需要回滚,也向数据库中插入日志所以S2适合设置传播行为属性为REQUIRES_NEW			*/@Transactional@Overridepublic void transfer(int outId, int inId, double money) {s1(outId,inId,money);
    //            int i = 10 / 0;s2(outId,inId,money);}
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/594052.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

R语言数据操纵:常用函数

这篇文章主要介绍R语言中处理循环&#xff0c;排序&#xff0c;总结重要信息的常用函数。 处理循环的函数 lapply函数 这个函数就是俗称的一句话循环函数&#xff0c;不同于while循环或者for循环&#xff0c;这个函数可以实现一句话就是一个循环的效果。 具体格式为lapply(…

网络编程核心概念解析:IP地址、端口号与网络字节序深度探讨

⭐小白苦学IT的博客主页 ⭐初学者必看&#xff1a;Linux操作系统入门 ⭐代码仓库&#xff1a;Linux代码仓库 ❤关注我一起讨论和学习Linux系统 本节重点 认识IP地址, 端口号, 网络字节序等网络编程中的基本概念; 1.前言 网络编程&#xff0c;作为现代信息社会中的一项核心技术&…

c# wpf template ItemTemplate 简单试验

1.概要 ItemTemplate&#xff0c;定义列表类的控件形状 2.代码 2.1 控件 <Window x:Class"WpfApp2.Window2"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xml…

Spyder5.4.3升级到5.5.1

1.升级提示 2.根据升级提示,打开conda终端,分别执行上面命令 conda update anaconda 升级成功 安装Spyder5.5.1 用管理员身份运行下面命令: conda install spyder5.5.1 输入y进行更新 安装完成后出现一个done. 成功升级Spyder为5.5.1

(二)whatsapp 语音通话基本实现

Whatsapp VoiceCall 客户端通过websocket连接到服务器&#xff0c;客户端发起语音通话请求&#xff0c;并且完成必要的协商之后&#xff0c;就可以直接将语音数据发送给服务器&#xff0c;服务器接收到对方的语音数据之后也会通过websocket将语音数据转发给客户端。 websocke…

【数据分析面试】6.计算对话总数(SQL)

题目&#xff1a;计算对话总数 给定了名为 messenger_sends 的消息发送表格&#xff0c;找出总共有多少个唯一的对话。 注&#xff1a;在某些记录中&#xff0c;receiver_id 和 sender_id 从初始消息中互换了。这些记录应视为同一个对话。 示例&#xff1a; 输入&#xff1…

训练营十六天(二叉树part03)

104.二叉树的最大深度 力扣题目链接(opens new window) 题目 给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示例&#xff1a; 给定二叉树 [3,9,20,null,null,15,7]&…

挑选人力资源管理系统,专家推荐的6款必看!

在当今数字化时代&#xff0c;人力资源管理系统已成为企业高效运营和持续发展的重要工具。本文为您介绍的6款好用的人力资源管理系统有Zoho People、金蝶人力云、Workday、北森eHR、用友人力云、易路&#xff0c;帮助您找到最适合自己企业的解决方案。 一、Zoho People Zoho P…

构建开源可观测平台

企业始终面临着确保 IT 基础设施和应用程序全年可用的压力。现代架构&#xff08;容器、混合云、SOA、微服务等&#xff09;的复杂性不断增长&#xff0c;产生大量难以管理的日志。我们需要智能应用程序性能管理 (APM) 和可观察性工具来实现卓越生产并满足可用性和正常运行时间…

《搜广推算法指南》(2024版) 重磅发布!

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 结合…

从头开发一个RISC-V的操作系统(五)汇编语言编程

文章目录 前提RISC-V汇编语言入门RISC-V汇编指令总览汇编指令操作对象汇编指令编码格式add指令介绍无符号数 练习参考链接 目标&#xff1a;通过这一个系列课程的学习&#xff0c;开发出一个简易的在RISC-V指令集架构上运行的操作系统。 前提 这个系列的大部分文章和知识来自于…