专栏首页IT技术小咖TransactionTemplate编程式事务管理

TransactionTemplate编程式事务管理

Spring JdbcTemplate 事务控制

之前使用 JDBC API 操作, 经常用到的对象有: connection 和 preparedStatement. dbConnection.setAutoCommit(false); //transaction block start //some db manipulation dbConnection.commit(); //transaction block end

虽然通过 jdbcTemplate 可以获取 connection 对象, 但是我们不能使用 jdbcTemplate.getDataSource().getConnection().rollback() 命令式方式来控制事务, 因为这样获取到 connection 不并一定是执行SQL的那个connection.

Spring提供下面两个方式控制事务:

  1. 命令式事务控制方式. 使用 TransactionTemplate 类. 特点: 个人觉得 JdbcTemplate + TransactionTemplate 非常搭配, 都是轻量级, 都是命令式. 另外 TransactionTemplate 因为是写代码形式, 事务控制做到更细粒度.
  2. 声明式事务控制方式 (@Transactional) 将DB访问封装到 @Service/@Component 类中, 并将具体访问过程放到一个 public 方法中, 并加上 @Transactional 注解. 优点: 代码很简洁, 不仅适用于 JdbcTemplate, 而且适用于 Jpa/MyBatis 等数据库访问技术. 缺点: 事务控制粒度较粗, 只能做到函数粒度的事务控制, 无法做到代码块级的事务控制, 另外需要理解其背后是通过 AOP + proxy 方式实现的, 使用有比较多的讲究, 下文有提及.

Spring 事务控制的基础

Spring 控制方式基础是 PlatformTransactionManager 接口, 它为各种数据访问技术提供了统一的事务支持接口, 不同的数据技术都有自己的实现:

  • Spring JDBC 技术: DataSourceTransactionManager
  • JPA 技术: JpaTransactionManager
  • Hibernate 技术: HibernateTransactionManager
  • JDO 技术: JdoTransactionManager
  • 分布式事务: JtaTransactionManager

Spring Boot 项目中, 引入了 spring-boot-starter-jdbc 之后, 会自动注入一个 DataSourceTransactionManager 类型 bean 对象, 这个对象有两个名称, 分别为 transactionManager 和 platformTransactionManager . 引入了 spring-boot-starter-data-jpa 依赖后, 会自动注入一个 JpaTransactionManager 类型 bean 对象, 这个对象有两个名称, 分别为 transactionManager 和 platformTransactionManager.

如果我们项目有多个数据源, 或者既引入了 spring-boot-starter-jdbc, 又引入了 spring-boot-starter-data-jpa 依赖, 自动注入事务控制器就会混乱, 所以需要创建一个 TransactionManager configuration 类, 手动为不同数据源建立对应的 PlatformTransactionManager bean. 如果使用 @Transactional 注解控制事务, 需要指定对应的事务控制器, 比如 @Transactional(value="txManager1") .

@EnableTransactionManagementpublic class TransactionManagerConfig{
    @Bean        @Autowired    //自动注入 dataSource1    public PlatformTransactionManager txManager1(DataSource dataSource1) {        return new DataSourceTransactionManager(dataSource1);    }
    @Bean     @Autowired    //自动注入 dataSource2    public PlatformTransactionManager txManager2(DataSource dataSource2) {        return new DataSourceTransactionManager(dataSource2);    }    }

使用 TransactionTemplate 进行事务控制

生成 TransactionTemplate 对象时, 需要指定一个 Spring PlatformTransactionManager 接口的实现类. 因为我们使用的是 JdbcTemplate, 所以创建 TransactionTemplate 对象要传入 DataSourceTransactionManager 参数. 使用 TransactionTemplate 类控制事务, 我们只需要将数据访问代码封装成一个callback对象, 然后将callback对象传值给TransactionTemplate.execute()方法, 事务控制由TransactionTemplate.execute()完成.

TransactionTemplate.execute() 函数的主要代码:

public <T> T execute(TransactionCallback<T> action) throws TransactionException {    TransactionStatus status = this.transactionManager.getTransaction(this);    T result;    try {        result = action.doInTransaction(status);  //    }    catch (RuntimeException | Error ex) {        // Transactional code threw application exception -> rollback        rollbackOnException(status, ex);        throw ex;    }    catch (Throwable ex) {        // Transactional code threw unexpected exception -> rollback        rollbackOnException(status, ex);        throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");    }    this.transactionManager.commit(status);    return result;         }

从上面代码中可以看到, 要想要回滚数据库操作, 可以在callback对象的doInTransaction函数抛出异常, 或者在doInTransaction函数中可以控制 一个 TransactionStatus 接口的变量(transactionStatus 变量), 该TransactionStatus 接口为处理事务的代码提供一个简单的控制事务执行和查询事务状态的方法, 调用 transactionStatus.setRollbackOnly() 可以回滚事务.

TransactionTemplate.execute() 使用回调机制传参, 参数类型是 TransactionCallback 接口, 实参可以是:

  1. TransactionCallbackWithoutResult 类实例, 适合于事务没有返回值, 例如save、update、delete等等.
  2. TransactionCallback 虚拟类实例, TransactionCallback 泛型中的类型 T 是 doInTransaction() 函数的返回类型, 一般情况下这个 T 类型并不是很重要的, 直接使用 Object 类型即可. 也可以使用将Select的结果保存到这个返回值上.

使用 @Transactional 注解

使用 @Transactional 的要点有:

  1. 在DAO 层使用 JdbcTemplate 实现DB操作, 在 Service 的实现类上加上 @Transactional 注解, 不推荐在 Service 接口上加 @Transactional 注解.
  2. 需要进行事务控制的方法, 必须是 public 方法, 同时要打上 @Transactional 注解.
  3. 也可以在Class上加上 @Transactional 注解, 这样相当于给每个 public 函数加上了 @Transactional 注解, 当然我们还可以在其中的函数上加该注解, 这时候将以函数上的设置为准.

@Transactional 使用陷阱:

  1. 只有 public 方法打上 @Transactional 注解, 事务控制才能生效.
  2. 注意自调用问题, @Transactional 注解仅在外部类的调用才生效, 原因是使用 Spring AOP 机制造成的. 所以: 主调函数如果是本Service类, 应该也要打上 @Transactional, 否则事务控制被忽略.
  3. 缺省的情况下, 只有 RuntimeException 类异常才会触发回滚. 如果在事务中抛出其他异常,并期望回滚事务, 必须设定 rollbackFor 参数. 例子: @Transactional(propagation=Propagation.REQUIRED,rollbackFor= MyException.class)
  4. 如果主调函数和多个被调函数都加了 @Transactional 注解, 则整个主调函数将是一个统一的事务控制范围, 甚至它们分属多个Service也能被统一事务控制着
  5. 通常我们应该使用 Propagation.REQUIRED, 但需要说明的是, 如果一个非事务方法顺序调用了"两个不同service bean"的事务函数, 它们并不在同一个事务上下文中, 而是分属于不同的事务上下文.

关于自调用问题和 Public 的限制, 是因为Spring 使用了 Spring AOP 代理造成的, 如果要解决这两个问题, 使用 AspectJ 取代 Spring AOP 代理. 但并不推荐这么做, 更换底层AOP技术可能会引起其他副作用.

示例: 单个 Service 类的多个事务函数调用问题

Class ServiceImpl{    @Autowired    Dao dao;
    // 因为自调用问题, 直接调用 test() 将没有任何事务控制    public void test() {            test1();        test2();            }
    // 因为 testNew() 加了 @Transactional 注解, 所以形成了一个整体事务.     @Transactional     public void testNew() {            test1();        test2();            }    
    @Transactional    public void test1() {        dao.updateUser('1') ;    }
    @Transactional    public void test2() {        dao.updateUser('2') ;    }}

示例: 多个 Service 类的事务函数调用问题,

Class ServiceImplA{      @Autowired    Dao1 dao;
    @Transactional    public void test() {        dao.updateUser('1') ;       } }
Class ServiceImplB{      @Autowired    Dao2 dao;
    @Transactional    public void test() {        dao.updateOrder('2') ;      } }
Class Controller{    @Autowired    ServiceImplA serviceImplA;
    @Autowired    ServiceImplB serviceImplB;
    //serviceImplA.test() 和 serviceImplB.test() 并不是在同一个事务上下文中, 他们分别在各自的事务上下文中.    //原因是: 事务上下文是从属于主调bean的, 不同主调bean的事务是在不同的事务上下文中.     public void wholeTest() {       serviceImplA.test();       serviceImplB.test();    } }

pom.xml & application.properties & DB

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <scope>runtime</scope></dependency>

application.properties文件

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/world?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

在示例中使用了MySQL 官方提供的 sakila 样本数据库, 该数据用来模拟DVD租赁业务. 先 clone 一个actor_new 新表.

CREATE TABLE `actor_new` (  `actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,  `first_name` varchar(45) NOT NULL,  `last_name` varchar(45) NOT NULL,  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  PRIMARY KEY (`actor_id`),  KEY `idx_actor_last_name` (`last_name`)) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8
insert into actor_new  select * from actor ;

使用 TransactionTemplate 的java 代码

使用 TransactionTemplate 很直接, 不需要将代码先封装为class, 将我们的JdbcTemplate代码以匿名类的形式嵌入到 transTemplate.execute() 方法即可.

package com.example.demo;
import java.sql.SQLException;
import javax.sql.DataSource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.stereotype.Component;import org.springframework.transaction.TransactionStatus;import org.springframework.transaction.support.TransactionCallback;import org.springframework.transaction.support.TransactionTemplate;

@SpringBootApplicationpublic class TransTemplateApplication implements CommandLineRunner {    @Autowired    DataSource dataSource;
    @Autowired    JdbcTemplate jdbcTemplate;
    TransactionTemplate transTemplate;
    /*     * 该方法会被Spring自动在合适的时机调用, 用来初始化一个 TransactionTemplate 对象. 参数 dataSource 被自动注入.     */    @Autowired    private void transactionTemplate(DataSource dataSource) {        transTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource));    }
    public static void main(String[] args) throws Exception {        SpringApplication.run(TransTemplateApplication.class, args);    }
    @Override    public void run(String... args) throws Exception {        runTransactionSamples();    }

    /*     * 带有事务控制的DML 将DML操作放到 TransactionCallback类的doInTransaction()方法中.     * 只有在下面两种情况下才会回滚:     * 1. 通过设置 transactionStatus 为 RollbackOnly     * 2. 抛出任何异常     */    public void runTransactionSamples() throws SQLException {
        transTemplate.execute(new TransactionCallback<Object>() {            @Override            public Object doInTransaction(TransactionStatus transactionStatus) {                // DML执行                jdbcTemplate.update("Delete from actor_new where actor_id=?", 11);
                // 回滚                transactionStatus.setRollbackOnly();                return null;            }        });
    }}

使用 @Transactional 注解的 java 代码

使用 @Transactional , 需要进行事务控制的方法, 必须是 public 方法, 同时要打上 @Transactional 注解.

package com.example.demo;
import java.sql.SQLException;
import javax.sql.DataSource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;

@SpringBootApplicationpublic class AnnotationSampleApplication implements CommandLineRunner {
    @Autowired    ActorService actorService;
    public static void main(String[] args) throws Exception {        SpringApplication.run(AnnotationSampleApplication.class, args);    }
    @Override    public void run(String... args) throws Exception {        //actorService.runRollbackCase();        actorService.runCommitCase();    }}
/* * 自定义 RuntimeException 类 * */class MyRuntimeException extends RuntimeException {    private static final long serialVersionUID = -862367066204594182L;
    public MyRuntimeException(String msg) {        super(msg);    }}
/* * 自定义 非RuntimeException 类 * */class MyException extends Exception {    private static final long serialVersionUID = 8175536977672815349L;
    public MyException(String msg) {        super(msg);    }}
@Serviceclass ActorService {    @Autowired    DataSource dataSource;
    @Autowired    JdbcTemplate jdbcTemplate;
    /*     * 回滚事务的示例 -- 能回滚     */    @Transactional    public void runRollbackCase1() throws SQLException {        jdbcTemplate.update("Delete from actor_new where actor_id=?", 15);
        throw new RuntimeException("故意抛出异常来回滚事务.");    }
    /*     * 回滚事务的示例 -- 抛出 Non-RuntimeException 异常, 事务不能回滚     */    @Transactional    public void runRollbackCase2() throws SQLException, MyException {        jdbcTemplate.update("Delete from actor_new where actor_id=?", 13);        throw new MyException("故意抛出异常来回滚事务.");    }
    /*     * 回滚事务的示例 -- 抛出MyException异常, 并设置了 rollbackFor 参数, 事务能回滚     */    @Transactional(rollbackFor=MyException.class)    public void runRollbackCase3() throws SQLException, MyException {        jdbcTemplate.update("Delete from actor_new where actor_id=?", 14);        throw new MyException("故意抛出异常来回滚事务.");    }
    /*     * 回滚事务的示例 -- 抛出自定义RuntimeException异常, 事务能回滚     */    @Transactional    public void runRollbackCase4() throws SQLException {        jdbcTemplate.update("Delete from actor_new where actor_id=?", 14);        throw new MyRuntimeException("故意抛出异常来回滚事务.");    }
    /*     * 回滚事务的示例 -- 方法最后没有抛出异常, 不回滚     */    @Transactional    public void runRollbackCase5() throws SQLException {        jdbcTemplate.update("Delete from actor_new where actor_id=?", 14);        try {            throw new MyRuntimeException("故意抛出异常来回滚事务.");        } catch (Exception ex) {            System.out.println(ex.getMessage());        }    }
    /*     * runRollbackCase6 也是事务标注方法, 调用本类事务标注方法, 回滚     */    @Transactional    public void runRollbackCase6() throws SQLException {        runRollbackCase1();    }
    /*     * runRollbackCase7 为普通方法, 调用本类事务标注方法, 不回滚     */    public void runRollbackCase7() throws SQLException {        runRollbackCase1();    }
    /*     * 提交事务的示例     */    @Transactional()    public void runCommitCase() throws SQLException {        jdbcTemplate.update("Delete from actor_new where actor_id=?", 12);    }}

转自:http://t.cn/EJcAqhx

本文分享自微信公众号 - IT技术小咖(IT-arch)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 分布式任务调度框架 Elastic-Job 之动态任务发布实现详解

    任务调度 JDK 的几种实现方式如下: 1)多线程: 通过开启一个线程,while 循环执行业务逻辑,让线程 sleep 休眠,达到任务间隔执行。代码清单如...

    IT技术小咖
  • 基于GIS的合肥市BRT和Metro的交通可达性研究-part4

    看完《基于GIS的合肥市BRT和Metro的交通可达性研究》part1、part2、part3……系列文章,紧接着往下看......

    IT技术小咖
  • 一般实现分布式锁都有哪些方式?

    释放锁就是删除 key ,但是一般可以用 lua 脚本删除,判断 value 一样才删除:

    IT技术小咖
  • 初探Java设计模式1:创建型模式(工厂,单例等)

    转自https://javadoop.com/post/design-pattern

    Java技术江湖
  • OOAD-设计模式(三)之创建型设计模式(5种)

    前言   前面介绍了OOAD的基础知识,现在我们来详细的说明一下GOF设计模式中的23种模式,希望大家能够学到东西! 一、工厂方法模式(Factory Meth...

    用户1195962
  • 第二阶段-Java面向对象:【第三章 多态】

    当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

    BWH_Steven
  • 初探Java设计模式1:创建型模式(工厂,单例等)

    转自https://javadoop.com/post/design-pattern

    Java技术江湖
  • java准确的获取操作系统的名称

    在我们日常开发中,经常需要判断操作系统的版本或者系统的名字等等。这就需要我们用到jdk默认带的一些属性了。这里我对各个版本的系统都做了区分,分别能判断mac,l...

    业余草
  • 【程序源代码】《Spring Boot 开发笔记》简单搭建

    这套笔记和源码是我自己在学习springboot开发中实际一个字一个字敲出来的。因为这套开发笔记是逐步整理出来的,每期会介绍不同的技术开发点。所以请大家关注公众...

    程序源代码
  • 手把手带你开发一款 IIS 模块后门

    记得之前看一篇 APT 组织的报告时偶然间看到过 IIS 模块后门然后在网上找了找了资料,想自己开发一款然后开发到一半因为一些事情就停止了很久,这次清理项目文件...

    信安之路

扫码关注云+社区

领取腾讯云代金券