码迷,mamicode.com
首页 > 数据库 > 详细

SpringBoot系列: JdbcTemplate 事务控制

时间:2018-10-26 19:32:48      阅读:133      评论:0      收藏:0      [点我收藏+]

标签:require   utf-8   on()   没有   stat   for   roo   object   code   

============================
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() 命令式方式来控制事务.
我们可以使用下面两个方式实现事务控制:
1. 命令式事务控制方式.
    具体为使用 TransactionTemplate 或 PlatformTransactionManager, 推荐使用 TransactionTemplate 类.
    特点: 个人觉得 JdbcTemplate + TransactionTemplate 非常搭配, 都是轻量级, 都是命令式, 另外TransactionTemplate因为是写代码形式, 比较容易能对事务做到更加精细控制.
2. 声明式事务控制方式 (@Transactional)
    将DB访问封装到 @Service/@Component 类中, 并将具体访问过程放到一个 public 方法中, 并加上 @Transactional 注解.
    特点: 代码很简洁, 但事务精细控制稍弱, 不仅适用于 JdbcTemplate, 而且适用于 Jpa/MyBatis 等数据库访问技术.


============================
使用 TransactionTemplate 进行事务控制
============================
生成 TransactionTemplate 对象时, 需要指定一个 Spring PlatformTransactionManager 接口的实现类. Spring PlatformTransactionManager 是Spring 框架为各种数据访问技术提供事务支持的统一底层接口, 不同的数据技术都有自己的实现:
    Spring JDBC 技术: DataSourceTransactionManager
    JPA 技术: JpaTransactionManager
    Hibernate 技术: HibernateTransactionManager
    JDO 技术: JdoTransactionManager
    分布式事务: JtaTransactionManager

因为我们使用的是 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. 将 JdbcTemplate代码封装到 @Service/@Component 类, 让 Spring 负责管理对应的实例, 这时可以用到 AOP 技术自动注入事务控制代码, 这是 @Transactional 运行的基础.
2. 需要进行事务控制的方法, 必须是 public 方法, 同时要打上 @Transactional 注解.

@Transactional 使用陷阱:
1. 只有 public 方法打上@Transactional 注解, 事务控制才能生效.
2. 同一个 @Service/@Component 内部那些没有@Transactional 注解的方法, 如果调用了有@Transactional注解的方法, 事务控制被忽略. 如果主调函数也标了 @Transactional 注解, 事务控制有效.
3. @Transactional(rollbackFor= clazz) , 可以指定 rollbackFor 参数, 缺省的情况下, 只有 RuntimeException 类异常和 Error 才会触发回滚. 如果在事务中抛出其他异常,并期望回滚事务, 可以设定 rollbackFor 参数.
     例子: @Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)

前两个问题, 是由于使用 Spring AOP 代理造成的, 如果要解决这两个问题, 使用 AspectJ 取代 Spring AOP 代理.


============================
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 的要点有:
1. 将 JdbcTemplate代码封装到 @Service/@Component 类, 让 Spring 负责管理对应的实例, 这时可以用到 AOP 技术自动注入事务控制代码, 这是 @Transactional 运行的基础.
2. 需要进行事务控制的方法, 必须是 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 事务控制

标签:require   utf-8   on()   没有   stat   for   roo   object   code   

原文地址:https://www.cnblogs.com/harrychinese/p/SpringBoot_jdbc_transaction.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!