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

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);

步骤2:定义事务属性TransactionDefinition

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

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

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

步骤3:开启事务

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

TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);

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

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

//有一个全局共享的threadLocal对象 resources

static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>(“Transactional resources”);

//获取一个db的连接

DataSource datasource = platformTransactionManager.getDataSource();

Connection connection = datasource.getConnection();

//设置手动提交事务

connection.setAutoCommit(false);

Map<Object, Object> map = new HashMap<>();

map.put(datasource,connection);

resources.set(map);

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

步骤4:执行业务操作

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

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

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

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

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

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

PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);

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

步骤5:提交 or 回滚

//5.提交事务:platformTransactionManager.commit

platformTransactionManager.commit(transactionStatus);

//6.回滚事务:platformTransactionManager.rollback

platformTransactionManager.rollback(transactionStatus);

方式2:TransactionTemplate


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

案例代码位置

测试代码

@Test

public void test1() throws Exception {

//定义一个数据源

org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();

dataSource.setDriverClassName(“com.mysql.jdbc.Driver”);

dataSource.setUrl(“jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8”);

dataSource.setUsername(“root”);

dataSource.setPassword(“root123”);

dataSource.setInitialSize(5);

//定义一个JdbcTemplate,用来方便执行数据库增删改查

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)

PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);

//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。

DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();

transactionDefinition.setTimeout(10);//如:设置超时时间10s

//3.创建TransactionTemplate对象

TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);

/**

* 4.通过TransactionTemplate提供的方法执行业务操作

* 主要有2个方法:

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

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

* 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。

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

* (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态

* (2)execute方法或者executeWithoutResult方法内部抛出异常

* 什么时候事务会提交?

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

*/

transactionTemplate.executeWithoutResult(new Consumer() {

@Override

public void accept(TransactionStatus transactionStatus) {

jdbcTemplate.update(“insert into t_user (name) values (?)”, “transactionTemplate-1”);

jdbcTemplate.update(“insert into t_user (name) values (?)”, “transactionTemplate-2”);

}

});

System.out.println(“after:” + jdbcTemplate.queryForList(“SELECT * from t_user”));

}

运行输出

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

代码分析

TransactionTemplate,主要有2个方法:

executeWithoutResult:无返回值场景

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

transactionTemplate.executeWithoutResult(new Consumer() {

@Override

public void accept(TransactionStatus transactionStatus) {

//执行业务操作

}

});

execute:有返回值场景

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

Integer result = transactionTemplate.execute(new TransactionCallback() {

@Nullable

@Override

public Integer doInTransaction(TransactionStatus status) {

return jdbcTemplate.update(“insert into t_user (name) values (?)”, “executeWithoutResult-3”);

}

});

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

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

方式1

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

方式2

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

什么时候事务会提交?

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

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


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

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

package com.javacode2018.tx.demo3;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import org.springframework.transaction.PlatformTransactionManager;

import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;

@Configuration

@ComponentScan

public class MainConfig3 {

@Bean

public DataSource dataSource() {

org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();

dataSource.setDriverClassName(“com.mysql.jdbc.Driver”);

dataSource.setUrl(“jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8”);

dataSource.setUsername(“root”);

dataSource.setPassword(“root123”);

dataSource.setInitialSize(5);

return dataSource;

}

@Bean

public JdbcTemplate jdbcTemplate(DataSource dataSource) {

return new JdbcTemplate(dataSource);

}

@Bean

public PlatformTransactionManager transactionManager(DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

@Bean

public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {

return new TransactionTemplate(transactionManager);

}

}

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

package com.javacode2018.tx.demo3;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.stereotype.Component;

import org.springframework.transaction.support.TransactionTemplate;

import java.util.List;

@Component

public class UserService {

@Autowired

private JdbcTemplate jdbcTemplate;

@Autowired

private TransactionTemplate transactionTemplate;

//模拟业务操作1

public void bus1() {

this.transactionTemplate.executeWithoutResult(transactionStatus -> {

//先删除表数据

this.jdbcTemplate.update(“delete from t_user”);

//调用bus2

this.bus2();

});

}

//模拟业务操作2

public void bus2() {

this.transactionTemplate.executeWithoutResult(transactionStatus -> {

this.jdbcTemplate.update(“insert into t_user (name) VALUE (?)”, “java”);

this.jdbcTemplate.update(“insert into t_user (name) VALUE (?)”, “spring”);

this.jdbcTemplate.update(“insert into t_user (name) VALUE (?)”, “mybatis”);

});

}

//查询表中所有数据

public List userList() {

return jdbcTemplate.queryForList(“select * from t_user”);

}

}

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

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

start transaction; //开启事务

delete from t_user;

insert into t_user (name) VALUE (‘java’);

insert into t_user (name) VALUE (‘spring’);

insert into t_user (name) VALUE (‘mybatis’);

commit;

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

package com.javacode2018.tx.demo3;

import org.junit.Test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo3Test {

@Test

public void test1() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);

UserService userService = context.getBean(UserService.class);

userService.bus1();

System.out.println(userService.userList());

}

}

运行test1()输出

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

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

总结一下


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

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

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

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

案例源码


git地址:

https://gitee.com/javacode2018/spring-series

本文案例对应源码模块:lesson-002-tx

路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。

Spring系列


  1. Spring系列第1篇:为何要学spring?

  2. Spring系列第2篇:控制反转(IoC)与依赖注入(DI)

  3. Spring系列第3篇:Spring容器基本使用及原理

  4. Spring系列第4篇:xml中bean定义详解(-)

  5. Spring系列第5篇:创建bean实例这些方式你们都知道?

  6. Spring系列第6篇:玩转bean scope,避免跳坑里!

  7. Spring系列第7篇:依赖注入之手动注入

  8. Spring系列第8篇:自动注入(autowire)详解,高手在于坚持

  9. Spring系列第9篇:depend-on到底是干什么的?

  10. Spring系列第10篇:primary可以解决什么问题?

  11. Spring系列第11篇:bean中的autowire-candidate又是干什么的?

  12. Spring系列第12篇:lazy-init:bean延迟初始化

  13. Spring系列第13篇:使用继承简化bean配置(abstract & parent)

  14. Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?

  15. Spring系列第15篇:代理详解(Java动态代理&cglib代理)?

  16. Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)

  17. Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)

  18. Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)

  19. Spring系列第18篇:@import详解(bean批量注册)

  20. Spring系列第20篇:@Conditional通过条件来控制bean的注册

  21. Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)

  22. Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解

  23. Spring系列第23篇:Bean生命周期详解

  24. Spring系列第24篇:父子容器详解

  25. Spring系列第25篇:@Value【用法、数据来源、动态刷新】

  26. Spring系列第26篇:国际化详解

  27. Spring系列第27篇:spring事件机制详解

  28. Spring系列第28篇:Bean循环依赖详解

  29. Spring系列第29篇:BeanFactory扩展(BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor)

  30. Spring系列第30篇:jdk动态代理和cglib代理

  31. Spring系列第31篇:aop概念详解

  32. Spring系列第32篇:AOP核心源码、原理详解

  33. Spring系列第33篇:ProxyFactoryBean创建AOP代理

最后

由于篇幅限制,小编在此截出几张知识讲解的图解

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  1. Spring系列第24篇:父子容器详解

  2. Spring系列第25篇:@Value【用法、数据来源、动态刷新】

  3. Spring系列第26篇:国际化详解

  4. Spring系列第27篇:spring事件机制详解

  5. Spring系列第28篇:Bean循环依赖详解

  6. Spring系列第29篇:BeanFactory扩展(BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor)

  7. Spring系列第30篇:jdk动态代理和cglib代理

  8. Spring系列第31篇:aop概念详解

  9. Spring系列第32篇:AOP核心源码、原理详解

  10. Spring系列第33篇:ProxyFactoryBean创建AOP代理

最后

由于篇幅限制,小编在此截出几张知识讲解的图解

[外链图片转存中…(img-4kBoSvcu-1714766807244)]

[外链图片转存中…(img-yJ4MjYXI-1714766807244)]

[外链图片转存中…(img-eVo1Xjt4-1714766807245)]

[外链图片转存中…(img-l5Kug966-1714766807245)]

[外链图片转存中…(img-6E6toLur-1714766807245)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值