spring事務有2種用法:編程式事務和聲明式事務。
編程式事務上一篇文章中已經介紹了,不熟悉的建議先看一下編程式事務的用法。
這篇主要介紹聲明式事務的用法,我們在工作中基本上用的都是聲明式事務,所以這篇文章是比較重要的,建議各位打起精神,正式開始。
什么是聲明式事務?
所謂聲明式事務,就是通過配置的方式,比如通過配置文件(xml)或者注解的方式,告訴spring,哪些方法需要spring幫忙管理事務,然后開發者只用關注業務代碼,而事務的事情spring自動幫我們控制。
比如注解的方式,只需在方法上面加一個@Transaction
注解,那么方法執行之前spring會自動開啟一個事務,方法執行完畢之后,會自動提交或者回滾事務,而方法內部沒有任何事務相關代碼,用起來特別的方法。
@Transaction
public void insert(String userName){
this.jdbcTemplate.update("insert into t_user (name) values (?)", userName);
}
聲明式事務的2種實現方式
- 配置文件的方式,即在spring xml文件中進行統一配置,開發者基本上就不用關注事務的事情了,代碼中無需關心任何和事務相關的代碼,一切交給spring處理。
- 注解的方式,只需在需要spring來幫忙管理事務的方法上加上@Transaction注解就可以了,注解的方式相對來說更簡潔一些,都需要開發者自己去進行配置,可能有些同學對spring不是太熟悉,所以配置這個有一定的風險,做好代碼review就可以了。
配置文件的方式這里就不講了,用的相對比較少,我們主要掌握注解的方式如何使用,就可以了。
聲明式事務注解方式5個步驟
1、啟用Spring的注釋驅動事務管理功能
在spring配置類上加上@EnableTransactionManagement
注解
@EnableTransactionManagement
public class MainConfig4 {
}
簡要介紹一下原理:當spring容器啟動的時候,發現有@EnableTransactionManagement注解,此時會攔截所有bean的創建,掃描看一下bean上是否有@Transaction注解(類、或者父類、或者接口、或者方法中有這個注解都可以),如果有這個注解,spring會通過aop的方式給bean生成代理對象,代理對象中會增加一個攔截器,攔截器會攔截bean中public方法執行,會在方法執行之前啟動事務,方法執行完畢之后提交或者回滾事務。稍后會專門有一篇文章帶大家看這塊的源碼。
如果有興趣的可以自己先去讀一下源碼,主要是下面這個這方法會
org.springframework.transaction.interceptor.TransactionInterceptor#invoke
再來看看 EnableTransactionManagement 的源碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
/**
* spring是通過aop的方式對bean創建代理對象來實現事務管理的
* 創建代理對象有2種方式,jdk動態代理和cglib代理
* proxyTargetClass:為true的時候,就是強制使用cglib來創建代理
*/
boolean proxyTargetClass() default false;
/**
* 用來指定事務攔截器的順序
* 我們知道一個方法上可以添加很多攔截器,攔截器是可以指定順序的
* 比如你可以自定義一些攔截器,放在事務攔截器之前或者之后執行,就可以通過order來控制
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}
2、定義事務管理器
事務交給spring管理,那么你肯定要創建一個或者多個事務管理者,有這些管理者來管理具體的事務,比如啟動事務、提交事務、回滾事務,這些都是管理者來負責的。
spring中使用PlatformTransactionManager這個接口來表示事務管理者。
PlatformTransactionManager多個實現類,用來應對不同的環境
JpaTransactionManager:如果你用jpa來操作db,那么需要用這個管理器來幫你控制事務。
DataSourceTransactionManager:如果你用是指定數據源的方式,比如操作數據庫用的是:JdbcTemplate、mybatis、ibatis,那么需要用這個管理器來幫你控制事務。
HibernateTransactionManager:如果你用hibernate來操作db,那么需要用這個管理器來幫你控制事務。
JtaTransactionManager:如果你用的是java中的jta來操作db,這種通常是分布式事務,此時需要用這種管理器來控制事務。
比如:我們用的是mybatis或者jdbctemplate,那么通過下面方式定義一個事務管理器。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
3、需使用事務的目標上加@Transaction注解
- @Transaction放在接口上,那么接口的實現類中所有public都被spring自動加上事務
- @Transaction放在類上,那么當前類以及其下無限級子類中所有pubilc方法將被spring自動加上事務
- @Transaction放在public方法上,那么該方法將被spring自動加上事務
- 注意:@Transaction只對public方法有效
下面我們看一下@Transactional源碼:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* 指定事務管理器的bean名稱,如果容器中有多事務管理器PlatformTransactionManager,
* 那么你得告訴spring,當前配置需要使用哪個事務管理器
*/
@AliasFor("transactionManager")
String value() default "";
/**
* 同value,value和transactionManager選配一個就行,也可以為空,如果為空,默認會從容器中按照類型查找一個事務管理器bean
*/
@AliasFor("value")
String transactionManager() default "";
/**
* 事務的傳播屬性
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* 事務的隔離級別,就是制定數據庫的隔離級別,數據庫隔離級別大家知道么?不知道的可以去補一下
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* 事務執行的超時時間(秒),執行一個方法,比如有問題,那我不可能等你一天吧,可能最多我只能等你10秒
* 10秒后,還沒有執行完畢,就彈出一個超時異常吧
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* 是否是只讀事務,比如某個方法中只有查詢操作,我們可以指定事務是只讀的
* 設置了這個參數,可能數據庫會做一些性能優化,提升查詢速度
*/
boolean readOnly() default false;
/**
* 定義零(0)個或更多異常類,這些異常類必須是Throwable的子類,當方法拋出這些異常及其子類異常的時候,spring會讓事務回滾
* 如果不配做,那么默認會在 RuntimeException 或者 Error 情況下,事務才會回滾
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* 和 rollbackFor 作用一樣,只是這個地方使用的是類名
*/
String[] rollbackForClassName() default {};
/**
* 定義零(0)個或更多異常類,這些異常類必須是Throwable的子類,當方法拋出這些異常的時候,事務不會回滾
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* 和 noRollbackFor 作用一樣,只是這個地方使用的是類名
*/
String[] noRollbackForClassName() default {};
}
參數介紹
參數 | 描述 |
---|---|
value | 指定事務管理器的bean名稱,如果容器中有多事務管理器PlatformTransactionManager,那么你得告訴spring,當前配置需要使用哪個事務管理器 |
transactionManager | 同value,value和transactionManager選配一個就行,也可以為空,如果為空,默認會從容器中按照類型查找一個事務管理器bean |
propagation | 事務的傳播屬性,下篇文章詳細介紹 |
isolation | 事務的隔離級別,就是制定數據庫的隔離級別,數據庫隔離級別大家知道么?不知道的可以去補一下 |
timeout | 事務執行的超時時間(秒),執行一個方法,比如有問題,那我不可能等你一天吧,可能最多我只能等你10秒 10秒后,還沒有執行完畢,就彈出一個超時異常吧 |
readOnly | 是否是只讀事務,比如某個方法中只有查詢操作,我們可以指定事務是只讀的 設置了這個參數,可能數據庫會做一些性能優化,提升查詢速度 |
rollbackFor | 定義零(0)個或更多異常類,這些異常類必須是Throwable的子類,當方法拋出這些異常及其子類異常的時候,spring會讓事務回滾 如果不配做,那么默認會在 RuntimeException 或者 Error 情況下,事務才會回滾 |
rollbackForClassName | 同 rollbackFor,只是這個地方使用的是類名 |
noRollbackFor | 定義零(0)個或更多異常類,這些異常類必須是Throwable的子類,當方法拋出這些異常的時候,事務不會回滾 |
noRollbackForClassName | 同 noRollbackFor,只是這個地方使用的是類名 |
4、執行db業務操作
在@Transaction標注類或者目標方法上執行業務操作,此時這些方法會自動被spring進行事務管理。
如,下面的insertBatch操作,先刪除數據,然后批量插入數據,方法上加上了@Transactional注解,此時這個方法會自動受spring事務控制,要么都成功,要么都失敗。
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
//先清空表中數據,然后批量插入數據,要么都成功要么都失敗
@Transactional
public void insertBatch(String... names) {
jdbcTemplate.update("truncate table t_user");
for (String name : names) {
jdbcTemplate.update("INSERT INTO t_user(name) VALUES (?)", name);
}
}
}
5、啟動spring容器,使用bean執行業務操作
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig4.class);
context.refresh();
UserService userService = context.getBean(UserService.class);
userService.insertBatch("java高並發系列", "mysql系列", "maven系列", "mybatis系列");
}
案例1
准備數據庫
DROP DATABASE IF EXISTS javacode2018;
CREATE DATABASE if NOT EXISTS javacode2018;
USE javacode2018;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名'
);
spring配置類
package com.javacode2018.tx.demo4;
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.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
@EnableTransactionManagement //@1
@Configuration
@ComponentScan
public class MainConfig4 {
//定義一個數據源
@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;
}
//定義一個JdbcTemplate,用來執行db操作
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
//定義我一個事物管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) { //@2
return new DataSourceTransactionManager(dataSource);
}
}
@1:使用@EnableTransactionManagement注解開啟spring事務管理
@2:定義事務管理器
來個業務類
package com.javacode2018.tx.demo4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
//先清空表中數據,然后批量插入數據,要么都成功要么都失敗
@Transactional //@1
public int insertBatch(String... names) {
int result = 0;
jdbcTemplate.update("truncate table t_user");
for (String name : names) {
result += jdbcTemplate.update("INSERT INTO t_user(name) VALUES (?)", name);
}
return result;
}
//獲取所有用戶信息
public List<Map<String, Object>> userList() {
return jdbcTemplate.queryForList("SELECT * FROM t_user");
}
}
@1:insertBatch方法上加上了@Transactional注解,讓spring來自動為這個方法加上事務
測試類
package com.javacode2018.tx.demo4;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo4Test {
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig4.class);
context.refresh();
UserService userService = context.getBean(UserService.class);
//先執行插入操作
int count = userService.insertBatch(
"java高並發系列",
"mysql系列",
"maven系列",
"mybatis系列");
System.out.println("插入成功(條):" + count);
//然后查詢一下
System.out.println(userService.userList());
}
}
運行輸出
插入成功(條):4
[{id=1, name=java高並發系列}, {id=2, name=mysql系列}, {id=3, name=maven系列}, {id=4, name=mybatis系列}]
有些朋友可能會問,如何知道這個被調用的方法有沒有使用事務? 下面我們就來看一下。
如何確定方法有沒有用到spring事務
方式1:斷點調試
spring事務是由TransactionInterceptor攔截器處理的,最后會調用下面這個方法,設置個斷點就可以看到詳細過程了。
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
方式2:看日志
spring處理事務的過程,有詳細的日志輸出,開啟日志,控制台就可以看到事務的詳細過程了。
添加maven配置
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
src\main\resources新建logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{MM-dd HH:mm:ss.SSS}][%thread{20}:${PID:- }][%X{trace_id}][%level][%logger{56}:%line:%method\(\)]:%msg%n##########**********##########%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="debug">
<appender-ref ref="STDOUT" />
</logger>
</configuration>
再來運行一下案例1
[09-10 11:20:38.830][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:370:getTransaction()]:Creating new transaction with name [com.javacode2018.tx.demo4.UserService.insertBatch]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
##########**********##########
[09-10 11:20:39.120][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:265:doBegin()]:Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] for JDBC transaction
##########**********##########
[09-10 11:20:39.125][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:283:doBegin()]:Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] to manual commit
##########**********##########
[09-10 11:20:39.139][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:502:update()]:Executing SQL update [truncate table t_user]
##########**********##########
[09-10 11:20:39.169][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.169][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.234][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.235][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.236][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.237][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.238][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.239][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.241][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:741:processCommit()]:Initiating transaction commit
##########**********##########
[09-10 11:20:39.241][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:328:doCommit()]:Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]]
##########**********##########
[09-10 11:20:39.244][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:387:doCleanupAfterCompletion()]:Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] after transaction
##########**********##########
插入成功(條):4
[09-10 11:20:39.246][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:427:query()]:Executing SQL query [SELECT * FROM t_user]
##########**********##########
[09-10 11:20:39.247][main: ][][DEBUG][org.springframework.jdbc.datasource.DataSourceUtils:115:doGetConnection()]:Fetching JDBC Connection from DataSource
##########**********##########
[{id=1, name=java高並發系列}, {id=2, name=mysql系列}, {id=3, name=maven系列}, {id=4, name=mybatis系列}]
來理解一下日志
insertBatch方法上有@Transaction注解,所以會被攔截器攔截,下面是在insertBatch方法調用之前,創建了一個事務。
insertBatch方法上@Transaction注解參數都是默認值,@Transaction注解中可以通過value或者transactionManager
來指定事務管理器,但是沒有指定,此時spring會在容器中按照事務管理器類型找一個默認的,剛好我們在spring容器中定義了一個,所以直接拿來用了。事務管理器我們用的是new DataSourceTransactionManager(dataSource)
,從事務管理器的datasource中獲取一個數據庫連接,然后通過連接設置事務為手動提交,然后將(datasource->這個連接)丟到ThreadLocal中了,具體為什么,可以看上一篇文章。
下面就正是進入insertBatch方法內部了,通過jdbctemplate執行一些db操作,jdbctemplate內部會通過datasource到上面的threadlocal中拿到spring事務那個連接,然后執行db操作。
最后insertBatch方法執行完畢之后,沒有任何異常,那么spring就開始通過數據庫連接提交事務了。
總結
本文講解了一下spring中編程式事務的使用步驟。
主要涉及到了2個注解:
@EnableTransactionManagement:開啟spring事務管理功能
@Transaction:將其加在需要spring管理事務的類、方法、接口上,只會對public方法有效。
大家再消化一下,有問題,歡迎留言交流。
下篇文章將詳細介紹事務的傳播屬性,敬請期待。
案例源碼
git地址:
https://gitee.com/javacode2018/spring-series
本文案例對應源碼:spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo4
路人甲java所有案例代碼以后都會放到這個上面,大家watch一下,可以持續關注動態。
參考:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648936892&idx=2&sn=473a156dc141a2efc0580f93567f0630&scene=21#wechat_redirect