Spring boot 中的事務管理,並基於junit進行測試

Spring boot 整合mybatis已經配置好了,這裡就不重新敘述,只是挑選出幾個和事務有關的地方。

首先做一些準備,開啟Spring boot 的事務比較簡單,只是需要spring-tx-4。3。6。RELEASE。jar這個包,這裡看自己專案的版本下載的jar。

Spring boot 中的事務管理,並基於junit進行測試

這個包也不用專門去pom配置,在我們配置Spring boot的時候就已經下載了。Spring 開啟註解事務只需要在啟動類上配置@EnableTransactionManagement,啟動註解事務,等同於傳統Spring 專案中xml配置

package com。wzh。application;import org。mybatis。spring。annotation。MapperScan;import org。springframework。boot。SpringApplication;import org。springframework。boot。autoconfigure。SpringBootApplication;import org。springframework。boot。context。properties。ConfigurationProperties;import org。springframework。context。annotation。Bean;import org。springframework。context。annotation。ComponentScan;import org。apache。ibatis。io。VFS;import org。apache。ibatis。session。SqlSessionFactory;import org。apache。tomcat。jdbc。pool。DataSource;import org。mybatis。spring。SqlSessionFactoryBean;import org。springframework。core。io。support。PathMatchingResourcePatternResolver;import org。springframework。jdbc。datasource。DataSourceTransactionManager;import org。springframework。transaction。PlatformTransactionManager;import org。springframework。boot。builder。SpringApplicationBuilder;import org。springframework。boot。web。support。SpringBootServletInitializer;import org。springframework。transaction。annotation。EnableTransactionManagement;/** * Spring Boot 應用啟動類,這裡繼承SpringBootServletInitializer並重寫SpringApplicationBuilder方法 * 是為了打包為war進行外部tomcat的部署 */@SpringBootApplication // Spring Boot 應用的標識@EnableTransactionManagement // 啟動註解事務,等同於傳統Spring 專案中xml配置@ComponentScan(basePackages = { “com。wzh”}) // 指定spring管理路徑,就是那些bean 註解的路徑@MapperScan({ “com。wzh。**。mapper” }) // mapper 介面類掃描包配置,兩個*為目錄萬用字元public class Application extends SpringBootServletInitializer{ // 程式啟動入口 public static void main(String[] args) { // 啟動嵌入式的 Tomcat 並初始化 Spring 環境及其各 Spring 元件 SpringApplication。run(Application。class, args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application。sources(Application。class); } // 建立資料來源,因為用是mybatis-spring 1。2 取消了資料來源的自動注入,所以這裡需要手動配置 @Bean @ConfigurationProperties(prefix = “spring。datasource”)// 指定資料來源的字首 ,在application。properties檔案中指定 public DataSource dataSource() { return new DataSource(); } // 建立SqlSessionFactory @Bean public SqlSessionFactory sqlSessionFactoryBean() throws Exception { //解決myBatis下 不能巢狀jar檔案的問題 VFS。addImplClass(SpringBootVFS。class); SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean。setDataSource(dataSource()); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); // 兩個*為目錄萬用字元 sqlSessionFactoryBean。setMapperLocations(resolver。getResources(“classpath:/mapper/**/*。xml”)); //指定掃描別名包的路徑,多個bean的掃描路徑,拼接以分號隔開 String typeAliasesPackage = “com。wzh。demo。domain;”; sqlSessionFactoryBean。setTypeAliasesPackage(typeAliasesPackage); return sqlSessionFactoryBean。getObject(); } // 建立事物管理器 @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); }}

下面來寫一個junit的啟動基類,並啟動測試一下

import com。wzh。application。Application;import org。junit。Test;import org。junit。runner。RunWith;import org。springframework。boot。test。context。SpringBootTest;import org。springframework。test。context。junit4。SpringJUnit4ClassRunner;import org。springframework。test。context。web。WebAppConfiguration;/** * junit 測試基類 *///SpringJUnit支援,由此引入Spring-Test框架支援!@RunWith(SpringJUnit4ClassRunner。class)//啟動類@SpringBootTest(classes = Application。class)//web專案支援@WebAppConfigurationpublic class BaseJunit { @Test public void runJunitTest() { System。out。println(“Junit 啟動測試”); }}

Spring boot 中的事務管理,並基於junit進行測試

顯示Junit是配置好了,下面我們來進行事務的配置,因為SpringBoot中已經支援事務,不需額外配置,這裡的配置是簡單的介紹下事務的隔離等級和常用的引數spring事務特性

spring所有的事務管理策略類都繼承自org。springframework。transaction。PlatformTransactionManager介面

其中TransactionDefinition介面定義以下特性:

事務隔離級別

隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 介面中定義了五個表示隔離級別的常量:

TransactionDefinition。ISOLATION_DEFAULT:這是預設值,表示使用底層資料庫的預設隔離級別。對大部分資料庫而言,通常這值就是TransactionDefinition。ISOLATION_READ_COMMITTED。TransactionDefinition。ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的資料。該級別不能防止髒讀,不可重複讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上並沒有此級別。TransactionDefinition。ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的資料。該級別可以防止髒讀,這也是大多數情況下的推薦值。TransactionDefinition。ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。該級別可以防止髒讀和不可重複讀。TransactionDefinition。ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。

事務傳播行為

所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括瞭如下幾個表示傳播行為的常量:TransactionDefinition。PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。這是預設值。TransactionDefinition。PROPAGATION_REQUIRES_NEW:建立一個新的事務,如果當前存在事務,則把當前事務掛起。TransactionDefinition。PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。TransactionDefinition。PROPAGATION_NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,則把當前事務掛起。TransactionDefinition。PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。TransactionDefinition。PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。TransactionDefinition。PROPAGATION_NESTED:如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於TransactionDefinition。PROPAGATION_REQUIRED。

事務超時

所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。

預設設定為底層事務系統的超時值,如果底層資料庫事務系統沒有設定超時值,那麼就是none,沒有超時限制。

事務在Spring中的有兩種,程式設計事務和註解事務,先說下@Transactional的引數

Spring boot 中的事務管理,並基於junit進行測試

註解事務測試

@Override @Transactional public int addUser(UserBean user) { //這裡做註解事務的測試,呼叫兩次add方法,然後報錯一次,看事務是否成功 int a1 = userDao。addUser(user); Integer。valueOf(“ABC”); int a2 = userDao。addUser(user); return (a1 + a2); }

這裡用的預設的事務等級,出錯就整個回滾,可以看到在第一個插入語句後故意呼叫方法出現轉換錯誤,測試回滾成功。

宣告式事務除了註解宣告,還可以顯示的程式設計宣告,程式設計宣告會加大程式碼的耦合程度,不過在某些場合下又不得不用,這裡就看專案的實際情況定了。

程式設計事務

package com。wzh。demo。service。impl;import java。util。List;import java。util。Map;import javax。annotation。Resource;import org。springframework。beans。factory。annotation。Autowired;import org。springframework。beans。factory。annotation。Qualifier;import org。springframework。stereotype。Service;import org。springframework。transaction。PlatformTransactionManager;import org。springframework。transaction。TransactionDefinition;import org。springframework。transaction。TransactionStatus;import org。springframework。transaction。annotation。Transactional;import org。springframework。transaction。support。TransactionCallback;import org。springframework。transaction。support。TransactionCallbackWithoutResult;import org。springframework。transaction。support。TransactionTemplate;import com。wzh。demo。dao。UserDao;import com。wzh。demo。domain。UserBean;import com。wzh。demo。service。IUserService;@Service(“userService”)public class UserServiceImpl implements IUserService{ /** * 程式設計事務物件 */ private final TransactionTemplate transactionTemplate; /** * 構造方法初始化事務模板 */ public UserServiceImpl(@Qualifier(“transactionManager”) PlatformTransactionManager transactionManager) { this。transactionTemplate = new TransactionTemplate(transactionManager); //設定事務等級 this。transactionTemplate。setIsolationLevel(TransactionDefinition。ISOLATION_READ_COMMITTED); //設定連線超時時間 30seconds this。transactionTemplate。setTimeout(30); } @Autowired @Qualifier(value=“userDao”) private UserDao userDao; @Override public int addUserByProgramming(final UserBean user) { //開啟程式設計事務 Integer i = (Integer) transactionTemplate。execute(new TransactionCallback() { @Override public Object doInTransaction(TransactionStatus status) { try { int a1 = userDao。addUser(user); Integer。valueOf(“ABC”); int a2 = userDao。addUser(user); return (a1 + a2); } catch (Exception e) { //異常回滾 status。setRollbackOnly(); e。printStackTrace(); } return 0; } }); return i; }}

junit測試

package userTest;import base。BaseJunit;import com。wzh。demo。dao。UserDao;import com。wzh。demo。domain。UserBean;import com。wzh。demo。service。IUserService;import org。junit。Assert;import org。junit。Test;import org。springframework。beans。factory。annotation。Autowired;import org。springframework。beans。factory。annotation。Qualifier;/** * user表測試類 */public class userTest01 extends BaseJunit{ @Autowired @Qualifier(value = “userDao”) private UserDao userDao; @Autowired @Qualifier(value = “userService”) private IUserService userService; /** * 新增測試 */ @Test public void testAddUser() { UserBean bean = new UserBean(); bean。setName(“程式設計事務測試”); bean。setSex(“男”); bean。setAge(33L); int i = userService。addUserByProgramming(bean); Assert。assertSame(2,i); }}