混合事務(wù)
在ORM框架的事務(wù)管理器的事務(wù)內(nèi),使用JdbcTemplate執(zhí)行SQL是不會(huì)納入事務(wù)管理的。
下面進(jìn)行源碼分析,看為什么必須要在DataSourceTransactionManager的事務(wù)內(nèi)使用JdbcTemplate。
1.開啟事務(wù)
DataSourceTransactionManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
protected void doBegin(Object transaction,TransactionDefinition definition) { DataSourceTransactionObjecttxObject = (DataSourceTransactionObject) transaction; Connection con = null ; try { if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()){ ConnectionnewCon = this .dataSource.getConnection(); if (logger.isDebugEnabled()) { logger.debug( "AcquiredConnection [" + newCon + "] for JDBC transaction" ); } txObject.setConnectionHolder(newConnectionHolder(newCon), true ); } txObject.getConnectionHolder().setSynchronizedWithTransaction( true ); con =txObject.getConnectionHolder().getConnection(); IntegerpreviousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con,definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); // Switch to manualcommit if necessary. This is very expensive in some JDBC drivers, // so we don't wantto do it unnecessarily (for example if we've explicitly // configured theconnection pool to set it already). if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit( true ); if (logger.isDebugEnabled()) { logger.debug( "SwitchingJDBC Connection [" + con + "] to manual commit" ); } con.setAutoCommit( false ); } txObject.getConnectionHolder().setTransactionActive( true ); int timeout =determineTimeout(definition); if (timeout !=TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } // Bind the sessionholder to the thread. if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(getDataSource(),txObject.getConnectionHolder()); } } catch (Exception ex) { DataSourceUtils.releaseConnection(con, this .dataSource); throw newCannotCreateTransactionException( "Could not open JDBC Connection fortransaction" , ex); } } |
doBegin()方法會(huì)以數(shù)據(jù)源名為Key,ConnectionHolder(保存著連接)為Value,將已經(jīng)開啟事務(wù)的數(shù)據(jù)庫連接綁定到一個(gè)ThreadLocal變量上。
2.綁定連接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public static void bindResource(Objectkey, Object value) throws IllegalStateException { Object actualKey =TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); Assert.notNull(value, "Value must not be null" ); Map<Object, Object> map = resources.get(); // set ThreadLocal Map ifnone found if (map == null ) { map = newHashMap<Object, Object>(); resources.set(map); } Object oldValue = map.put(actualKey, value); // Transparently suppress aResourceHolder that was marked as void... if (oldValue instanceofResourceHolder && ((ResourceHolder) oldValue).isVoid()) { oldValue = null ; } if (oldValue != null ) { throw newIllegalStateException( "Already value [" + oldValue + "] for key[" + actualKey+ "] bound to thread [" + Thread.currentThread().getName() + "]" ); } if (logger.isTraceEnabled()){ logger.trace( "Boundvalue [" + value + "] for key [" + actualKey + "] to thread[" + Thread.currentThread().getName()+ "]" ); } } |
resources變量就是上面提到的ThreadLocal變量,這樣后續(xù)JdbcTemplate就可以用DataSource作為Key,查找到這個(gè)數(shù)據(jù)庫連接。
3.執(zhí)行SQL
JdbcTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
public Objectexecute(PreparedStatementCreator psc, PreparedStatementCallback action) throwsDataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null" ); Assert.notNull(action, "Callback object must not be null" ); if (logger.isDebugEnabled()){ String sql =getSql(psc); logger.debug( "Executingprepared SQL statement" + (sql != null ? " [" + sql + "]" : "" )); } Connection con = DataSourceUtils.getConnection(getDataSource()); PreparedStatement ps = null ; try { Connection conToUse= con; if ( this .nativeJdbcExtractor != null && this .nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()){ conToUse = this .nativeJdbcExtractor.getNativeConnection(con); } ps =psc.createPreparedStatement(conToUse); applyStatementSettings(ps); PreparedStatementpsToUse = ps; if ( this .nativeJdbcExtractor != null ) { psToUse = this .nativeJdbcExtractor.getNativePreparedStatement(ps); } Object result =action.doInPreparedStatement(psToUse); handleWarnings(ps); return result; } catch (SQLException ex) { // ReleaseConnection early, to avoid potential connection pool deadlock // in the case whenthe exception translator hasn't been initialized yet. if (psc instanceofParameterDisposer) { ((ParameterDisposer)psc).cleanupParameters(); } String sql =getSql(psc); psc = null ; JdbcUtils.closeStatement(ps); ps = null ; DataSourceUtils.releaseConnection(con,getDataSource()); con = null ; throwgetExceptionTranslator().translate( "PreparedStatementCallback" , sql,ex); } finally { if (psc instanceofParameterDisposer) { ((ParameterDisposer)psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con,getDataSource()); } } |
4.獲得連接
DataSourceUtils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public static Connection doGetConnection(DataSourcedataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified" ); ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && (conHolder.hasConnection() ||conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug( "Fetchingresumed JDBC Connection from DataSource" ); conHolder.setConnection(dataSource.getConnection()); } returnconHolder.getConnection(); } // Else we either got noholder or an empty thread-bound holder here. logger.debug( "FetchingJDBC Connection from DataSource" ); Connection con =dataSource.getConnection(); if (TransactionSynchronizationManager.isSynchronizationActive()){ logger.debug( "Registeringtransaction synchronization for JDBC Connection" ); // Use sameConnection for further JDBC actions within the transaction. // Thread-boundobject will get removed by synchronization at transaction completion. ConnectionHolderholderToUse = conHolder; if (holderToUse == null ) { holderToUse= new ConnectionHolder(con); } else { holderToUse.setConnection(con); } holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization( newConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction( true ); if (holderToUse !=conHolder) { TransactionSynchronizationManager.bindResource(dataSource,holderToUse); } } return con; } |
由此可見,DataSourceUtils也是通過TransactionSynchronizationManager獲得連接的。所以只要JdbcTemplate與DataSourceTransactionManager有相同的DataSource,就一定能得到相同的數(shù)據(jù)庫連接,自然就能正確地提交、回滾事務(wù)。
再以Hibernate為例來說明開篇提到的問題,看看為什么ORM框架的事務(wù)管理器不能管理JdbcTemplate。
5 ORM事務(wù)管理器
HibernateTransactionManager
1
2
3
|
if (txObject.isNewSessionHolder()) { TransactionSynchronizationManager.bindResource(getSessionFactory(),txObject.getSessionHolder()); } |
因?yàn)镺RM框架都不是直接將DataSource注入到TransactionManager中使用的,而是像上面Hibernate事務(wù)管理器一樣,使用自己的SessionFactory等對(duì)象來操作DataSource。所以盡管可能SessionFactory和JdbcTemplate底層都是一樣的數(shù)據(jù)源,但因?yàn)樵赥ransactionSynchronizationManager中綁定時(shí)使用了不同的Key(一個(gè)是sessionFactory名,一個(gè)是dataSource名),所以JdbcTemplate執(zhí)行時(shí)是拿不到ORM事務(wù)管理器開啟事務(wù)的那個(gè)數(shù)據(jù)庫連接的。
bean的區(qū)分
一個(gè)公共工程中的Spring配置文件,可能會(huì)被多個(gè)工程引用。因?yàn)槊總€(gè)工程可能只需要公共工程中的一部分Bean,所以這些工程的Spring容器啟動(dòng)時(shí),需要區(qū)分開哪些Bean要?jiǎng)?chuàng)建出來。
1.應(yīng)用實(shí)例
以Apache開源框架Jetspeed中的一段配置為例:page-manager.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
< bean name = "xmlPageManager" class = "org.apache.jetspeed.page.psml.CastorXmlPageManager" init-method = "init" destroy-method = "destroy" > < meta key = "j2:cat" value = "xmlPageManager orpageSerializer" /> < constructor-arg index = "0" > < ref bean = "IdGenerator" /> </ constructor-arg > < constructor-arg index = "1" > < refbean = "xmlDocumentHandlerFactory" /> </ constructor-arg > …… </ bean > < bean id = "dbPageManager" class = "org.apache.jetspeed.page.impl.DatabasePageManager" init-method = "init" destroy-method = "destroy" > < meta key = "j2:cat" value = "dbPageManager orpageSerializer" /> <!-- OJB configuration file resourcepath --> < constructor-arg index = "0" > < value >JETSPEED-INF/ojb/page-manager-repository.xml</ value > </ constructor-arg > <!-- fragment id generator --> < constructor-arg index = "1" > < ref bean = "IdGenerator" /> </ constructor-arg > …… </ bean > |
2.Bean過濾器
JetspeedBeanDefinitionFilter在Spring容器解析每個(gè)Bean定義時(shí),會(huì)取出上面Bean配置中j2:cat對(duì)應(yīng)的值,例如dbPageManageror pageSerializer。然后將這部分作為正則表達(dá)式與當(dāng)前的Key(從配置文件中讀出)進(jìn)行匹配。只有匹配上的Bean,才會(huì)被Spring容器創(chuàng)建出來。
JetspeedBeanDefinitionFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public boolean match(BeanDefinition bd) { String beanCategoriesExpression = (String)bd.getAttribute(CATEGORY_META_KEY); boolean matched = true ; if (beanCategoriesExpression != null ) { matched = ((matcher != null )&& matcher.match(beanCategoriesExpression)); } return matched; } public void registerDynamicAlias(BeanDefinitionRegistry registry, String beanName,BeanDefinition bd) { String aliases =(String)bd.getAttribute(ALIAS_META_KEY); if (aliases != null ) { StringTokenizer st = newStringTokenizer(aliases, " ," ); while (st.hasMoreTokens()) { String alias = st.nextToken(); if (!alias.equals(beanName)) { registry.registerAlias(beanName, alias); } } } } |
match()方法中的CATEGORY_META_KEY的值就是j2:cat,matcher類中保存的就是當(dāng)前的Key,并負(fù)責(zé)將當(dāng)前Key與每個(gè)Bean的進(jìn)行正則表達(dá)式匹配。
registerDynamicAlias的作用是:在Bean匹配成功后,定制的Spring容器會(huì)調(diào)用此方法為Bean注冊(cè)別名。詳見下面1.3中的源碼。
3.定制Spring容器
定制一個(gè)Spring容器,重寫registerBeanDefinition()方法,在Spring注冊(cè)Bean時(shí)進(jìn)行攔截。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
public class FilteringXmlWebApplicationContextextends XmlWebApplicationContext { private JetspeedBeanDefinitionFilterfilter; publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilter filter, String[]configLocations, Properties initProperties, ServletContext servletContext) { this (filter, configLocations,initProperties, servletContext, null ); } publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilter filter, String[]configLocations, Properties initProperties, ServletContext servletContext,ApplicationContext parent) { super (); if (parent != null ) { this .setParent(parent); } if (initProperties != null ) { PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.setIgnoreUnresolvablePlaceholders( true ); ppc.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK); ppc.setProperties(initProperties); addBeanFactoryPostProcessor(ppc); } setConfigLocations(configLocations); setServletContext(servletContext); this .filter = filter; } protected DefaultListableBeanFactorycreateBeanFactory() { return new FilteringListableBeanFactory(filter,getInternalParentBeanFactory()); } } public classFilteringListableBeanFactory extends DefaultListableBeanFactory { private JetspeedBeanDefinitionFilterfilter; public FilteringListableBeanFactory(JetspeedBeanDefinitionFilterfilter, BeanFactory parentBeanFactory) { super (parentBeanFactory); this .filter = filter; if ( this .filter == null ) { this .filter = newJetspeedBeanDefinitionFilter(); } this .filter.init(); } /** * Override of the registerBeanDefinitionmethod to optionally filter out a BeanDefinition and * if requested dynamically register anbean alias */ public void registerBeanDefinition(StringbeanName, BeanDefinition bd) throws BeanDefinitionStoreException { if (filter.match(bd)) { super .registerBeanDefinition(beanName, bd); if (filter != null ) { filter.registerDynamicAlias( this , beanName, bd); } } } } |
4.為Bean起別名
使用BeanReferenceFactoryBean工廠Bean,將上面配置的兩個(gè)Bean(xmlPageManager和dbPageManager)包裝起來。將Key配成各自的,實(shí)現(xiàn)通過配置當(dāng)前Key來切換兩種實(shí)現(xiàn)。別名都配成一個(gè),這樣引用他們的Bean就直接引用這個(gè)別名就行了。例如下面的PageLayoutComponent。
page-manager.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
< bean class = "org.springframework.beans.factory.config.BeanReferenceFactoryBean" > < meta key = "j2:cat" value = "xmlPageManager" /> < meta key = "j2:alias" value = "org.apache.jetspeed.page.PageManager" /> < propertyname = "targetBeanName" value = "xmlPageManager" /> </ bean > < bean class = "org.springframework.beans.factory.config.BeanReferenceFactoryBean" > < meta key = "j2:cat" value = "dbPageManager" /> < meta key = "j2:alias" value = "org.apache.jetspeed.page.PageManager" /> < propertyname = "targetBeanName" value = "dbPageManager" /> </ bean > < bean id = "org.apache.jetspeed.layout.PageLayoutComponent" class = "org.apache.jetspeed.layout.impl.PageLayoutComponentImpl" > < meta key = "j2:cat" value = "default" /> < constructor-arg index = "0" > < refbean = "org.apache.jetspeed.page.PageManager" /> </ constructor-arg > < constructor-arg index = "1" > < value >jetspeed-layouts::VelocityOneColumn</ value > </ constructor-arg > </ bean > |