Spring提供了一致的事務管理抽象,這個抽象是Spring最重要的抽象之一, 它有如下的優點:
- 為不同的事務API提供一致的編程模型,如JTA、JDBC、Hibernate和MyBatis資料庫層 等;
- 提供比大多數事務API更簡單的,易于使用的編程式事務管理API;
- 完美整合Spring資料訪問抽象;
- 支持Spring宣告式事務管理;
這篇博客就來介紹Spring事務管理相關的內容,
事務簡介
什么是事務
事務(Transaction)一般是指對資料庫的一個或一組操作單元,
事務的作用
1、為資料庫操作提供了一個從失敗中恢復到正常狀態的方法,同時提供了資料庫即使在例外狀態下仍能保持一致性的方法,
2、當多個應用程式在并發訪問資料庫時,可以在這些應用程式之間提供一個隔離方法,以防止彼此的操作互相干擾,
當一個事務被提交給了DBMS(資料庫管理系統),則DBMS需要確保該事務中的所有操作都成功完成且其結果被永久保存在資料庫中,如果事務中有的操作沒有成功完成,則事務中的所有操作都需要被回滾,回到事務執行前的狀態(要么全執行,要么全都不執行);同時,該事務對資料庫或者其他事務的執行無影響,所有的事務都好像在獨立的運行,
事務的特點
事務具有4個屬性:原子性、一致性、隔離性、持久性,這四個屬性通常稱為ACID特性,
原子性(Atomicity):事務作為一個整體被執行,包含在其中的對資料庫的操作要么全部被執行,要么都不執行,
一致性(Consistency):事務應確保資料庫的狀態從一個一致狀態轉變為另一個一致狀態,一致狀態的含義是資料庫中的資料應滿足完整性約束,
隔離性(Isolation):多個事務并發執行時,一個事務的執行不應影響其他事務的執行,
持久性(Durability):一個事務一旦提交,他對資料庫的修改應該永久保存在資料庫中,
事務的隔離級別
在多個事務并發操作的程序中,如果控制不好隔離級別,就有可能產生臟讀、不可重復讀或者幻讀等讀現象,資料操作程序中利用資料庫的鎖機制或者多版本并發控制機制獲取更高的隔離等級,但是,隨著資料庫隔離級別的提高,資料的并發能力也會有所下降,所以,如何在并發性和隔離性之間做一個很好的權衡就成了一個至關重要的問題,
ANSI/ISO SQL定義的標準隔離級別有四種,從高到底依次為:可序列化(Serializable)、可重復讀(Repeatable reads)、提交讀(Read committed)、未提交讀(Read uncommitted),
- 讀未提交
未提交讀(READ UNCOMMITTED)是最低的隔離級別,通過名字我們就可以知道,在這種事務隔離級別下,一個事務可以讀到另外一個事務未提交的資料,未提交讀會導致臟讀
事務在讀資料的時候并未對資料加鎖,
務在修改資料的時候只對資料增加行級共享鎖,
- 讀已提交
提交讀(READ COMMITTED)也可以翻譯成讀已提交,通過名字也可以分析出,在一個事務修改資料程序中,如果事務還沒提交,其他事務不能讀該資料,讀已提交會導致不可重復讀,
事務對當前被讀取的資料加 行級共享鎖(當讀到時才加鎖),一旦讀完該行,立即釋放該行級共享鎖;
事務在更新某資料的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放,
- 可重復讀
可重復讀能保障一個事務在事務內讀到的某條資料是一致的,但是可重復讀不能解決幻讀的問題,就是在事務還沒結束時,其他事務又插入了一條新的資料,
事務在讀取某資料的瞬間(就是開始讀取的瞬間),必須先對其加 行級共享鎖,直到事務結束才釋放;
事務在更新某資料的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放
- 序列化
可序列化(Serializable)是最高的隔離級別,前面提到的所有的隔離級別都無法解決的幻讀,在可序列化的隔離級別中可以解決,
事務在讀取資料時,必須先對其加 表級共享鎖 ,直到事務結束才釋放;
事務在更新資料時,必須先對其加 表級排他鎖 ,直到事務結束才釋放,
下面是臟讀、不可重復讀和幻讀的解釋,
臟讀就是指當一個事務正在訪問資料,并且對資料進行了修改,而這種修改還沒有提交(commit)到資料庫中,這時,另外一個事務也訪問這個資料,然后使用了這個資料,因為這個資料是還沒有提交的資料,那么另外一個事務讀到的這個資料是臟資料,依據臟資料所做的操作可能是不正確的,
不可重復讀:在一個事務內,多次讀同一個資料,在這個事務還沒有結束時,另一個事務也訪問該同一資料,那么,在第一個事務的兩次讀資料之間,由于第二個事務的修改,那么第一個事務讀到的資料可能不一樣,這樣就發生了在一個事務內兩次讀到的資料是不一樣的,因此稱為不可重復讀,即原始讀取不可重復,
幻讀指的是一個事務在前后兩次查詢同一個范圍的時候,后一次查詢看到了前一次查詢沒有看到的資料行,幻讀專指“新插入的行”是不可重復讀(Non-repeatable reads)的一種特殊場景
Spring事務
Spring事務模型的優勢
事務可以分為本地事務和全域事務,這兩種事務都有一定程度的局限性,Spring框架的事務管理支持解決全域和本地事務模型的局限性,
1. 全域事務
全域事務可以讓你跨多個事務進行作業,比如你的事務同事包含多個關系型資料庫,也可以包含關系型資料庫和JMS事務,一般情況下都是通過JTA來實作全域事務,但是JTA一般需要具體的應用容器來支持,這就導致代碼的通用性較低,
下面舉個全域事務的列子,方便理解,
在電商網站上,在消費者點擊購買按鈕后,交易后臺會進行庫存檢查、下單、減庫存、更新訂單狀態等一連串的服務呼叫,每一個操作對應一個獨立的服務,服務一般會有獨立的資料庫,因此會產生分布式事務問題,分布式事務就是一種比較常見的全域事務,
2. 本地事務
本地事務和具體的某個事務關聯,比如說JDBC事務,本地事務比較簡單,但是不能實作分布式事務的功能,
Spring提供了統一方便的事務編程模型,可以解決上面本地事務和全域事務的局限,使用Spring的事務API進行事務管理,底層可以適應各種事務資源,
Spring事務抽象
Spring為提供統一的事務編程模型,提供相關的介面,主要介面如下:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
上面介面的getTransaction方法接收一個TransactionDefinition引數,回傳一個TransactionStatus 值,其中TransactionStatus 可能代表一個新的事務,或者回傳一個已經存在本次呼叫堆疊中的事務,(TransactionStatus 和具體的執行緒系結,可以自己寫代碼測驗下)
TransactionStatus介面定義如下,
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
宣告式事務管理
使用Spring的事務管理,推薦使用宣告式事務管理,Spring的宣告式事務管理是通過Spring的AOP功能實作的,

因為平時在開發程序中都是使用注解的方式使用宣告式事務,下面就介紹注解的方式,
step1:添加@EnableTransactionManagement注解
@Configuration
@EnableTransactionManagement
@MapperScan("com.csx.demo.spring.boot.dao")
public class MyBatisConfig {
}
step2:添加@Transactional注解到介面的實作,
@Service
@Transactional(readOnly = true,rollbackFor = Exception.class)
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public int saveSysUser(SysUser user) {
int i = sysUserMapper.insert(user);
return i;
}
}
使用Spring的宣告式事務就這么簡單,
當你使用Spring的AOP方式來使用事務的話,你添加@Transactional注解的方法一定要是
public的,不然事務不會生效,假如你需要讓非
public的方法生效,你需要使用AspectJ 的AOP實作,(說明:Spring的AOP功能有兩種實作方式,一種是Spring自己實作的AOP功能,主要是通過JDK動態代理或者CGLIB動態代理實作的,還有一種方式是整合AspectJ 這個第三方AOP框架實作的)
另外,@Transactional注解可以添加到介面、介面中的方法定義、類和類里面的方法,Spring團隊建議將注解加到具體的類和方法實作上,而不是加到介面定義上(原因見下面英文描述),當然,您可以將@Transactional注釋放在介面(或介面方法)上,但是只有在使用基于介面的代理時,才會像您期望的那樣作業,
The fact that Java annotations are not inherited from interfaces means that, if you use class-based proxies (
proxy-target-) or the weaving-based aspect (mode="aspectj"), the transaction settings are not recognized by the proxying and weaving infrastructure, and the object is not wrapped in a transactional proxy.
@Transactional注解的配置
@Transactional注解可以進行以下配置,
| Property | Type | Description |
|---|---|---|
| value | String |
一個專案中可以存在多個事務管理器,這個值用于指定具體使用哪個事務管理器, |
| propagation | enum: Propagation |
設定傳播機制 |
isolation |
enum: Isolation |
設定隔離級別(只有當傳播機制設定成 REQUIRED or REQUIRES_NEW時這個配置才生效) |
timeout |
int (in seconds of granularity) |
設定超時時間(以秒為單位,只有當傳播機制設定成 REQUIRED or REQUIRES_NEW時這個配置才生效) |
readOnly |
boolean |
只讀事務配置(只有當傳播機制設定成 REQUIRED or REQUIRES_NEW時這個配置才生效) |
rollbackFor |
Array of Class objects, which must be derived from Throwable. |
回滾的例外 |
rollbackForClassName |
Array of class names. The classes must be derived from Throwable. |
|
noRollbackFor |
Array of Class objects, which must be derived from Throwable. |
不回滾的例外 |
noRollbackForClassName |
Array of String class names, which must be derived from Throwable. |
假如我們沒有配置上面的屬性,這些屬性也都是有默認值的,
- The propagation setting is
PROPAGATION_REQUIRED. - The isolation level is
ISOLATION_DEFAULT. - The transaction is read-write.
- The transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
- Any
RuntimeExceptiontriggers rollback, and any checkedExceptiondoes not.(默認回滾RuntimeException )
多事務管理器
有時候專案中可能會存在多個事務管理器,比如JDBC事務,比如JMS事務,這時候我們可以通過transactionManager屬性指定,
public class TransactionalService {
@Transactional("jdbc")
public void setSomething(String name) { ... }
@Transactional("jms")
public void doSomething() { ... }
}
上面的jdbc和jms是指兩個事務管理器在Spring容器中Bean的名字,
事務的傳播機制
在TransactionDefinition這個類中定義了6中傳播機制的型別,
1. PROPAGATION_REQUIRED

2. PROPAGATION_REQUIRES_NEW

3. PROPAGATION_NESTED
只支持JDBC事務,
編程式事務管理
Spring框架提供兩種方式來進行編程式事務管理:
- The
TransactionTemplate. PlatformTransactionManager的實作,
Spring團隊推薦使用第一種方式進行編程式事務管理,
1. 使用TransactionTemplate進行事務管理
下面是使用TransactionTemplate進行事務管理的一個例子,
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TxApp.class)
public class TxTest {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Autowired
private TransactionTemplate transactionTemplate;
@Test
public void selectUserTest() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
//同一個sqlSession創建的Mapper
SysUserMapper mapper = sqlSession1.getMapper(SysUserMapper.class);
SysUser sysUser = new SysUser();
sysUser.setUsername("zyzl");
sysUser.setPassword("11");
//有回傳值的操作
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
try {
return mapper.insert(sysUser);
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
}
});
//沒回傳值的操作
transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
@Override
public void accept(TransactionStatus transactionStatus) {
try {
mapper.insert(sysUser);
} catch (Exception e) {
transactionStatus.setRollbackOnly();
throw e;
}
}
});
}
}
我們也可以通過TransactionTemplate來設定事務的隔離級別等屬性,
//設定隔離級別
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
//設定超時時間
transactionTemplate.setTimeout(30);
//設定傳播機制
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
對于不同的事務操作,如果需要不同的隔離級別和傳播機制的話,請使用不同的transactionTemplate,也就是說,你要創建不同的transactionTemplate物件來進行操作,
2. 使用PlatformTransactionManager進行事務管理
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
// 設定傳播機制
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//開啟事務
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
//提交事務
txManager.commit(status);
事務系結事件
使用@TransactionalEventListener可以在事務提交前后,回滾后等階段觸發某些操作,但是這個功能暫時還沒想到很好的使用場景,后續有需要再來用,
@Component
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
// ...
}
}
重要類和介面
- PlatformTransactionManager:事務管理器,用于獲取事務,提交回滾事務;
- TransactionDefinition:
- TransactionStatus:代表一個事務
事務為什么要加在service層
一個Service的方法可能會呼叫多個Dao方法,如果將事務配置在Dao層,就會提交多個事務,這樣就會出現一種情況,第一事務提交成功,但是第二的Dao事務失敗了,就無法回滾第一個Dao提交的事務,
如果將事務加在controller層,事務的范圍比較大,而且controller層的內容基本都不涉及資料庫操作,
綜合下來,還是將事務加在service比較合理,
進一步閱讀
Distributed transactions in Spring, with and without XA is a JavaWorld presentation in which Spring’s David Syer guides you through seven patterns for distributed transactions in Spring applications, three of them with XA and four without.(Spring實作分布式事務的介紹)
參考閱讀
- spring transaction原始碼分析--事務架構
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/161017.html
標籤:Java
