Spring 多資料來源事務配置問題
阿新 • • 發佈:2019-01-30
在SpringSide 3 中,白衣提供的預先配置好的環境非常有利於使用者進行快速開發,但是同時也會為擴充套件帶來一些困難。最直接的例子就是關於在專案中使用多個數據源的問題,似乎 很難搞。在上一篇中,我探討了SpringSide 3 中的資料訪問層,在這一篇中,我立志要解決多資料來源配置的難題,我的思路是這樣的:
第一步、測試能否配置多個DataSource
第二步、測試能否配置多個SessionFactory
第三步、測試能否配置多個TransactionManager
第四步、測試能否使用多個TransactionManager,也就是看能否配置多個
基本上到第四步就應該走不通了,因為Spring中似乎不能配置多個,而且@transactional註解也無法讓使用者選擇具體使用哪個TransactionManager。也就是說,在SpringSide的應用中,不能讓不同的資料來源分別屬於不同的事務管理器,多資料來源只能使用分散式事務管理器,那麼測試思路繼續如下進行:
第五步、測試能否配置JTATransactionManager
如果到這一步,專案還能順利在Tomcat中執行的話,我們就算大功告成了。但我總認為事情不會那麼順利,我總覺得JTATransactionManager需要應用伺服器的支援,而且需要和JNDI配合使用,具體是不是這樣,那只有等測試後才知道。如果被我不幸言中,那麼進行下一步:
第六步、更換Tomcat為GlassFish,更換JDBC的DataSource為JNDI查詢的DataSource,然後配置JTATransactionManager
下面測試開始,先假設場景,還是繼續用上一篇中提到的簡單的文章釋出系統,假設該系統執行一段時間後非常火爆,單靠一臺伺服器已經無法支援巨大的使用者數, 這時候,站長想到了把資料進行水平劃分,於是,需要建立一個索引資料庫,該索引資料庫需儲存每一篇文章的Subject及其內容所在的Web伺服器,而每 一個Web伺服器上執行的專案,需要同時訪問索引資料庫和內容資料庫。所以,需要建立索引資料庫,如下:
第一步測試,配置多個DataSource,配置檔案如下:
application.properties:
applicationContext.xml:
這個時候執行上一篇文章中寫好的單元測試DaoTest.java,結果發現還是會出錯,錯誤原因如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cn.puretext.unit.service.DaoTest': Autowiring of methods failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests.setDataSource(javax.sql.DataSource); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [dataSourceContent, dataSourceIndex]
經過分析,發現是測試類的基類需要注入DataSource,而現在配置了多個DataSource,所以Spring不知道哪個DataSource匹配了,所以需要改寫DaoTest.java,如下:
改變的內容主要為重寫了基類中的setDataSource方法,並使用@Resource註解指定使用的DataSource為dataSourceContent。經過修改後,單元測試成功執行。
第二步,配置多個SessionFactory,配置檔案如下:
執行單元測試,報錯,錯誤程式碼如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cn.puretext.unit.service.DaoTest': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private cn.puretext.dao.ArticleDao cn.puretext.unit.service.DaoTest.articleDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'articleDao': Autowiring of methods failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springside.modules.orm.hibernate.SimpleHibernateDao.setSessionFactory(org.hibernate.SessionFactory); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.hibernate.SessionFactory] is defined: expected single matching bean but found 2: [sessionFactoryContent, sessionFactoryIndex]
這和上面出現的錯誤是異曲同工的,只不過這次是ArticleDao類裡面不知道注入哪一個SessionFactory,因此,需要修改ArticleDao類,重寫setSessionFactory方法,並用@Resource註解指定,如下:
執行單元測試,成功。
第三步、配置多個TransactionManager,如下:
這個時候得出結論是,可以配置多個TransactionManager,但是必須有一個的名字是transactionManager。
第四步、配置多個,如下:
執行測試,天啦,竟然成功了。和我之前預料的完全不一樣,居然在一個配置檔案中配置多個一點 問題都沒有。那麼在使用@Transactional的地方,它真的能夠選擇正確的事務管理器嗎?我不得不寫更多的程式碼來進行測試。那就針對索引資料庫中 的表寫一個Entity,寫一個Dao測試一下吧。
程式碼如下:
執行測試,結果還是成功的。到目前,發現在一個專案中使用多個TransactionManager可以正常執行,但是有兩個問題需要考慮:
1、為什麼必須得有一個TransactionManager名字為transactionManager?
2、這兩個TransactionManager真的能正常工作嗎?
3、OpenSessionInView的問題怎麼解決?
以上的三個問題在單元測試中是不能找出答案的,我只好再去寫Action層的程式碼,期望能夠從中得到線索。經過一天艱苦的努力,終於真相大白:
1、並不是必須有一個TransactionManager的名字為transactionMananger,這只是單元測試在搞鬼,在真實的Web環境 中,無論兩個TransactionManager取什麼名字都可以,執行不會報錯。所以這個答案很明確,是因為單元測試的基類需要一個名為 transactionMananger的事務管理器。
2、在單元測試中,只能測試Dao類和Entity類能否正常工作,但是由於單元測試結束後事務會自動回滾,不會把資料寫入到資料庫中,所以沒有辦法確定 兩個TransactionManager能否正常工作。在真實的Web環境中,問題很快就浮出水面,只有一個數據庫中有資料,另外一個數據庫中沒有,經 過調整的位置並對比分析,發現只有放在前面的TransactionMananger的事務 能夠正常提交,放在後面的TransactionManager的事務不能提交,所以永遠只有一個數據庫裡面有資料。
3、如果早一點脫離單元測試而進入真實的Web環境,就會早一點發現OpenSessionInViewFilter的問題,因為只要配置多個 SessionFactory,執行的時候OpenSessionInViewFilter就會報錯。為了解決這個問題,我只能去閱讀 OpenSessionInViewFilter的原始碼,發現它在將Session繫結到執行緒的時候用的是Map,而且使用 SessionFactory作為Map的key,這就說明線上程中繫結多個Session不會衝突,也進一步說明可以在web.xml中配置多個 OpenSessionInViewFilter。而我也正是通過配置多個OpenSessionInViewFilter來解決問題的。我的 web.xml檔案如下:
經過上面的分析,發現使用多個TransactionManager是不可行的(這個時候我在想,也許不使用Annotation就可以使用多個 TransactionMananger吧,畢竟Spring的AOP應該是可以把不同的TransactionManager插入到不同的類和方法中, 但是誰願意走回頭路呢?畢竟都已經是@Transactional的年代了),雖然執行不會報錯,但是隻有一個TransactionManager的事 務能夠正常提交。所以測試進入下一步:
第五步、使用JTATransactionManager
簡單地修改配置檔案,使用JTATransactionManager做為事務管理器,配置檔案我就不列出來了,執行,結果抱錯,錯誤資訊如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name '_filterChainProxy': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '_filterChainList': Cannot create inner bean '(inner bean)' of type [org.springframework.security.config.OrderedFilterBeanDefinitionDecorator$OrderedFilterDecorator] while setting bean property 'filters' with key [10]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)': Cannot resolve reference to bean 'filterSecurityInterceptor' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'filterSecurityInterceptor' defined in file [D:Temp1-PureTextWEB-INFclassesapplicationContext-security.xml]: Cannot resolve reference to bean 'databaseDefinitionSource' while setting bean property 'objectDefinitionSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'databaseDefinitionSource': FactoryBean threw exception on object creation; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.interceptor.TransactionInterceptor#0': Cannot resolve reference to bean 'transactionManager' while setting bean property 'transactionManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager' defined in file [D:Temp1-PureTextWEB-INFclassesapplicationContext.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: No JTA UserTransaction available - specify either 'userTransaction' or 'userTransactionName' or 'transactionManager' or 'transactionManagerName'
通過分析,發現其中最關鍵的一句是No JTA UserTransaction available,看來,我們只能進入到第六步,使用GlassFish了。
第六步、將專案部署到GlassFish中
將專案簡單地部署到GlassFish中之後,專案可以成功執行,沒有報錯,說明JTA UserTransaction問題解決了,但是檢查資料庫卻發現依然沒有資料,看來JTATransactionManager不僅要和應用伺服器配合 使用,還要和JNDI資料來源一起使用。將資料來源的配置修改為JNDI後,問題解決。下面是我的配置檔案:
最後,我得出的結論是:要想使用多個數據庫,就必須使用JTATransactionMananger,必須使用GlassFish等應用伺服器而不是Tomcat,必須使用JNDI來管理dataSource。
如果一定要使用Tomcat呢?
這確實是一個難題,但是並不代表著沒有解決辦法。經過廣泛的Google一番之後,終於發現了一個好東東,那就是JOTM,它的全稱就是Java Open Transaction Mananger,它的作用就是可以單獨提供JTA事務管理的功能,不需要應用伺服器。JOTM的使用方法有兩種,一種就是把它配置到專案中,和 Spring結合起來使用,另外一種就是把它配置到Tomcat中,這時,Tomcat搖身一變就成了和GlassFish一樣的能夠提供JTA功能的服 務器了。
JOTM的官方網站為http://jotm.ow2.org,這是它的新網站,舊網站為http://jotm.objectweb.org。
我選擇了把JOTM 2.0.11整合到Tomcat中的方法進行了測試,結果發現還是不能夠正常執行,我使用的是JOTM2.0.11,Tomcat 6.0.20,JKD 6 Update10。看來還得繼續折騰下去了。
另外一個開源的JTA事務管理器是Atomikos,它供了事務管理和連線池,不需要應用伺服器支援,其官方網站為http://www.atomikos.com/。有興趣的朋友可以試試。
第一步、測試能否配置多個DataSource
第二步、測試能否配置多個SessionFactory
第三步、測試能否配置多個TransactionManager
第四步、測試能否使用多個TransactionManager,也就是看能否配置多個
基本上到第四步就應該走不通了,因為Spring中似乎不能配置多個,而且@transactional註解也無法讓使用者選擇具體使用哪個TransactionManager。也就是說,在SpringSide的應用中,不能讓不同的資料來源分別屬於不同的事務管理器,多資料來源只能使用分散式事務管理器,那麼測試思路繼續如下進行:
第五步、測試能否配置JTATransactionManager
如果到這一步,專案還能順利在Tomcat中執行的話,我們就算大功告成了。但我總認為事情不會那麼順利,我總覺得JTATransactionManager需要應用伺服器的支援,而且需要和JNDI配合使用,具體是不是這樣,那只有等測試後才知道。如果被我不幸言中,那麼進行下一步:
第六步、更換Tomcat為GlassFish,更換JDBC的DataSource為JNDI查詢的DataSource,然後配置JTATransactionManager
下面測試開始,先假設場景,還是繼續用上一篇中提到的簡單的文章釋出系統,假設該系統執行一段時間後非常火爆,單靠一臺伺服器已經無法支援巨大的使用者數, 這時候,站長想到了把資料進行水平劃分,於是,需要建立一個索引資料庫,該索引資料庫需儲存每一篇文章的Subject及其內容所在的Web伺服器,而每 一個Web伺服器上執行的專案,需要同時訪問索引資料庫和內容資料庫。所以,需要建立索引資料庫,如下:
create database puretext_index; use puretext_index; create table articles( id int primary key auto_increment, subject varchar(256), webserver varchar(30) );
第一步測試,配置多個DataSource,配置檔案如下:
application.properties:
jdbc.urlContent=jdbc:mysql://localhost:3306/PureText useUnicode=true&characterEncoding=utf8
jdbc.urlIndex=jdbc:mysql://localhost:3306/PureText_Index useUnicode=true&characterEncoding=utf8
applicationContext.xml:
< xml version="1.0" encoding="UTF-8" > Spring公共配置檔案 classpath*:/application.propertiesclasspath*:/application.local.propertiesorg.hibernate.dialect.MySQL5InnoDBDialect${hibernate.show_sql}${hibernate.format_sql}org.hibernate.cache.EhCacheProvider ${hibernate.ehcache_config_file}!-->!--
這個時候執行上一篇文章中寫好的單元測試DaoTest.java,結果發現還是會出錯,錯誤原因如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cn.puretext.unit.service.DaoTest': Autowiring of methods failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests.setDataSource(javax.sql.DataSource); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [dataSourceContent, dataSourceIndex]
經過分析,發現是測試類的基類需要注入DataSource,而現在配置了多個DataSource,所以Spring不知道哪個DataSource匹配了,所以需要改寫DaoTest.java,如下:
package cn.puretext.unit.service; import java.util.List; import javax.annotation.Resource; import javax.sql.DataSource; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springside.modules.orm.Page; import org.springside.modules.test.junit4.SpringTxTestCase; import cn.puretext.dao.ArticleDao; import cn.puretext.entity.web.Article; public class DaoTest extends SpringTxTestCase { @Autowired private ArticleDao articleDao; public ArticleDao getArticleDao() { return articleDao; } public void setArticleDao(ArticleDao articleDao) { this.articleDao = articleDao; } @Override @Resource(name = "dataSourceContent") public void setDataSource(DataSource dataSource) { // TODO Auto-generated method stub super.setDataSource(dataSource); } @Test public void addArticle() { Article article = new Article(); article.setSubject("article test"); article.setContent("article test"); articleDao.save(article); } @Test public void pageQuery() { Page page = new Page(); page.setPageSize(10); page.setPageNo(2); page = articleDao.getAll(page); List articles = page.getResult(); } }
改變的內容主要為重寫了基類中的setDataSource方法,並使用@Resource註解指定使用的DataSource為dataSourceContent。經過修改後,單元測試成功執行。
第二步,配置多個SessionFactory,配置檔案如下:
< xml version="1.0" encoding="UTF-8" >
Spring公共配置檔案 classpath*:/application.propertiesclasspath*:/application.local.propertiesorg.hibernate.dialect.MySQL5InnoDBDialect${hibernate.show_sql}${hibernate.format_sql}org.hibernate.cache.EhCacheProvider
${hibernate.ehcache_config_file}org.hibernate.dialect.MySQL5InnoDBDialect${hibernate.show_sql}${hibernate.format_sql}org.hibernate.cache.EhCacheProvider
${hibernate.ehcache_config_file}!-->!--
執行單元測試,報錯,錯誤程式碼如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cn.puretext.unit.service.DaoTest': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private cn.puretext.dao.ArticleDao cn.puretext.unit.service.DaoTest.articleDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'articleDao': Autowiring of methods failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springside.modules.orm.hibernate.SimpleHibernateDao.setSessionFactory(org.hibernate.SessionFactory); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.hibernate.SessionFactory] is defined: expected single matching bean but found 2: [sessionFactoryContent, sessionFactoryIndex]
這和上面出現的錯誤是異曲同工的,只不過這次是ArticleDao類裡面不知道注入哪一個SessionFactory,因此,需要修改ArticleDao類,重寫setSessionFactory方法,並用@Resource註解指定,如下:
package cn.puretext.dao;
import javax.annotation.Resource;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import org.springside.modules.orm.hibernate.HibernateDao;
import cn.puretext.entity.web.Article;
@Repository
public class ArticleDao extends HibernateDao {
@Override
@Resource(name = "sessionFactoryContent")
public void setSessionFactory(SessionFactory sessionFactory) {
// TODO Auto-generated method stub
super.setSessionFactory(sessionFactory);
}
}
,>
執行單元測試,成功。
第三步、配置多個TransactionManager,如下:
< xml version="1.0" encoding="UTF-8" >
Spring公共配置檔案 classpath*:/application.propertiesclasspath*:/application.local.propertiesorg.hibernate.dialect.MySQL5InnoDBDialect${hibernate.show_sql}${hibernate.format_sql}org.hibernate.cache.EhCacheProvider
${hibernate.ehcache_config_file}org.hibernate.dialect.MySQL5InnoDBDialect${hibernate.show_sql}${hibernate.format_sql}org.hibernate.cache.EhCacheProvider
${hibernate.ehcache_config_file}!-->!--
這個時候執行還是會出錯,出錯的原因為 org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'transactionManager' is defined,因為該出錯資訊很短,我也難以找出究竟是哪個地方需要名為“transactionManager”的事務管理器 ,改個名字都不行,看來Spring的自動注入有時候也錯綜複雜害人不淺。不過,如果把上面的其中一個名字改成“transactionManger”, 另外一個名字不改,執行是成功的,如下:
這個時候得出結論是,可以配置多個TransactionManager,但是必須有一個的名字是transactionManager。
第四步、配置多個,如下:
執行測試,天啦,竟然成功了。和我之前預料的完全不一樣,居然在一個配置檔案中配置多個一點 問題都沒有。那麼在使用@Transactional的地方,它真的能夠選擇正確的事務管理器嗎?我不得不寫更多的程式碼來進行測試。那就針對索引資料庫中 的表寫一個Entity,寫一個Dao測試一下吧。
程式碼如下:
package cn.puretext.entity.web;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import cn.puretext.entity.IdEntity;
@Entity
// 表名與類名不相同時重新定義表名.
@Table(name = "articles")
// 預設的快取策略.
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ArticleIndex extends IdEntity {
private String subject;
private String webServer;
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
@Column(name = "webserver")
public String getWebServer() {
return webServer;
}
public void setWebServer(String webServer) {
this.webServer = webServer;
}
}
package cn.puretext.dao;
import javax.annotation.Resource;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import org.springside.modules.orm.hibernate.HibernateDao;
import cn.puretext.entity.web.ArticleIndex;
@Repository
public class ArticleIndexDao extends HibernateDao {
@Override
@Resource(name = "sessionFactoryIndex")
public void setSessionFactory(SessionFactory sessionFactory) {
// TODO Auto-generated method stub
super.setSessionFactory(sessionFactory);
}
},>
package cn.puretext.unit.service;
import java.util.List;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springside.modules.orm.Page;
import org.springside.modules.test.junit4.SpringTxTestCase;
import cn.puretext.dao.ArticleDao;
import cn.puretext.dao.ArticleIndexDao;
import cn.puretext.entity.web.Article;
import cn.puretext.entity.web.ArticleIndex;
import cn.puretext.service.ServiceException;
public class DaoTest extends SpringTxTestCase {
@Autowired
private ArticleDao articleDao;
@Autowired
private ArticleIndexDao articleIndexDao;
public void setArticleIndexDao(ArticleIndexDao articleIndexDao) {
this.articleIndexDao = articleIndexDao;
}
public void setArticleDao(ArticleDao articleDao) {
this.articleDao = articleDao;
}
@Override
@Resource(name = "dataSourceContent")
public void setDataSource(DataSource dataSource) {
// TODO Auto-generated method stub
super.setDataSource(dataSource);
}
@Test
@Transactional
public void addArticle() {
Article article = new Article();
article.setSubject("article test");
article.setContent("article test");
articleDao.save(article);
}
@Test
@Transactional
public void pageQuery() {
Page page = new Page();
page.setPageSize(10);
page.setPageNo(2);
page = articleDao.getAll(page);
List articles = page.getResult();
}
@Test
@Transactional
public void addIndex() {
ArticleIndex articleIndex = new ArticleIndex();
articleIndex.setSubject("test");
articleIndex.setWebServer("www001");
articleIndexDao.save(articleIndex);
}
@Test
@Transactional
public void addArticleAndAddIndex() {
addArticle();
addIndex();
throw new ServiceException("測試事務回滾");
}
}
執行測試,結果還是成功的。到目前,發現在一個專案中使用多個TransactionManager可以正常執行,但是有兩個問題需要考慮:
1、為什麼必須得有一個TransactionManager名字為transactionManager?
2、這兩個TransactionManager真的能正常工作嗎?
3、OpenSessionInView的問題怎麼解決?
以上的三個問題在單元測試中是不能找出答案的,我只好再去寫Action層的程式碼,期望能夠從中得到線索。經過一天艱苦的努力,終於真相大白:
1、並不是必須有一個TransactionManager的名字為transactionMananger,這只是單元測試在搞鬼,在真實的Web環境 中,無論兩個TransactionManager取什麼名字都可以,執行不會報錯。所以這個答案很明確,是因為單元測試的基類需要一個名為 transactionMananger的事務管理器。
2、在單元測試中,只能測試Dao類和Entity類能否正常工作,但是由於單元測試結束後事務會自動回滾,不會把資料寫入到資料庫中,所以沒有辦法確定 兩個TransactionManager能否正常工作。在真實的Web環境中,問題很快就浮出水面,只有一個數據庫中有資料,另外一個數據庫中沒有,經 過調整的位置並對比分析,發現只有放在前面的TransactionMananger的事務 能夠正常提交,放在後面的TransactionManager的事務不能提交,所以永遠只有一個數據庫裡面有資料。
3、如果早一點脫離單元測試而進入真實的Web環境,就會早一點發現OpenSessionInViewFilter的問題,因為只要配置多個 SessionFactory,執行的時候OpenSessionInViewFilter就會報錯。為了解決這個問題,我只能去閱讀 OpenSessionInViewFilter的原始碼,發現它在將Session繫結到執行緒的時候用的是Map,而且使用 SessionFactory作為Map的key,這就說明線上程中繫結多個Session不會衝突,也進一步說明可以在web.xml中配置多個 OpenSessionInViewFilter。而我也正是通過配置多個OpenSessionInViewFilter來解決問題的。我的 web.xml檔案如下:
< xml version="1.0" encoding="UTF-8" >
PureTextcontextConfigLocationclasspath*:/applicationContext*.xmlencodingFilterorg.springframework.web.filter.CharacterEncodingFilterencodingUTF-8forceEncodingtruehibernateOpenSessionInViewFilterContentorg.springside.modules.orm.hibernate.OpenSessionInViewFilterexcludeSuffixsjs,css,jpg,gifsessionFactoryBeanNamesessionFactoryContenthibernateOpenSessionInViewFilterIndexorg.springside.modules.orm.hibernate.OpenSessionInViewFilterexcludeSuffixsjs,css,jpg,gifsessionFactoryBeanNamesessionFactoryIndexspringSecurityFilterChainorg.springframework.web.filter.DelegatingFilterProxystruts2Filterorg.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilterencodingFilter/*springSecurityFilterChain/*hibernateOpenSessionInViewFilterContent/*hibernateOpenSessionInViewFilterIndex/*struts2Filter/*org.springframework.web.context.ContextLoaderListenerorg.springframework.web.util.IntrospectorCleanupListener20java.lang.Throwable/common/500.jsp500/common/500.jsp404/common/404.jsp403/common/403.jsp
經過上面的分析,發現使用多個TransactionManager是不可行的(這個時候我在想,也許不使用Annotation就可以使用多個 TransactionMananger吧,畢竟Spring的AOP應該是可以把不同的TransactionManager插入到不同的類和方法中, 但是誰願意走回頭路呢?畢竟都已經是@Transactional的年代了),雖然執行不會報錯,但是隻有一個TransactionManager的事 務能夠正常提交。所以測試進入下一步:
第五步、使用JTATransactionManager
簡單地修改配置檔案,使用JTATransactionManager做為事務管理器,配置檔案我就不列出來了,執行,結果抱錯,錯誤資訊如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name '_filterChainProxy': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '_filterChainList': Cannot create inner bean '(inner bean)' of type [org.springframework.security.config.OrderedFilterBeanDefinitionDecorator$OrderedFilterDecorator] while setting bean property 'filters' with key [10]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)': Cannot resolve reference to bean 'filterSecurityInterceptor' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'filterSecurityInterceptor' defined in file [D:Temp1-PureTextWEB-INFclassesapplicationContext-security.xml]: Cannot resolve reference to bean 'databaseDefinitionSource' while setting bean property 'objectDefinitionSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'databaseDefinitionSource': FactoryBean threw exception on object creation; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.interceptor.TransactionInterceptor#0': Cannot resolve reference to bean 'transactionManager' while setting bean property 'transactionManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager' defined in file [D:Temp1-PureTextWEB-INFclassesapplicationContext.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: No JTA UserTransaction available - specify either 'userTransaction' or 'userTransactionName' or 'transactionManager' or 'transactionManagerName'
通過分析,發現其中最關鍵的一句是No JTA UserTransaction available,看來,我們只能進入到第六步,使用GlassFish了。
第六步、將專案部署到GlassFish中
將專案簡單地部署到GlassFish中之後,專案可以成功執行,沒有報錯,說明JTA UserTransaction問題解決了,但是檢查資料庫卻發現依然沒有資料,看來JTATransactionManager不僅要和應用伺服器配合 使用,還要和JNDI資料來源一起使用。將資料來源的配置修改為JNDI後,問題解決。下面是我的配置檔案:
< xml version="1.0" encoding="UTF-8" >
Spring公共配置檔案 classpath*:/application.propertiesclasspath*:/application.local.propertiesorg.hibernate.dialect.MySQL5InnoDBDialect${hibernate.show_sql}${hibernate.format_sql}org.hibernate.cache.EhCacheProvider
${hibernate.ehcache_config_file}org.hibernate.dialect.MySQL5InnoDBDialect${hibernate.show_sql}${hibernate.format_sql}org.hibernate.cache.EhCacheProvider
${hibernate.ehcache_config_file}!-->
最後,我得出的結論是:要想使用多個數據庫,就必須使用JTATransactionMananger,必須使用GlassFish等應用伺服器而不是Tomcat,必須使用JNDI來管理dataSource。
如果一定要使用Tomcat呢?
這確實是一個難題,但是並不代表著沒有解決辦法。經過廣泛的Google一番之後,終於發現了一個好東東,那就是JOTM,它的全稱就是Java Open Transaction Mananger,它的作用就是可以單獨提供JTA事務管理的功能,不需要應用伺服器。JOTM的使用方法有兩種,一種就是把它配置到專案中,和 Spring結合起來使用,另外一種就是把它配置到Tomcat中,這時,Tomcat搖身一變就成了和GlassFish一樣的能夠提供JTA功能的服 務器了。
JOTM的官方網站為http://jotm.ow2.org,這是它的新網站,舊網站為http://jotm.objectweb.org。
我選擇了把JOTM 2.0.11整合到Tomcat中的方法進行了測試,結果發現還是不能夠正常執行,我使用的是JOTM2.0.11,Tomcat 6.0.20,JKD 6 Update10。看來還得繼續折騰下去了。
另外一個開源的JTA事務管理器是Atomikos,它供了事務管理和連線池,不需要應用伺服器支援,其官方網站為http://www.atomikos.com/。有興趣的朋友可以試試。