之前使用 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提供下面两个方式控制事务:
Spring 控制方式基础是 PlatformTransactionManager 接口, 它为各种数据访问技术提供了统一的事务支持接口, 不同的数据技术都有自己的实现:
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 对象时, 需要指定一个 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 接口, 实参可以是:
使用 @Transactional 的要点有:
@Transactional 使用陷阱:
关于自调用问题和 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(); } }
<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 很直接, 不需要将代码先封装为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 , 需要进行事务控制的方法, 必须是 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