本篇文章主要介紹的是SpringBoot的事物Transaction使用的教程。html
說明:若是想直接獲取工程那麼能夠直接跳到底部,經過連接下載工程代碼。java
在Spring中,事務有兩種實現方式,分別是編程式事務管理和聲明式事務管理兩種方式。mysql
默認狀況下,數據庫處於自動提交模式。每一條語句處於一個單獨的事務中,在這條語句執行完畢時,若是執行成功則隱式的提交事務,若是執行失敗則隱式的回滾事務。
對於正常的事務管理,是一組相關的操做處於一個事務之中,所以必須關閉數據庫的自動提交模式。不過,這個咱們不用擔憂,spring會將底層鏈接的自動提交特性設置爲false。也就是在使用spring進行事物管理的時候,spring會將是否自動提交設置爲false,等價於JDBC中的 connection.setAutoCommit(false);
,在執行完以後在進行提交,connection.commit();
。git
隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:github
所謂事務的傳播行爲是指,若是在開始當前事務以前,一個事務上下文已經存在,此時有若干選項能夠指定一個事務性方法的執行行爲。在TransactionDefinition定義中包括了以下幾個表示傳播行爲的常量:web
指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,而後依據規則決定是否回滾拋出異常的事務。
默認配置下,spring只有在拋出的異常爲運行時unchecked異常時纔回滾該事務,也就是拋出的異常爲RuntimeException的子類(Errors也會致使事務回滾),而拋出checked異常則不會致使事務回滾。
能夠明確的配置在拋出那些異常時回滾事務,包括checked異常。也能夠明肯定義那些異常拋出時不回滾事務。spring
rollbackFor
註解指定須要回滾的異常或者將異常拋出交給調用的方法處理。一句話就是在使用事物的時候子方法最好將異常拋出!throw new RuntimeException();
。環境要求sql
JDK:1.8數據庫
SpringBoot:1.5.17.RELEASE編程
首先仍是Maven的相關依賴:
pom.xml文件以下:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.17.RELEASE</version> <relativePath /> </parent> <dependencies> <!-- Spring Boot Web 依賴 核心 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency> <!-- MySQL 鏈接驅動依賴 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</version> </dependency> <!-- Druid 數據鏈接池依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.8</version> </dependency> </dependencies>
application.properties
的文件的配置:
banner.charset=UTF-8 server.tomcat.uri-encoding=UTF-8 spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true spring.http.encoding.force=true spring.messages.encoding=UTF-8 spring.application.name=springboot-transactional server.port=8182 spring.datasource.url=jdbc:mysql://localhost:3306/springBoot?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.initialSize=5 spring.datasource.minIdle=5 spring.datasource.maxActive=20 spring.datasource.maxWait=60000 spring.datasource.timeBetweenEvictionRunsMillis=60000 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.validationQuery=SELECT 1 FROM DUAL spring.datasource.testWhileIdle=true spring.datasource.testOnBorrow=false spring.datasource.testOnReturn=false spring.datasource.poolPreparedStatements=true spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 spring.datasource.filters=stat,wall,log4j spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 logging.level.com.pancm.dao=debug
SpringBoot在使用事物Transactional的時候,要在main方法上加上 @EnableTransactionManagement
註解開發事物聲明,在使用的service層的公共方法加上 @Transactional
(spring)註解。
那麼首先咱們來看下 @Transactional
這個註解的使用方法吧,只須要你在須要添加公共方法上面添加該註解便可。可是這麼使用的話須要你將異常拋出,由spring進行去控制。
代碼示例:
@Transactional public boolean test1(User user) throws Exception { long id = user.getId(); System.out.println("查詢的數據1:" + udao.findById(id)); // 新增兩次,會出現主鍵ID衝突,看是否能夠回滾該條數據 udao.insert(user); System.out.println("查詢的數據2:" + udao.findById(id)); udao.insert(user); return false; }
若是咱們在使用事物 @Transactional
的時候,想本身對異常進行處理的話,那麼咱們能夠進行手動回滾事物。在catch中加上 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
方法進行手動回滾。不過須要注意的是發生異常須要第一時間進行手動回滾事物,也就是要在異常拋出以前!
代碼示例:
@Transactional public boolean test2(User user) { long id = user.getId(); try { System.out.println("查詢的數據1:" + udao.findById(id)); // 新增兩次,會出現主鍵ID衝突,看是否能夠回滾該條數據 udao.insert(user); System.out.println("查詢的數據2:" + udao.findById(id)); udao.insert(user); } catch (Exception e) { System.out.println("發生異常,進行手動回滾!"); // 手動回滾事物 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace(); } return false; }
若是咱們在使用事物 @Transactional
的時候,調用了其餘的子方法進行了數據庫的操做,可是咱們想使其事物生效的話,咱們可使用rollbackFor
註解或者將該子方法的異常拋出由調用的方法進行處理,不過這裏須要注意的是,子方法也必須是公共的方法!
代碼示例:
@Transactional public boolean test3(User user) { /* * 子方法出現異常進行回滾 */ try { System.out.println("查詢的數據1:" + udao.findById(user.getId())); deal1(user); deal2(user); deal3(user); } catch (Exception e) { System.out.println("發生異常,進行手動回滾!"); // 手動回滾事物 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace(); } return false; } public void deal1(User user) throws SQLException { udao.insert(user); System.out.println("查詢的數據2:" + udao.findById(user.getId())); } public void deal2(User user) throws SQLException{ if(user.getAge()<20){ //SQL異常 udao.insert(user); }else{ user.setAge(21); udao.update(user); System.out.println("查詢的數據3:" + udao.findById(user.getId())); } } @Transactional(rollbackFor = SQLException.class) public void deal3(User user) { if(user.getAge()>20){ //SQL異常 udao.insert(user); } }
若是咱們不想使用事物 @Transactional
註解,想本身進行事物控制(編程事物管理),控制某一段的代碼事物生效,可是又不想本身去編寫那麼多的代碼,那麼可使用springboot中的DataSourceTransactionManager
和TransactionDefinition
這兩個類來結合使用,可以達到手動控制事物的提交回滾。不過在進行使用的時候,須要注意在回滾的時候,要確保開啓了事物可是未提交,若是未開啓或已提交的時候進行回滾是會在catch裏面發生異常的!
代碼示例:
@Autowired private DataSourceTransactionManager dataSourceTransactionManager; @Autowired private TransactionDefinition transactionDefinition; public boolean test4(User user) { /* * 手動進行事物控制 */ TransactionStatus transactionStatus=null; boolean isCommit = false; try { transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); System.out.println("查詢的數據1:" + udao.findById(user.getId())); // 進行新增/修改 udao.insert(user); System.out.println("查詢的數據2:" + udao.findById(user.getId())); if(user.getAge()<20) { user.setAge(user.getAge()+2); udao.update(user); System.out.println("查詢的數據3:" + udao.findById(user.getId())); }else { throw new Exception("模擬一個異常!"); } //手動提交 dataSourceTransactionManager.commit(transactionStatus); isCommit= true; System.out.println("手動提交事物成功!"); throw new Exception("模擬第二個異常!"); } catch (Exception e) { //若是未提交就進行回滾 if(!isCommit){ System.out.println("發生異常,進行手動回滾!"); //手動回滾事物 dataSourceTransactionManager.rollback(transactionStatus); } e.printStackTrace(); } return false; }
上述的這幾種示例是比較常見使用的,基本能夠知足平常咱們對事物的使用,spring裏面還有一種事物的控制方法,就是設置斷點進行回滾。可是這種方法我的還沒實際驗證過,可靠性待確認。
使用方法以下:
Object savePoint =null; try{ //設置回滾點 savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); }catch(Exception e){ //出現異常回滾到savePoint。 TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); }
上面的使用示例介紹完畢以後,咱們再來介紹一下幾個主要的類。
首先仍是實體類:
實體類
又是萬能的用戶表
public class User { private Long id; private String name; private Integer age; //getter 和 setter 略 }
Controller 控制層
而後即是控制層,控制層這塊的我作了下最後的查詢,用於校驗事物是否成功生效!
控制層代碼以下:
@RestController @RequestMapping(value = "/api/user") public class UserRestController { @Autowired private UserService userService; @Autowired private UserDao userDao; @PostMapping("/test1") public boolean test1(@RequestBody User user) { System.out.println("請求參數:" + user); try { userService.test1(user); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("最後查詢的數據:" + userDao.findById(user.getId())); return true; } @PostMapping("/test2") public boolean test2(@RequestBody User user) { System.out.println("請求參數:" + user); userService.test2(user); System.out.println("最後查詢的數據:" + userDao.findById(user.getId())); return true; } @PostMapping("/test3") public boolean test3(@RequestBody User user) { System.out.println("請求參數:" + user); userService.test3(user); System.out.println("最後查詢的數據:" + userDao.findById(user.getId())); return true; } @PostMapping("/test4") public boolean test4(@RequestBody User user) { System.out.println("請求參數:" + user); userService.test4(user); System.out.println("最後查詢的數據:" + userDao.findById(user.getId())); return true; } }
App 入口
和普通的SpringBoot項目基本同樣,只不過須要加上 @EnableTransactionManagement
註解!
代碼以下:
@EnableTransactionManagement @SpringBootApplication public class TransactionalApp { public static void main( String[] args ) { SpringApplication.run(TransactionalApp.class, args); System.out.println("Transactional 程序正在運行..."); } }
咱們在啓動程序以後,來進行上述的幾個示例測試,這裏的測試示例分別對應上述的使用示例,有的示例須要測試兩邊以上才能驗證事物是否可以生效!這裏咱們使用Postman進行測試!
兩次測試,第一次不使用@Transactional
註解,第二次使用!
第一次測試:
註釋掉@Transactional
註解!
使用進行POST請求
http://localhost:8182/api/user/test1
Body參數爲:
{"id":1,"name":"xuwujing","age":18}
控制檯打印的數據:
請求參數:User [id=1, name=xuwujing, age=18] 查詢的數據1:null 查詢的數據2:User [id=1, name=xuwujing, age=18] Duplicate entry '1' for key 'PRIMARY' 最後查詢的數據:User [id=1, name=xuwujing, age=18]
第二次測試:
解除@Transactional
註解註釋!
使用進行POST請求
http://localhost:8182/api/user/test1
Body參數爲:
{"id":1,"name":"xuwujing","age":18}
控制檯打印的數據:
請求參數:User [id=1, name=xuwujing, age=18] 查詢的數據1:null 查詢的數據2:User [id=1, name=xuwujing, age=18] Duplicate entry '1' for key 'PRIMARY' 最後查詢的數據:null
注: 在第二次測試的以前是把第一次測試寫入數據庫的id爲1的數據個刪除了!
第一次測試中因爲沒有添加@Transactional
註解,所以發生了異常數據仍是寫入了,可是第二次測試中添加了@Transactional
註解,發現即便數據已經寫入了,可是出現了異常以後,數據最終被回滾了,沒有寫入!
從上述的測試用例中能夠看到測試用例一種的事物已經生效了!
因爲使用示例二中的代碼幾乎和使用示例一種的同樣,不一樣的是異常由咱們本身進行控制!
使用進行POST請求
http://localhost:8182/api/user/test2
Body參數爲:
{"id":1,"name":"xuwujing","age":18}
控制檯打印的數據:
請求參數:User [id=1, name=xuwujing, age=18] 查詢的數據1:null 查詢的數據2:User [id=1, name=xuwujing, age=18] 發生異常,進行手動回滾! Duplicate entry '1' for key 'PRIMARY' 最後查詢的數據:null
能夠看到事物生效了!
因爲使用示例三中進行了子方法調用,這裏咱們進行兩次測試,根據不一樣的請求條件來進行測試!
第一次測試:
使用進行POST請求
http://localhost:8182/api/user/test3
Body參數爲:
{"id":1,"name":"xuwujing","age":18}
控制檯打印的數據:
請求參數:User [id=1, name=xuwujing, age=18] 查詢的數據1:null 查詢的數據2:User [id=1, name=xuwujing, age=18] 發生異常,進行手動回滾! Duplicate entry '1' for key 'PRIMARY' 最後查詢的數據:null
第二次測試:
使用進行POST請求
http://localhost:8182/api/user/test3
Body參數爲:
{"id":1,"name":"xuwujing","age":21}
控制檯打印的數據:
請求參數:User [id=1, name=xuwujing, age=21] 查詢的數據1:null 查詢的數據2:User [id=1, name=xuwujing, age=21] 查詢的數據3:User [id=1, name=xuwujing2, age=21] 發生異常,進行手動回滾! Duplicate entry '1' for key 'PRIMARY' 最後查詢的數據:null
根據上述的兩次測試,能夠得出使用rollbackFor
註解或者將該子方法的異常拋出由調用的方法進行處理均可以使事物生效!
因爲使用示例四中進行了手動控制事物,這裏咱們進行兩次測試,根據不一樣的請求條件來進行測試!
第一次測試:
使用進行POST請求
http://localhost:8182/api/user/test4
Body參數爲:
{"id":1,"name":"xuwujing","age":18}
控制檯打印的數據:
請求參數:User [id=1, name=xuwujing, age=18] 查詢的數據1:null 查詢的數據2:User [id=1, name=xuwujing, age=18] 查詢的數據3:User [id=1, name=xuwujing2, age=20] 手動提交事物成功! 模擬第二個異常! 最後查詢的數據:User [id=1, name=xuwujing, age=20]
第二次測試:
事先仍是把數據庫id爲1的數據給刪除!
使用進行POST請求
http://localhost:8182/api/user/test4
Body參數爲:
{"id":1,"name":"xuwujing","age":21}
控制檯打印的數據:
請求參數:User [id=1, name=xuwujing, age=21] 查詢的數據1:null 查詢的數據2:User [id=1, name=xuwujing, age=21] 發生異常,進行手動回滾! 模擬一個異常! 最後查詢的數據:null
根據上述的兩次測試,咱們能夠得出使用手動控制事物徹底ok,只要提交了事物,即便後面發生了異常也不回影響以前的寫入!若是在控制的範圍之類發生了異常,也能夠進行回滾!
測試示例圖:
參考:
https://www.cnblogs.com/yepei/p/4716112.html
SpringBoot 事物Transaction的項目工程地址:
https://github.com/xuwujing/springBoot-study/tree/master/springboot-transactional
SpringBoot整個集合的地址:
https://github.com/xuwujing/springBoot-study
原創不易,若是感受不錯,但願給個推薦!您的支持是我寫做的最大動力! 版權聲明: 做者:虛無境 博客園出處:http://www.cnblogs.com/xuwujing CSDN出處:http://blog.csdn.net/qazwsxpcm 我的博客出處:http://www.panchengming.com