Spring系列第43篇:spring中编程式事务怎么用的?

star2017 1年前 ⋅ 279 阅读

本文开始,大概用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

本博客所有系列案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。

最新资料

更多内容请访问:IT源点

全部评论: 0

    我有话说: