============================
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") .

@EnableTransactionManagement
public 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<T> 接口, 实参可以是:
1. TransactionCallbackWithoutResult 类实例, 适合于事务没有返回值, 例如save、update、delete等等.
2. TransactionCallback<T> 虚拟类实例, TransactionCallback<T> 泛型中的类型 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=toor
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; @SpringBootApplication
public 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; @SpringBootApplication
public 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);
}
} @Service
class 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);
}
}

==========================
参考
==========================
https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
https://spring.io/guides/gs/managing-transactions/#initial
https://blog.csdn.net/xrt95050/article/details/18076167
https://blog.csdn.net/zq9017197/article/details/6321391?utm_source=blogxgwz0
https://www.cnblogs.com/heartstage/p/3363640.html

SpringBoot系列: JdbcTemplate 事务控制的更多相关文章

  1. springboot学习3事务控制

    springboot学习3事务控制 spring的事务控制本质上是通过aop实现的. 在springboot中使用时,可以通过注解@Transactional进行类或者方法级别的事务控制,也可以自己通 ...

  2. SpringBoot系列: JdbcTemplate 快速入门

    对于一些小的项目, 我们没有必要使用MyBatis/JPA/Hibernate等重量级技术, 直接使用Spring JDBC 即可, Spring JDBC 是对 jdbc的简单封装, 很容易掌握. ...

  3. mysql 开发基础系列20 事务控制和锁定语句(上)

    一.概述 在mysql 里不同存储引擎有不同的锁,默认情况下,表锁和行锁都是自动获得的,不需要额外的命令, 有的情况下,用户需要明确地进行锁表或者进行事务的控制,以便确保整个事务的完整性.这样就需要使 ...

  4. springBoot service层 事务控制

    springBoot使用事物比较简单,在Application启动类s上添加@EnableTransactionManagement注解,然后在service层的方法上添加@Transactional ...

  5. mysql 开发基础系列21 事务控制和锁定语句(下)

    1.  隐含的执行unlock tables 如果在锁表期间,用start transaction命令来开始一个新事务,会造成一个隐含的unlock tables 被执行,如下所示: 会话1 会话2 ...

  6. SpringBoot系列七:SpringBoot 整合 MyBatis(配置 druid 数据源、配置 MyBatis、事务控制、druid 监控)

    1.概念:SpringBoot 整合 MyBatis 2.背景 SpringBoot 得到最终效果是一个简化到极致的 WEB 开发,但是只要牵扯到 WEB 开发,就绝对不可能缺少数据层操作,所有的开发 ...

  7. SpringBoot 系列教程之事务隔离级别知识点小结

    SpringBoot 系列教程之事务隔离级别知识点小结 上一篇博文介绍了声明式事务@Transactional的简单使用姿势,最文章的最后给出了这个注解的多个属性,本文将着重放在事务隔离级别的知识点上 ...

  8. SpringBoot 系列教程之编程式事务使用姿势介绍篇

    SpringBoot 系列教程之编程式事务使用姿势介绍篇 前面介绍的几篇事务的博文,主要是利用@Transactional注解的声明式使用姿势,其好处在于使用简单,侵入性低,可辨识性高(一看就知道使用 ...

  9. SpringBoot 系列教程之事务不生效的几种 case

    SpringBoot 系列教程之事务不生效的几种 case 前面几篇博文介绍了声明式事务@Transactional的使用姿势,只知道正确的使用姿势可能还不够,还得知道什么场景下不生效,避免采坑.本文 ...

随机推荐

  1. 基于DataTables实现根据每个用户动态显示隐藏列,可排序

      前言 在后台管理系统开发中,难免会出现列数太多的情况,这里提供一个解决方案:用户设置显示哪些列,每个用户互不影响,并且可以根据用户的习惯设置列的排序. 1.演示 2.html代码说明 3.java ...

  2. 【Python 04】Python开发环境概述

    1.Python概述 Python是一种计算机程序设计语言,一个python环境中需要有一个解释器和一个包集合. (1)Python解释器 使用python语言编写程序之前需要下载一个python解释 ...

  3. flink如何动态支持依赖jar包提交

    通常我们在编写一个flink的作业的时候,肯定会有依赖的jar包.flink官方希望你将所有的依赖和业务逻辑打成一个fat jar,这样方便提交,因为flink认为你应该对自己的业务逻辑做好单元测试, ...

  4. Configuring High Availability and Consistency for Apache Kafka

    To achieve high availability and consistency targets, adjust the following parameters to meet your r ...

  5. 浏览器各个版本和系统(chrome/safari/edge/qq/360)

    浏览器对象: let userAgent = navigator.userAgent.toLowerCase()console.log(userAgent) Edge: mozilla/5.0 (wi ...

  6. 001_ jQuery的表格插件dataTable详解

    一. 1.启用id为"datatable1"标签的html的表格jQuery库 $("#datatable1").dataTable( ) Reference: ...

  7. 如何将Bitcoin比特币区块链数据导入关系数据库

    在接触了比特币和区块链后,我一直有一个想法,就是把所有比特币的区块链数据放入到关系数据库(比如SQL Server)中,然后当成一个数据仓库,做做比特币交易数据的各种分析.想法已经很久了,但是一直没有 ...

  8. 关于wxpython多线程研究包括(import Publisher等错误研究)

    作为一个自动化测试人员,开发基本的应用桌面程序是必须的!最近在研究wxpython相关知识,目前看到多线程一块,发现官方文档介绍说:"在线程中不能修改修改窗口属性!",但是实际情况 ...

  9. ansible 与 Jinja2的结合

    1.文件架构 [root@master template]# tree . ├── jinj2_test.yml ├── meta ├── tasks ├── templates │   └── te ...

  10. 为什么很多IT公司不喜欢进过培训机构的人呢?

    转载原文链接:https://www.cnblogs.com/alex3714/p/9105765.html 这几天在知乎看到一个问题“为什么很多IT公司不喜欢进过培训机构的人呢?” 身为老男孩的教学 ...