43.Spring中编程式事务怎么用的?

本文详细介绍了Spring框架中编程式事务的使用,包括通过PlatformTransactionManager和TransactionTemplate两种方式,以及它们在控制事务过程中的关键概念和示例代码。

本文开始,大概用10篇左右的文章来详解spring中事务的使用,吃透spring事务。

本文内容

详解spring中编程式事务的使用。

spring中使用事务的2种方式

spring使事务操作变的异常容易了,spring中控制事务主要有2种方式

  • 编程式事务:硬编码的方式
  • 声明式事务:大家比较熟悉的注解@Transaction的方式

编程式事务

什么是编程式事务?

通过硬编码的方式使用spring中提供的事务相关的类来控制事务。

编程式事务主要有2种用法

  • 方式1:通过PlatformTransactionManager控制事务
  • 方式2:通过TransactionTemplate控制事务

方式1:PlatformTransactionManager

这种是最原始的方式,代码量比较大,后面其他方式都是对这种方式的封装。

直接看案例,有详细的注释,大家一看就懂。

案例代码位置

准备sql

 
  1. DROP DATABASE IF EXISTS javacode2018;
  2. CREATE DATABASE if NOT EXISTS javacode2018;
  3. USE javacode2018;
  4. DROP TABLE IF EXISTS t_user;
  5. CREATE TABLE t_user(
  6. id int PRIMARY KEY AUTO_INCREMENT,
  7. name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名'
  8. );

maven配置

 
  1. <!-- JdbcTemplate需要的 -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-jdbc</artifactId>
  5. <version>5.2.3.RELEASE</version>
  6. </dependency>
  7. <!-- spring 事务支持 -->
  8. <dependency>
  9. <groupId>org.springframework</groupId>
  10. <artifactId>spring-tx</artifactId>
  11. <version>5.2.3.RELEASE</version>
  12. </dependency>

测试代码

代码中会用到JdbcTemplate,对这个不理解的可以看一下:JdbcTemplate使用详解

 
  1. @Test
  2. public void test1() throws Exception {
  3. //定义一个数据源
  4. org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
  5. dataSource.setDriverClassName("com.mysql.jdbc.Driver");
  6. dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
  7. dataSource.setUsername("root");
  8. dataSource.setPassword("root123");
  9. dataSource.setInitialSize(5);
  10. //定义一个JdbcTemplate,用来方便执行数据库增删改查
  11. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  12. //1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
  13. PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
  14. //2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
  15. TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
  16. //3.开启事务:调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus)对象
  17. TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
  18. //4.执行业务操作,下面就执行2个插入操作
  19. try {
  20. System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
  21. jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
  22. jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
  23. //5.提交事务:platformTransactionManager.commit
  24. platformTransactionManager.commit(transactionStatus);
  25. } catch (Exception e) {
  26. //6.回滚事务:platformTransactionManager.rollback
  27. platformTransactionManager.rollback(transactionStatus);
  28. }
  29. System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
  30. }

运行输出

 
  1. before:[]
  2. after:[{id=1, name=test1-1}, {id=2, name=test1-2}]

代码分析

代码中主要有5个步骤

步骤1:定义事务管理器PlatformTransactionManager

事务管理器相当于一个管理员,这个管理员就是用来帮你控制事务的,比如开启事务,提交事务,回滚事务等等。

spring中使用PlatformTransactionManager这个接口来表示事务管理器,

 
  1. public interface PlatformTransactionManager {
  2. //获取一个事务(开启事务)
  3. TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
  4. throws TransactionException;
  5. //提交事务
  6. void commit(TransactionStatus status) throws TransactionException;
  7. //回滚事务
  8. void rollback(TransactionStatus status) throws TransactionException;
  9. }

PlatformTransactionManager多个实现类,用来应对不同的环境

JpaTransactionManager:如果你用jpa来操作db,那么需要用这个管理器来帮你控制事务。

DataSourceTransactionManager:如果你用是指定数据源的方式,比如操作数据库用的是:JdbcTemplate、mybatis、ibatis,那么需要用这个管理器来帮你控制事务。

HibernateTransactionManager:如果你用hibernate来操作db,那么需要用这个管理器来帮你控制事务。

JtaTransactionManager:如果你用的是java中的jta来操作db,这种通常是分布式事务,此时需要用这种管理器来控制事务。

上面案例代码中我们使用的是JdbcTemplate来操作db,所以用的是DataSourceTransactionManager这个管理器。

 
  1. PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);

步骤2:定义事务属性TransactionDefinition

定义事务属性,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。

spring中使用TransactionDefinition接口来表示事务的定义信息,有个子类比较常用:DefaultTransactionDefinition。

关于事务属性细节比较多,篇幅比较长,后面会专门有文章来详解。

步骤3:开启事务

调用事务管理器的getTransaction方法,即可以开启一个事务

 
  1. TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);

这个方法会返回一个TransactionStatus表示事务状态的一个对象,通过TransactionStatus提供的一些方法可以用来控制事务的一些状态,比如事务最终是需要回滚还是需要提交。

执行了getTransaction后,spring内部会执行一些操作,为了方便大家理解,咱们看看伪代码:

 
  1. //有一个全局共享的threadLocal对象 resources
  2. static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
  3. //获取一个db的连接
  4. DataSource datasource = platformTransactionManager.getDataSource();
  5. Connection connection = datasource.getConnection();
  6. //设置手动提交事务
  7. connection.setAutoCommit(false);
  8. Map<Object, Object> map = new HashMap<>();
  9. map.put(datasource,connection);
  10. resources.set(map);

上面代码,将数据源datasource和connection映射起来放在了ThreadLocal中,ThreadLocal大家应该比较熟悉,用于在同一个线程中共享数据;后面我们可以通过resources这个ThreadLocal获取datasource其对应的connection对象。

步骤4:执行业务操作

我们使用jdbcTemplate插入了2条记录。

 
  1. jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
  2. jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");

大家看一下创建JdbcTemplate的代码,需要指定一个datasource

 
  1. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

再来看看创建事务管理器的代码

 
  1. PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);

2者用到的是同一个dataSource,而事务管理器开启事务的时候,会创建一个连接,将datasource和connection映射之后丢在了ThreadLocal中,而JdbcTemplate内部执行db操作的时候,也需要获取连接,JdbcTemplate会以自己内部的datasource去上面的threadlocal中找有没有关联的连接,如果有直接拿来用,若没找到将重新创建一个连接,而此时是可以找到的,那么JdbcTemplate就参与到spring的事务中了。

步骤5:提交 or 回滚

 
  1. //5.提交事务:platformTransactionManager.commit
  2. platformTransactionManager.commit(transactionStatus);
  3. //6.回滚事务:platformTransactionManager.rollback
  4. platformTransactionManager.rollback(transactionStatus);

方式2:TransactionTemplate

方式1中部分代码是可以重用的,所以spring对其进行了优化,采用模板方法模式就其进行封装,主要省去了提交或者回滚事务的代码。

案例代码位置

测试代码

 
  1. @Test
  2. public void test1() throws Exception {
  3. //定义一个数据源
  4. org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
  5. dataSource.setDriverClassName("com.mysql.jdbc.Driver");
  6. dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
  7. dataSource.setUsername("root");
  8. dataSource.setPassword("root123");
  9. dataSource.setInitialSize(5);
  10. //定义一个JdbcTemplate,用来方便执行数据库增删改查
  11. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  12. //1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
  13. PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
  14. //2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
  15. DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
  16. transactionDefinition.setTimeout(10);//如:设置超时时间10s
  17. //3.创建TransactionTemplate对象
  18. TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);
  19. /**
  20. * 4.通过TransactionTemplate提供的方法执行业务操作
  21. * 主要有2个方法:
  22. * (1).executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
  23. * (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
  24. * 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。
  25. * 那么什么时候事务会回滚,有2种方式:
  26. * (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
  27. * (2)execute方法或者executeWithoutResult方法内部抛出异常
  28. * 什么时候事务会提交?
  29. * 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
  30. */
  31. transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
  32. @Override
  33. public void accept(TransactionStatus transactionStatus) {
  34. jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
  35. jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");
  36. }
  37. });
  38. System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
  39. }

运行输出

 
  1. after:[{id=1, name=transactionTemplate-1}, {id=2, name=transactionTemplate-2}]

代码分析

TransactionTemplate,主要有2个方法:

executeWithoutResult:无返回值场景

executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作

 
  1. transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
  2. @Override
  3. public void accept(TransactionStatus transactionStatus) {
  4. //执行业务操作
  5. }
  6. });

execute:有返回值场景

<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作

 
  1. Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {
  2. @Nullable
  3. @Override
  4. public Integer doInTransaction(TransactionStatus status) {
  5. return jdbcTemplate.update("insert into t_user (name) values (?)", "executeWithoutResult-3");
  6. }
  7. });

通过上面2个方法,事务管理器会自动提交事务或者回滚事务。

什么时候事务会回滚,有2种方式

方式1

在execute或者executeWithoutResult内部执行transactionStatus.setRollbackOnly();将事务状态标注为回滚状态,spring会自动让事务回滚

方式2

execute方法或者executeWithoutResult方法内部抛出任意异常即可回滚。

什么时候事务会提交?

方法没有异常 && 未调用过transactionStatus.setRollbackOnly();

编程式事务正确的使用姿势

如果大家确实想在系统中使用编程式事务,那么可以参考下面代码,使用spring来管理对象,更简洁一些。

先来个配置类,将事务管理器PlatformTransactionManager、事务模板TransactionTemplate都注册到spring中,重用。

 
  1. package com.javacode2018.tx.demo3;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.ComponentScan;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.jdbc.core.JdbcTemplate;
  6. import org.springframework.jdbc.datasource.DataSourceTransactionManager;
  7. import org.springframework.transaction.PlatformTransactionManager;
  8. import org.springframework.transaction.support.TransactionTemplate;
  9. import javax.sql.DataSource;
  10. @Configuration
  11. @ComponentScan
  12. public class MainConfig3 {
  13. @Bean
  14. public DataSource dataSource() {
  15. org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
  16. dataSource.setDriverClassName("com.mysql.jdbc.Driver");
  17. dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
  18. dataSource.setUsername("root");
  19. dataSource.setPassword("root123");
  20. dataSource.setInitialSize(5);
  21. return dataSource;
  22. }
  23. @Bean
  24. public JdbcTemplate jdbcTemplate(DataSource dataSource) {
  25. return new JdbcTemplate(dataSource);
  26. }
  27. @Bean
  28. public PlatformTransactionManager transactionManager(DataSource dataSource) {
  29. return new DataSourceTransactionManager(dataSource);
  30. }
  31. @Bean
  32. public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
  33. return new TransactionTemplate(transactionManager);
  34. }
  35. }

通常我们会将业务操作放在service中,所以我们也来个service:UserService。

 
  1. package com.javacode2018.tx.demo3;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.jdbc.core.JdbcTemplate;
  4. import org.springframework.stereotype.Component;
  5. import org.springframework.transaction.support.TransactionTemplate;
  6. import java.util.List;
  7. @Component
  8. public class UserService {
  9. @Autowired
  10. private JdbcTemplate jdbcTemplate;
  11. @Autowired
  12. private TransactionTemplate transactionTemplate;
  13. //模拟业务操作1
  14. public void bus1() {
  15. this.transactionTemplate.executeWithoutResult(transactionStatus -> {
  16. //先删除表数据
  17. this.jdbcTemplate.update("delete from t_user");
  18. //调用bus2
  19. this.bus2();
  20. });
  21. }
  22. //模拟业务操作2
  23. public void bus2() {
  24. this.transactionTemplate.executeWithoutResult(transactionStatus -> {
  25. this.jdbcTemplate.update("insert into t_user (name) VALUE (?)", "java");
  26. this.jdbcTemplate.update("insert into t_user (name) VALUE (?)", "spring");
  27. this.jdbcTemplate.update("insert into t_user (name) VALUE (?)", "mybatis");
  28. });
  29. }
  30. //查询表中所有数据
  31. public List userList() {
  32. return jdbcTemplate.queryForList("select * from t_user");
  33. }
  34. }

bus1中会先删除数据,然后调用bus2,此时bus1中的所有操作和bus2中的所有操作会被放在一个事务中执行,这是spring内部默认实现的,bus1中调用executeWithoutResult的时候,会开启一个事务,而内部又会调用bus2,而bus2内部也调用了executeWithoutResult,bus内部会先判断一下上线文环境中有没有事务,如果有就直接参与到已存在的事务中,刚好发现有bus1已开启的事务,所以就直接参与到bus1的事务中了,最终bus1和bus2会在一个事务中运行。

上面bus1代码转换为sql脚本如下:

 
  1. start transaction; //开启事务
  2. delete from t_user;
  3. insert into t_user (name) VALUE ('java');
  4. insert into t_user (name) VALUE ('spring');
  5. insert into t_user (name) VALUE ('mybatis');
  6. commit;

来个测试案例,看一下效果

 
  1. package com.javacode2018.tx.demo3;
  2. import org.junit.Test;
  3. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  4. public class Demo3Test {
  5. @Test
  6. public void test1() {
  7. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
  8. UserService userService = context.getBean(UserService.class);
  9. userService.bus1();
  10. System.out.println(userService.userList());
  11. }
  12. }

运行test1()输出

 
  1. [{id=18, name=java}, {id=19, name=spring}, {id=20, name=mybatis}]

上面代码中,bus1或者bus2中,如果有异常或者执行transactionStatus.setRollbackOnly(),此时整个事务都会回滚,大家可以去试试!

总结一下

大家看了之后,会觉得这样用好复杂啊,为什么要这么玩?

的确,看起来比较复杂,代码中融入了大量spring的代码,耦合性比较强,不利于扩展,本文的目标并不是让大家以后就这么用,主要先让大家从硬编码上了解spring中事务是如何控制的,后面学起来才会更容易。

我们用的最多的是声明式事务,声明式事务的底层还是使用上面这种方式来控制事务的,只不过对其进行了封装,让我们用起来更容易些。

下篇文章将详解声明式事务的使用。

案例源码

 
  1. git地址:
  2. https://gitee.com/javacode2018/spring-series
  3. 本文案例对应源码模块:lesson-002-tx
### Spring框架的优势 Spring框架以其模块化设计、轻量级特性以及强大的功能集,成为Java企业级应用开发的首选框架之一。其主要优势包括: - **解耦性强**:通过IoC容器管理对象之间的依赖关系,减少了组件间的直接耦合。 - **易于测试**:由于依赖注入的存在,使得单元测试更加方便,可以轻松地替换依赖项。 - **丰富的功能支持**:除了核心的IoC和AOP支持外,还提供了数据访问抽象层(如JDBC、ORM)、事务管理、消息服务等。 - **良好的社区支持**:拥有庞大的开发者社区,文档齐全,问题解决速度快。 ### AOP的概念 面向切面编程(Aspect-Oriented Programming, AOP)是一种编程范式,旨在提高代码的模块化程度,尤其是那些跨越多个类的行为(称为横切关注点)。在Spring中,AOP主要用于日志记录、安全控制、事务管理等方面。通过定义切面(Aspects),可以在不修改业务逻辑代码的情况下添加这些通用功能[^3]。 ### IoC的原理 控制反转(Inversion of Control, IoC)是指将对象的创建权交给第三方容器来负责,而不是由程序本身决定。这样做的目的是为了降低组件间的耦合度。Spring中的IoC容器通过读取配置信息(XML或注解),实例化Bean,并自动装配它们之间的依赖关系[^2]。 ### Spring的主要模块 Spring框架由多个模块组成,主要包括但不限于以下几个部分: - **Core Container (Beans, Core, Context)**: 提供了IoC的基础支持。 - **Data Access/Integration**: 包含了对JDBC、ORM、事务的支持。 - **Web**: 支持创建Web应用程序,特别是与Servlet API集成。 - **AOP**: 实现了面向切面编程的功能。 - **Instrumentation**: 对类加载器进行增强以支持某些高级特性。 ### 常用的Bean注入方式 在Spring中,可以通过多种方式进行Bean的注入: - **构造器注入**:通过构造方法传递参数给新创建的对象。 - **设值注入(Setter Injection)**:利用setter方法设置属性值。 - **字段注入(Field Injection)**:直接在字段上使用@Autowired注解进行注入。 ### Spring中Bean的线程安全性 默认情况下,Spring中的Bean是单例模式(Singleton Scope),这意味着同一个Bean实例会被多个线程共享。因此,如果Bean包含状态信息,则需要确保它是线程安全的。通常建议保持无状态的服务类或者采用原型作用域(Prototype Scope)来避免并发问题。 ### Bean的作用域类型 Spring支持几种不同的Bean作用域: - **Singleton**:每个容器中仅有一个Bean实例。 - **Prototype**:每次请求都会创建一个新的Bean实例。 - **Request**:针对Web应用,每个HTTP请求都有一个独立的Bean实例。 - **Session**:同样适用于Web应用,每个HTTP会话期间有一个Bean实例。 ### Spring自动装配Bean的方式 Spring提供以下几种自动装配策略: - **no**:不执行自动装配。 - **byName**:根据名称匹配其他Bean。 - **byType**:根据类型匹配其他Bean。 - **constructor**:基于构造函数参数类型的匹配。 ### Spring事务的实现方法 Spring通过声明式事务管理和编程式事务管理两种方式来处理事务。声明式事务管理更常用,它允许通过XML配置文件或注解(如@Transactional)来定义事务边界和行为。 ### Spring事务隔离级别 Spring支持标准的SQL事务隔离级别,包括: - **DEFAULT** - **READ_UNCOMMITTED** - **READ_COMMITTED** - **REPEATABLE_READ** - **SERIALIZABLE** 此外,还可以指定是否为只读事务、超时时间等属性。 ### Spring MVC运行流程 当用户发送请求到服务器时,Spring MVC的工作流程大致如下: 1. 请求首先到达DispatcherServlet。 2. DispatcherServlet调用HandlerMapping找到合适的Controller。 3. Controller处理请求并返回ModelAndView对象。 4. ViewResolver解析视图名称得到实际的View。 5. 最后渲染Model数据生成最终响应返回给客户端。 ### Spring MVC组件结构 Spring MVC架构由几个关键组件构成: - **DispatcherServlet**:前端控制器,协调各个组件。 - **HandlerMapping**:确定哪个Controller应该处理当前请求。 - **Controller**:处理具体的业务逻辑。 - **ModelAndView**:封装模型数据和视图信息。 - **ViewResolver**:确定如何呈现结果给用户。 - **View**:负责展示数据给用户。 ### @RequestMapping和@Autowired注解的作用 - **@RequestMapping**:用于映射Web请求到特定的处理类或方法上。它可以指定URL路径、HTTP方法等。 - **@Autowired**:用来自动装配Bean。可以放在字段、构造器、方法等位置,让Spring自动寻找匹配的Bean注入进来。 ```java @RestController public class ExampleController { @Autowired private SomeService someService; @RequestMapping("/example") public String exampleMethod() { return someService.doSomething(); } } ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值