本文將深入研究Spring的事務管理。主要介紹@Transactional
在底層是如何工作的。之后的文章將介紹:
- propagation(事務傳播)和isolation(隔離性)等屬性的使用
- 事務使用的陷阱有哪些以及如何避免
JPA和事務管理
很重要的一點是JPA本身並不提供任何類型的聲明式事務管理。如果在依賴注入容器之外使用JPA,事務處理必須由開發人員編程實現。
1
2
3
4
5
6
7
8
9
10
11
12
|
UserTransaction utx = entityManager.getTransaction();
try
{
utx.begin();
businessLogic();
utx.commit();
}
catch
(Exception ex) {
utx.rollback();
throw
ex;
}
|
這種方式的事務管理使事務范圍可以在代碼中很清晰地表達出來,但它有以下缺點:
- 容易出現重復代碼和錯誤
- 任何錯誤可能產生較大的影響
- 錯誤難以調試和復現
- 降低了代碼庫的可讀性
- 如果該方法調用了其他的事務方法如何處理呢?
使用Spring @Transactional
使用Spring @Transactional
,上面的代碼就簡化為:
1
2
3
4
|
@Transactional
public
void
businessLogic() {
... use entity manager inside a transaction ...
}
|
代碼更加簡潔,可讀性更好,也是目前Spring中事務處理的推薦方式。
通過使用@Transactional
,事務傳播等很多重要方面可以自動處理。這種情況下如果businessLogic()
調用了其他事務方法,該方法將根據選項確定如何加入正在運行事務。
這個強大機制的一個潛在缺點是它隱藏了底層的運行,當它不能正常工作時很難調試。
@Transactional
含義
關於@Transactional
,關鍵點之一是要考慮兩個獨立的概念,它們都有各自的范圍和生命周期:
- persistence context(持久化上下文)
- database transaction(事務)
@Transactional
本身定義了單個事務的范圍。這個事務在persistence context的范圍內。
JPA中的持久化上下文是EntityManager
,內部實現使用了Hibernate Session
(使用Hibernate作為持久化provider)。
持久化上下文僅僅是一個同步對象,它記錄了有限集合的Java對象的狀態,並且保證這些對象的變化最終持久化到數據庫。
這是與單個事務非常不同的概念。一個Entity Manager可以跨越多個事務使用,而且的確是這樣使用的。
EntityManager何時跨越多個事務?
最常見的情況是應用使用Open Session In View模式處理懶初始化異常時,之前的文章介紹過這種做法的優勢和劣勢。
這種情況下視圖層運行的多個查詢處於獨立的事務中,而不是單事務的業務邏輯,但這些查詢由相同的entity manager管理。
另一種情況是開發人員將持久化上下文標記為PersistenceContextType.EXTENDED
,這表示它能夠響應多個請求。
如何定義EntityManager和Transaction之間的關系?
這由應用開發者來選擇,但是JPA Entity Manager最常用的方式是“Entity Manager per application transaction”(每個事務都有自己的實體管理器)模式。entity manager注入的常用方法是:
1
2
|
@PersistenceContext
private
EntityManager em;
|
這里默認為“Entity Manager per transaction”模式。這種模式下如果在@Transactional
方法內部使用該Entity Manager,那么該方法將在單一事務中運行。
@PersistenceContext如何工作?
隨之而來的問題就是@PersistenceContext
如何僅在容器啟動時注入entity manager,假定entity manager生命周期很短暫,而且每次請求需要多個entity manager。
答案是它不能:EntityManager
是一個接口,注入到spring bean中的不是entity manager本身,而是在運行時代理具體entity manager的context aware proxy(上下文感知代理)。
通常用於代理的具體類為SharedEntityManagerInvocationHandler
,借助調試器可以確認這一點。
那么@Transactional如何工作?
實現了EntityManager
接口的持久化上下文代理並不是聲明式事務管理的唯一部分,事實上包含三個組成部分:
- EntityManager Proxy本身
- 事務的切面
- 事務管理器
看一下這三部分以及它們之間的相互作用。
事務的切面
事務的切面是一個“around(環繞)”切面,在注解的業務方法前后都可以被調用。實現切面的具體類是TransactionInterceptor
。
事務的切面有兩個主要職責:
- 在’before’時,切面提供一個調用點,來決定被調用業務方法應該在正在進行事務的范圍內運行,還是開始一個新的獨立事務。
- 在’after’時,切面需要確定事務被提交,回滾或者繼續運行。
在’before’時,事務切面自身不包含任何決策邏輯,是否開始新事務的決策委派給事務管理器完成。
事務管理器
事務管理器需要解決下面兩個問題:
- 新的Entity Manager是否應該被創建?
- 是否應該開始新的事務?
這些需要事務切面’before’邏輯被調用時決定。事務管理器的決策基於以下兩點:
- 事務是否正在進行
- 事務方法的propagation屬性(比如
REQUIRES_NEW
總要開始新事務)
如果事務管理器確定要創建新事務,那么將:
- 創建一個新的entity manager
- entity manager綁定到當前線程
- 從數據庫連接池中獲取連接
- 將連接綁定到當前線程
使用ThreadLocal變量將entity manager和數據庫連接都綁定到當前線程。
事務運行時他們存儲在線程中,當它們不再被使用時,事務管理器決定是否將他們清除。
程序的任何部分如果需要當前的entity manager和數據庫連接都可以從線程中獲取。
EntityManager proxy
EntityManager proxy(前面已經介紹過)就是謎題的最后一部分。當業務方法調用entityManager.persist()
時,這不是由entity manager直接調用的。
而是業務方法調用代理,代理從線程獲取當前的entity manager,前面介紹過事務管理器將entity manager綁定到線程。
了解了@Transactional
機制的各個部分,我們來看一下實現它的常用Spring配置。
整合三個部分
如何將三個部分組合起來使事務注解可以正確地發揮作用呢?首先定義entity manager工廠。
這樣就可以通過持久化上下文注解注入Entity Manager proxy。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Configuration
public
class
EntityManagerFactoriesConfiguration {
@Autowired
private
DataSource dataSource;
@Bean
(name =
"entityManagerFactory"
)
public
LocalContainerEntityManagerFactoryBean emf() {
LocalContainerEntityManagerFactoryBean emf = ...
emf.setDataSource(dataSource);
emf.setPackagesToScan(
new
String[] {
"your.package"
});
emf.setJpaVendorAdapter(
new
HibernateJpaVendorAdapter());
return
emf;
}
}
|
下一步實現配置事務管理器和在@Transactional
注解的類中應用事務的切面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Configuration
@EnableTransactionManagement
public
class
TransactionManagersConfig {
@Autowired
EntityManagerFactory emf;
@Autowired
private
DataSource dataSource;
@Bean
(name =
"transactionManager"
)
public
PlatformTransactionManager transactionManager() {
JpaTransactionManager tm =
new
JpaTransactionManager();
tm.setEntityManagerFactory(emf);
tm.setDataSource(dataSource);
return
tm;
}
}
|
注解@EnableTransactionManagement
通知Spring,@Transactional
注解的類被事務的切面包圍。這樣@Transactional
就可以使用了。
總結
Spring聲明式事務管理機制非常強大,但它可能被誤用或者容易發生配置錯誤。
當這個機制不能正常工作或者未達到預期運行結果等問題出現時,理解它的內部工作情況是很有幫助的。
需要記住的最重要的一點是,要考慮到兩個概念:事務和持久化上下文,每個都有自己不可讀的明顯的生命周期。