序言:此前,我們主要通過XML配置Spring來托管事務。在SpringBoot則非常簡單,只需在業務層添加事務注解(@Transactional )即可快速開啟事務。雖然事務很簡單,但對於數據方面是需要謹慎對待的,識別常見坑點對我們開發有幫助。
1. 引入依賴
<!--依賴管理 --> <dependencies> <dependency> <!--添加Web依賴 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <!--添加Mybatis依賴 --> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <dependency><!--添加MySQL驅動依賴 --> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency><!--添加Test依賴 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
2. 添加配置
主要是配置數據源和開啟Mybatis的自動駝峰映射
@SpringBootApplication public class MybatisTransactionApplication { public static void main(String[] args) { //1.初始化 SpringApplication application= new SpringApplication(MybatisTransactionApplication.class); //2.添加數據源 Map<String,Object> map = new HashMap<>(); map.put("spring.datasource.url","jdbc:mysql://localhost:3306/socks?useSSL=false"); map.put("spring.datasource.username","root"); map.put("spring.datasource.password","root"); //3.開啟駝峰映射 (Such as account_id ==> accountId) map.put("mybatis.configuration.map-underscore-to-camel-case",true); application.setDefaultProperties(map); //4.啟動應用 application.run(args); } }
3. 添加數據庫記錄
打開 Navicat 的查詢窗口,然后執行以下SQL:
DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `account_id` varchar(30) , `account_name` varchar(30), `balance` decimal(20,2), PRIMARY KEY (`account_id`) ); insert into account values ('1','admin','1000.25');
執行完畢后,可以查詢到賬戶數據,如圖:

4. 編寫代碼
以操作賬戶金額為例,模擬正常操作金額提交事務,以及發生異常回滾事務。
其中控制層代碼如下:
package com.hehe.controller; @RestController public class AccountController { @SuppressWarnings("all") @Autowired AccountService accountService; @GetMapping("/") public Account getAccount() { //查詢賬戶 return accountService.getAccount(); } @GetMapping("/add") public Object addMoney() { try { accountService.addMoney(); } catch (Exception e) { return "發生異常了:" + accountService.getAccount(); } return getAccount(); } }
在業務層使用 @Transactional 開啟事務,執行數據庫操作后拋出異常。具體代碼如下:
package com.hehe.service; @Service public class AccountService { @SuppressWarnings("all") @Autowired AccountMapper accountMapper; public Account getAccount() { return accountMapper.getAccount(); } @Transactional public void addMoney() throws Exception { //先增加余額 accountMapper.addMoney(); //然后遇到故障 throw new RuntimeException("發生異常了.."); } }
數據庫層就很簡單了,我們通過注解來實現賬戶數據的查詢,具體如下:
package com.hehe.mapper; @Mapper public interface AccountMapper { @Select("select * from account where account_id=1") Account getAccount(); @Update("update account set balance = balance+100 where account_id=1") void addMoney(); }
其中 Account 實體對象如下:
package com.hehe.pojo; public class Account { private String accountId; private String accountName; private BigDecimal balance; // Override toString Method .. // Getter & Setters .. }
5. 測試事務
啟動應用,訪問 http://localhost:8080 ,可以看到賬戶數據,如下:

然后訪問 http://localhost:8080/add ,可以看到賬戶余額並沒有增加,如下: 也就是說事務開啟成功,數據得到回滾。

6. 常見坑點
使用事務注解@Transactional 之前,應該先了解它的相關屬性,避免在實際項目中踩中各種各樣的坑點。
常見坑點1:遇到檢測異常時,事務默認不回滾。
例如下面這段代碼,賬戶余額依舊增加成功,並沒有因為后面遇到SQLException(檢測異常)而進行事務回滾!!
@Transactional public void addMoney() throws Exception { //先增加余額 accountMapper.addMoney(); //然后遇到故障 throw new SQLException("發生異常了.."); }
原因分析:因為Spring的默認的事務規則是遇到運行異常(RuntimeException及其子類)和程序錯誤(Error)才會進行事務回滾,顯然SQLException並不屬於這個范圍。如果想針對檢測異常進行事務回滾,可以在@Transactional 注解里使用
rollbackFor 屬性明確指定異常。例如下面這樣,就可以正常回滾:
@Transactional(rollbackFor = Exception.class) public void addMoney() throws Exception { //先增加余額 accountMapper.addMoney(); //然后遇到故障 throw new SQLException("發生異常了.."); }
常見坑點2: 在業務層捕捉異常后,發現事務不生效。
這是許多新手都會犯的一個錯誤,在業務層手工捕捉並處理了異常,你都把異常“吃”掉了,Spring自然不知道這里有錯,更不會主動去回滾數據。例如:下面這段代碼直接導致增加余額的事務回滾沒有生效。
@Transactional public void addMoney() throws Exception { //先增加余額 accountMapper.addMoney(); //謹慎:盡量不要在業務層捕捉異常並處理 try { throw new SQLException("發生異常了.."); } catch (Exception e) { e.printStackTrace(); } }
不要小瞧了這些細節,往前暴露異常很大程度上很能夠幫我們快速定位問題,而不是經常在項目上線后出現問題,卻無法刨根知道哪里報錯。
推薦做法:若非實際業務要求,則在業務層統一拋出異常,然后在控制層統一處理。
@Transactional public void addMoney() throws Exception { //先增加余額 accountMapper.addMoney(); //推薦:在業務層將異常拋出 throw new RuntimeException("發生異常了.."); }