Spring 多数据源事务配置问题

在SpringSide 3 中,白衣提供的预先配置好的环境非常有利于用户进行快速开发,但是同时也会为扩展带来一些困难。最直接的例子就是关于在项目中使用多个数据源的问题,似乎 很难搞。在上一篇中,我探讨了SpringSide 3 中的数据访问层,在这一篇中,我立志要解决多数据源配置的难题,我的思路是这样的:

第一步、测试能否配置多个DataSource

第二步、测试能否配置多个SessionFactory

第三步、测试能否配置多个TransactionManager

第四步、测试能否使用多个TransactionManager,也就是看能否配置多个<tx:annotation-driven/>

基本上到第四步就应该走不通了,因为Spring中似乎不能配置多个<tx:annotation-driven/>,而且@transactional注解也无法让用户选择具体使用哪个TransactionManager。也就是说,在SpringSide的应用中,不能让不同的数据源分别属于不同的事务管理器,多数据源只能使用分布式事务管理器,那么测试思路继续如下进行:

第五步、测试能否配置JTATransactionManager

如果到这一步,项目还能顺利在Tomcat中运行的话,我们就算大功告成了。但我总认为事情不会那么顺利,我总觉得JTATransactionManager需要应用服务器的支持,而且需要和JNDI配合使用,具体是不是这样,那只有等测试后才知道。如果被我不幸言中,那么进行下一步:

第六步、更换Tomcat为GlassFish,更换JDBC的DataSource为JNDI查找的DataSource,然后配置JTATransactionManager

下面测试开始,先假设场景,还是继续用上一篇中提到的简单的文章发布系统,假设该系统运行一段时间后非常火爆,单靠一台服务器已经无法支持巨大的用户数,这时候,站长想到了把数据进行水平划分,于是,需要建立一个索引数据库,该索引数据库需保存每一篇文章的Subject及其内容所在的Web服务器,而每一个Web服务器上运行的项目,需要同时访问索引数据库和内容数据库。所以,需要创建索引数据库,如下:

createdatabasepuretext_index;

usepuretext_index;

createtablearticles(

idintprimarykeyauto_increment,

subjectvarchar(256),

webservervarchar(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:

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:jee="http://www.springframework.org/schema/jee"xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsdhttp://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd"

default-lazy-init="true">

<description>Spring公共配置文件</description>

<!--定义受环境影响易变的变量-->

<beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<propertyname="systemPropertiesModeName"value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>

<propertyname="ignoreResourceNotFound"value="true"/>

<propertyname="locations">

<list>

<!--标准配置-->

<value>classpath*:/application.properties</value>

<!--本地开发环境配置-->

<value>classpath*:/application.local.properties</value>

<!--服务器生产环境配置-->

<!--<value>file:/var/myapp/application.server.properties</value>-->

</list>

</property>

</bean>

<!--使用annotation自动注册bean,并保证@Required,@Autowired的属性被注入-->

<context:component-scanbase-package="cn.puretext"/>

<!--数据源配置,使用应用内的DBCP数据库连接池-->

<beanid="dataSourceContent"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">

<!--ConnectionInfo-->

<propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>

<propertyname="url"value="${jdbc.urlContent}"/>

<propertyname="username"value="${jdbc.username}"/>

<propertyname="password"value="${jdbc.password}"/>

<!--ConnectionPoolingInfo-->

<propertyname="initialSize"value="5"/>

<propertyname="maxActive"value="100"/>

<propertyname="maxIdle"value="30"/>

<propertyname="maxWait"value="1000"/>

<propertyname="poolPreparedStatements"value="true"/>

<propertyname="defaultAutoCommit"value="false"/>

</bean>

<beanid="dataSourceIndex"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">

<!--ConnectionInfo-->

<propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>

<propertyname="url"value="${jdbc.urlIndex}"/>

<propertyname="username"value="${jdbc.username}"/>

<propertyname="password"value="${jdbc.password}"/>

<!--ConnectionPoolingInfo-->

<propertyname="initialSize"value="5"/>

<propertyname="maxActive"value="100"/>

<propertyname="maxIdle"value="30"/>

<propertyname="maxWait"value="1000"/>

<propertyname="poolPreparedStatements"value="true"/>

<propertyname="defaultAutoCommit"value="false"/>

</bean>

<!--数据源配置,使用应用服务器的数据库连接池-->

<!--<jee:jndi-lookupid="dataSource"jndi-name="java:comp/env/jdbc/ExampleDB"/>-->

<!--Hibernate配置-->

<beanid="sessionFactory"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceContent"/>

<propertyname="namingStrategy">

<beanclass="org.hibernate.cfg.ImprovedNamingStrategy"/>

</property>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>

<propkey="hibernate.show_sql">${hibernate.show_sql}</prop>

<propkey="hibernate.format_sql">${hibernate.format_sql}</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider

</prop>

<propkey="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>

</props>

</property>

<propertyname="packagesToScan"value="cn.puretext.entity.*"/>

</bean>

<!--事务管理器配置,单数据源事务-->

<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<propertyname="sessionFactory"ref="sessionFactory"/>

</bean>

<!--事务管理器配置,多数据源JTA事务-->

<!--

<beanid="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManageror

WebLogicJtaTransactionManager"/>

-->

<!--使用annotation定义事务-->

<tx:annotation-driventransaction-manager="transactionManager"/>

</beans>

这个时候运行上一篇文章中写好的单元测试DaoTest.java,结果发现还是会出错,错误原因如下:

org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'cn.puretext.unit.service.DaoTest':Autowiringofmethodsfailed;nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Couldnotautowiremethod:publicvoidorg.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests.setDataSource(javax.sql.DataSource);nestedexceptionisorg.springframework.beans.factory.NoSuchBeanDefinitionException:Nouniquebeanoftype[javax.sql.DataSource]isdefined:expectedsinglematchingbeanbutfound2:[dataSourceContent,dataSourceIndex]

经过分析,发现是测试类的基类需要注入DataSource,而现在配置了多个DataSource,所以Spring不知道哪个DataSource匹配了,所以需要改写DaoTest.java,如下:

packagecn.puretext.unit.service;

importjava.util.List;

importjavax.annotation.Resource;

importjavax.sql.DataSource;

importorg.junit.Test;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springside.modules.orm.Page;

importorg.springside.modules.test.junit4.SpringTxTestCase;

importcn.puretext.dao.ArticleDao;

importcn.puretext.entity.web.Article;

publicclassDaoTestextendsSpringTxTestCase{

@Autowired

privateArticleDaoarticleDao;

publicArticleDaogetArticleDao(){

returnarticleDao;

}

publicvoidsetArticleDao(ArticleDaoarticleDao){

this.articleDao=articleDao;

}

@Override

@Resource(name="dataSourceContent")

publicvoidsetDataSource(DataSourcedataSource){

//TODOAuto-generatedmethodstub

super.setDataSource(dataSource);

}

@Test

publicvoidaddArticle(){

Articlearticle=newArticle();

article.setSubject("articletest");

article.setContent("articletest");

articleDao.save(article);

}

@Test

publicvoidpageQuery(){

Page<Article>page=newPage<Article>();

page.setPageSize(10);

page.setPageNo(2);

page=articleDao.getAll(page);

List<Article>articles=page.getResult();

}

}

改变的内容主要为重写了基类中的setDataSource方法,并使用@Resource注解指定使用的DataSource为dataSourceContent。经过修改后,单元测试成功运行。

第二步,配置多个SessionFactory,配置文件如下:

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:jee="http://www.springframework.org/schema/jee"xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsdhttp://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd"

default-lazy-init="true">

<description>Spring公共配置文件</description>

<!--定义受环境影响易变的变量-->

<beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<propertyname="systemPropertiesModeName"value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>

<propertyname="ignoreResourceNotFound"value="true"/>

<propertyname="locations">

<list>

<!--标准配置-->

<value>classpath*:/application.properties</value>

<!--本地开发环境配置-->

<value>classpath*:/application.local.properties</value>

<!--服务器生产环境配置-->

<!--<value>file:/var/myapp/application.server.properties</value>-->

</list>

</property>

</bean>

<!--使用annotation自动注册bean,并保证@Required,@Autowired的属性被注入-->

<context:component-scanbase-package="cn.puretext"/>

<!--数据源配置,使用应用内的DBCP数据库连接池-->

<beanid="dataSourceContent"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">

<!--ConnectionInfo-->

<propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>

<propertyname="url"value="${jdbc.urlContent}"/>

<propertyname="username"value="${jdbc.username}"/>

<propertyname="password"value="${jdbc.password}"/>

<!--ConnectionPoolingInfo-->

<propertyname="initialSize"value="5"/>

<propertyname="maxActive"value="100"/>

<propertyname="maxIdle"value="30"/>

<propertyname="maxWait"value="1000"/>

<propertyname="poolPreparedStatements"value="true"/>

<propertyname="defaultAutoCommit"value="false"/>

</bean>

<beanid="dataSourceIndex"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">

<!--ConnectionInfo-->

<propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>

<propertyname="url"value="${jdbc.urlIndex}"/>

<propertyname="username"value="${jdbc.username}"/>

<propertyname="password"value="${jdbc.password}"/>

<!--ConnectionPoolingInfo-->

<propertyname="initialSize"value="5"/>

<propertyname="maxActive"value="100"/>

<propertyname="maxIdle"value="30"/>

<propertyname="maxWait"value="1000"/>

<propertyname="poolPreparedStatements"value="true"/>

<propertyname="defaultAutoCommit"value="false"/>

</bean>

<!--数据源配置,使用应用服务器的数据库连接池-->

<!--<jee:jndi-lookupid="dataSource"jndi-name="java:comp/env/jdbc/ExampleDB"/>-->

<!--Hibernate配置-->

<beanid="sessionFactoryContent"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceContent"/>

<propertyname="namingStrategy">

<beanclass="org.hibernate.cfg.ImprovedNamingStrategy"/>

</property>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>

<propkey="hibernate.show_sql">${hibernate.show_sql}</prop>

<propkey="hibernate.format_sql">${hibernate.format_sql}</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider

</prop>

<propkey="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>

</props>

</property>

<propertyname="packagesToScan"value="cn.puretext.entity.*"/>

</bean>

<beanid="sessionFactoryIndex"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceIndex"/>

<propertyname="namingStrategy">

<beanclass="org.hibernate.cfg.ImprovedNamingStrategy"/>

</property>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>

<propkey="hibernate.show_sql">${hibernate.show_sql}</prop>

<propkey="hibernate.format_sql">${hibernate.format_sql}</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider

</prop>

<propkey="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>

</props>

</property>

<propertyname="packagesToScan"value="cn.puretext.entity.*"/>

</bean>

<!--事务管理器配置,单数据源事务-->

<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<propertyname="sessionFactory"ref="sessionFactoryContent"/>

</bean>

<!--事务管理器配置,多数据源JTA事务-->

<!--

<beanid="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManageror

WebLogicJtaTransactionManager"/>

-->

<!--使用annotation定义事务-->

<tx:annotation-driventransaction-manager="transactionManager"/>

</beans>

运行单元测试,报错,错误代码如下:

org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'cn.puretext.unit.service.DaoTest':Autowiringoffieldsfailed;nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Couldnotautowirefield:privatecn.puretext.dao.ArticleDaocn.puretext.unit.service.DaoTest.articleDao;nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'articleDao':Autowiringofmethodsfailed;nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Couldnotautowiremethod:publicvoidorg.springside.modules.orm.hibernate.SimpleHibernateDao.setSessionFactory(org.hibernate.SessionFactory);nestedexceptionisorg.springframework.beans.factory.NoSuchBeanDefinitionException:Nouniquebeanoftype[org.hibernate.SessionFactory]isdefined:expectedsinglematchingbeanbutfound2:[sessionFactoryContent,sessionFactoryIndex]

这和上面出现的错误是异曲同工的,只不过这次是ArticleDao类里面不知道注入哪一个SessionFactory,因此,需要修改ArticleDao类,重写setSessionFactory方法,并用@Resource注解指定,如下:

packagecn.puretext.dao;

importjavax.annotation.Resource;

importorg.hibernate.SessionFactory;

importorg.springframework.stereotype.Repository;

importorg.springside.modules.orm.hibernate.HibernateDao;

importcn.puretext.entity.web.Article;

@Repository

publicclassArticleDaoextendsHibernateDao<Article,Long>{

@Override

@Resource(name="sessionFactoryContent")

publicvoidsetSessionFactory(SessionFactorysessionFactory){

//TODOAuto-generatedmethodstub

super.setSessionFactory(sessionFactory);

}

}

运行单元测试,成功。

第三步、配置多个TransactionManager,如下:

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:jee="http://www.springframework.org/schema/jee"xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsdhttp://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd"

default-lazy-init="true">

<description>Spring公共配置文件</description>

<!--定义受环境影响易变的变量-->

<beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<propertyname="systemPropertiesModeName"value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>

<propertyname="ignoreResourceNotFound"value="true"/>

<propertyname="locations">

<list>

<!--标准配置-->

<value>classpath*:/application.properties</value>

<!--本地开发环境配置-->

<value>classpath*:/application.local.properties</value>

<!--服务器生产环境配置-->

<!--<value>file:/var/myapp/application.server.properties</value>-->

</list>

</property>

</bean>

<!--使用annotation自动注册bean,并保证@Required,@Autowired的属性被注入-->

<context:component-scanbase-package="cn.puretext"/>

<!--数据源配置,使用应用内的DBCP数据库连接池-->

<beanid="dataSourceContent"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">

<!--ConnectionInfo-->

<propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>

<propertyname="url"value="${jdbc.urlContent}"/>

<propertyname="username"value="${jdbc.username}"/>

<propertyname="password"value="${jdbc.password}"/>

<!--ConnectionPoolingInfo-->

<propertyname="initialSize"value="5"/>

<propertyname="maxActive"value="100"/>

<propertyname="maxIdle"value="30"/>

<propertyname="maxWait"value="1000"/>

<propertyname="poolPreparedStatements"value="true"/>

<propertyname="defaultAutoCommit"value="false"/>

</bean>

<beanid="dataSourceIndex"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">

<!--ConnectionInfo-->

<propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>

<propertyname="url"value="${jdbc.urlIndex}"/>

<propertyname="username"value="${jdbc.username}"/>

<propertyname="password"value="${jdbc.password}"/>

<!--ConnectionPoolingInfo-->

<propertyname="initialSize"value="5"/>

<propertyname="maxActive"value="100"/>

<propertyname="maxIdle"value="30"/>

<propertyname="maxWait"value="1000"/>

<propertyname="poolPreparedStatements"value="true"/>

<propertyname="defaultAutoCommit"value="false"/>

</bean>

<!--数据源配置,使用应用服务器的数据库连接池-->

<!--<jee:jndi-lookupid="dataSource"jndi-name="java:comp/env/jdbc/ExampleDB"/>-->

<!--Hibernate配置-->

<beanid="sessionFactoryContent"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceContent"/>

<propertyname="namingStrategy">

<beanclass="org.hibernate.cfg.ImprovedNamingStrategy"/>

</property>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>

<propkey="hibernate.show_sql">${hibernate.show_sql}</prop>

<propkey="hibernate.format_sql">${hibernate.format_sql}</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider

</prop>

<propkey="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>

</props>

</property>

<propertyname="packagesToScan"value="cn.puretext.entity.*"/>

</bean>

<beanid="sessionFactoryIndex"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceIndex"/>

<propertyname="namingStrategy">

<beanclass="org.hibernate.cfg.ImprovedNamingStrategy"/>

</property>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>

<propkey="hibernate.show_sql">${hibernate.show_sql}</prop>

<propkey="hibernate.format_sql">${hibernate.format_sql}</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider

</prop>

<propkey="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>

</props>

</property>

<propertyname="packagesToScan"value="cn.puretext.entity.*"/>

</bean>

<!--事务管理器配置,单数据源事务-->

<beanid="transactionManagerContent"class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<propertyname="sessionFactory"ref="sessionFactoryContent"/>

</bean>

<beanid="transactionManagerIndex"class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<propertyname="sessionFactory"ref="sessionFactoryIndex"/>

</bean>

<!--事务管理器配置,多数据源JTA事务-->

<!--

<beanid="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManageror

WebLogicJtaTransactionManager"/>

-->

<!--使用annotation定义事务-->

<tx:annotation-driventransaction-manager="transactionManagerContent"/>

</beans>

这个时候运行还是会出错,出错的原因为org.springframework.beans.factory.NoSuchBeanDefinitionException:Nobeannamed'transactionManager'isdefined,因为该出错信息很短,我也难以找出究竟是哪个地方需要名为“transactionManager”的事务管理器,改个名字都不行,看来Spring的自动注入有时候也错综复杂害人不浅。不过,如果把上面的其中一个名字改成“transactionManger”,另外一个名字不改,运行是成功的,如下:

<!--事务管理器配置,单数据源事务-->

<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<propertyname="sessionFactory"ref="sessionFactoryContent"/>

</bean>

<beanid="transactionManagerIndex"class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<propertyname="sessionFactory"ref="sessionFactoryIndex"/>

</bean>

这个时候得出结论是,可以配置多个TransactionManager,但是必须有一个的名字是transactionManager。

第四步、配置多个<tx:annotation-driven/>,如下:

<!--使用annotation定义事务-->

<tx:annotation-driventransaction-manager="transactionManager"/>

<tx:annotation-driventransaction-manager="transactionManagerIndex"/>

运行测试,天啦,竟然成功了。和我之前预料的完全不一样,居然在一个配置文件中配置多个<tx:annotation-driven/>一点问题都没有。那么在使用@Transactional的地方,它真的能够选择正确的事务管理器吗?我不得不写更多的代码来进行测试。那就针对索引数据库中的表写一个Entity,写一个Dao测试一下吧。

代码如下:

packagecn.puretext.entity.web;

importjavax.persistence.Column;

importjavax.persistence.Entity;

importjavax.persistence.Table;

importorg.hibernate.annotations.Cache;

importorg.hibernate.annotations.CacheConcurrencyStrategy;

importcn.puretext.entity.IdEntity;

@Entity

//表名与类名不相同时重新定义表名.

@Table(name="articles")

//默认的缓存策略.

@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)

publicclassArticleIndexextendsIdEntity{

privateStringsubject;

privateStringwebServer;

publicStringgetSubject(){

returnsubject;

}

publicvoidsetSubject(Stringsubject){

this.subject=subject;

}

@Column(name="webserver")

publicStringgetWebServer(){

returnwebServer;

}

publicvoidsetWebServer(StringwebServer){

this.webServer=webServer;

}

}

packagecn.puretext.dao;

importjavax.annotation.Resource;

importorg.hibernate.SessionFactory;

importorg.springframework.stereotype.Repository;

importorg.springside.modules.orm.hibernate.HibernateDao;

importcn.puretext.entity.web.ArticleIndex;

@Repository

publicclassArticleIndexDaoextendsHibernateDao<ArticleIndex,Long>{

@Override

@Resource(name="sessionFactoryIndex")

publicvoidsetSessionFactory(SessionFactorysessionFactory){

//TODOAuto-generatedmethodstub

super.setSessionFactory(sessionFactory);

}

}

packagecn.puretext.unit.service;

importjava.util.List;

importjavax.annotation.Resource;

importjavax.sql.DataSource;

importorg.junit.Test;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.transaction.annotation.Transactional;

importorg.springside.modules.orm.Page;

importorg.springside.modules.test.junit4.SpringTxTestCase;

importcn.puretext.dao.ArticleDao;

importcn.puretext.dao.ArticleIndexDao;

importcn.puretext.entity.web.Article;

importcn.puretext.entity.web.ArticleIndex;

importcn.puretext.service.ServiceException;

publicclassDaoTestextendsSpringTxTestCase{

@Autowired

privateArticleDaoarticleDao;

@Autowired

privateArticleIndexDaoarticleIndexDao;

publicvoidsetArticleIndexDao(ArticleIndexDaoarticleIndexDao){

this.articleIndexDao=articleIndexDao;

}

publicvoidsetArticleDao(ArticleDaoarticleDao){

this.articleDao=articleDao;

}

@Override

@Resource(name="dataSourceContent")

publicvoidsetDataSource(DataSourcedataSource){

//TODOAuto-generatedmethodstub

super.setDataSource(dataSource);

}

@Test

@Transactional

publicvoidaddArticle(){

Articlearticle=newArticle();

article.setSubject("articletest");

article.setContent("articletest");

articleDao.save(article);

}

@Test

@Transactional

publicvoidpageQuery(){

Page<Article>page=newPage<Article>();

page.setPageSize(10);

page.setPageNo(2);

page=articleDao.getAll(page);

List<Article>articles=page.getResult();

}

@Test

@Transactional

publicvoidaddIndex(){

ArticleIndexarticleIndex=newArticleIndex();

articleIndex.setSubject("test");

articleIndex.setWebServer("www001");

articleIndexDao.save(articleIndex);

}

@Test

@Transactional

publicvoidaddArticleAndAddIndex(){

addArticle();

addIndex();

thrownewServiceException("测试事务回滚");

}

}

运行测试,结果还是成功的。到目前,发现在一个项目中使用多个TransactionManager可以正常运行,但是有两个问题需要考虑:

1、为什么必须得有一个TransactionManager名字为transactionManager?

2、这两个TransactionManager真的能正常工作吗?

3、OpenSessionInView的问题怎么解决?

以上的三个问题在单元测试中是不能找出答案的,我只好再去写Action层的代码,期望能够从中得到线索。经过一天艰苦的努力,终于真相大白:

1、并不是必须有一个TransactionManager的名字为transactionMananger,这只是单元测试在搞鬼,在真实的Web环境中,无论两个TransactionManager取什么名字都可以,运行不会报错。所以这个答案很明确,是因为单元测试的基类需要一个名为transactionMananger的事务管理器。

2、在单元测试中,只能测试Dao类和Entity类能否正常工作,但是由于单元测试结束后事务会自动回滚,不会把数据写入到数据库中,所以没有办法确定两个TransactionManager能否正常工作。在真实的Web环境中,问题很快就浮出水面,只有一个数据库中有数据,另外一个数据库中没有,经过调整<tx:annotation-driven/>的位置并对比分析,发现只有放在前面的TransactionMananger的事务能够正常提交,放在后面的TransactionManager的事务不能提交,所以永远只有一个数据库里面有数据。

3、如果早一点脱离单元测试而进入真实的Web环境,就会早一点发现OpenSessionInViewFilter的问题,因为只要配置多个SessionFactory,运行的时候OpenSessionInViewFilter就会报错。为了解决这个问题,我只能去阅读OpenSessionInViewFilter的源代码,发现它在将Session绑定到线程的时候用的是Map,而且使用SessionFactory作为Map的key,这就说明在线程中绑定多个Session不会冲突,也进一步说明可以在web.xml中配置多个OpenSessionInViewFilter。而我也正是通过配置多个OpenSessionInViewFilter来解决问题的。我的web.xml文件如下:

<?xmlversion="1.0"encoding="UTF-8"?>

<web-appversion="2.4"xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<display-name>PureText</display-name>

<!--SpringApplicationContext配置文件的路径,可使用通配符,多个路径用,号分隔

此参数用于后面的SpringContextLoader-->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath*:/applicationContext*.xml</param-value>

</context-param>

<!--CharacterEncodingfilter-->

<filter>

<filter-name>encodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

<init-param>

<param-name>forceEncoding</param-name>

<param-value>true</param-value>

</init-param>

</filter>

<filter>

<filter-name>hibernateOpenSessionInViewFilterContent</filter-name>

<filter-class>org.springside.modules.orm.hibernate.OpenSessionInViewFilter</filter-class>

<init-param>

<param-name>excludeSuffixs</param-name>

<param-value>js,css,jpg,gif</param-value>

</init-param>

<init-param>

<param-name>sessionFactoryBeanName</param-name>

<param-value>sessionFactoryContent</param-value>

</init-param>

</filter>

<filter>

<filter-name>hibernateOpenSessionInViewFilterIndex</filter-name>

<filter-class>org.springside.modules.orm.hibernate.OpenSessionInViewFilter</filter-class>

<init-param>

<param-name>excludeSuffixs</param-name>

<param-value>js,css,jpg,gif</param-value>

</init-param>

<init-param>

<param-name>sessionFactoryBeanName</param-name>

<param-value>sessionFactoryIndex</param-value>

</init-param>

</filter>

<!--SpringSecurityfilter-->

<filter>

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<!--Struts2filter-->

<filter>

<filter-name>struts2Filter</filter-name>

<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>hibernateOpenSessionInViewFilterContent</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>hibernateOpenSessionInViewFilterIndex</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>struts2Filter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<!--Spring的ApplicationContext载入-->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<!--Spring刷新Introspector防止内存泄露-->

<listener>

<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>

</listener>

<!--session超时定义,单位为分钟-->

<session-config>

<session-timeout>20</session-timeout>

</session-config>

<!--出错页面定义-->

<error-page>

<exception-type>java.lang.Throwable</exception-type>

<location>/common/500.jsp</location>

</error-page>

<error-page>

<error-code>500</error-code>

<location>/common/500.jsp</location>

</error-page>

<error-page>

<error-code>404</error-code>

<location>/common/404.jsp</location>

</error-page>

<error-page>

<error-code>403</error-code>

<location>/common/403.jsp</location>

</error-page>

</web-app>

经过上面的分析,发现使用多个TransactionManager是不可行的(这个时候我在想,也许不使用Annotation就可以使用多个TransactionMananger吧,毕竟Spring的AOP应该是可以把不同的TransactionManager插入到不同的类和方法中,但是谁愿意走回头路呢?毕竟都已经是@Transactional的年代了),虽然运行不会报错,但是只有一个TransactionManager的事务能够正常提交。所以测试进入下一步:

第五步、使用JTATransactionManager

简单地修改配置文件,使用JTATransactionManager做为事务管理器,配置文件我就不列出来了,运行,结果抱错,错误信息如下:

org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'_filterChainProxy':Initializationofbeanfailed;nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'_filterChainList':Cannotcreateinnerbean'(innerbean)'oftype[org.springframework.security.config.OrderedFilterBeanDefinitionDecorator$OrderedFilterDecorator]whilesettingbeanproperty'filters'withkey[10];nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'(innerbean)':Cannotresolvereferencetobean'filterSecurityInterceptor'whilesettingconstructorargument;nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'filterSecurityInterceptor'definedinfile[D:\Temp\1-PureText\WEB-INF\classes\applicationContext-security.xml]:Cannotresolvereferencetobean'databaseDefinitionSource'whilesettingbeanproperty'objectDefinitionSource';nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'databaseDefinitionSource':FactoryBeanthrewexceptiononobjectcreation;nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'org.springframework.transaction.interceptor.TransactionInterceptor#0':Cannotresolvereferencetobean'transactionManager'whilesettingbeanproperty'transactionManager';nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'transactionManager'definedinfile[D:\Temp\1-PureText\WEB-INF\classes\applicationContext.xml]:Invocationofinitmethodfailed;nestedexceptionisjava.lang.IllegalStateException:NoJTAUserTransactionavailable-specifyeither'userTransaction'or'userTransactionName'or'transactionManager'or'transactionManagerName'

通过分析,发现其中最关键的一句是NoJTAUserTransactionavailable,看来,我们只能进入到第六步,使用GlassFish了。

第六步、将项目部署到GlassFish中

将项目简单地部署到GlassFish中之后,项目可以成功运行,没有报错,说明JTAUserTransaction问题解决了,但是检查数据库却发现依然没有数据,看来JTATransactionManager不仅要和应用服务器配合使用,还要和JNDI数据源一起使用。将数据源的配置修改为JNDI后,问题解决。下面是我的配置文件:

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:jee="http://www.springframework.org/schema/jee"xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsdhttp://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd"

default-lazy-init="true">

<description>Spring公共配置文件</description>

<!--定义受环境影响易变的变量-->

<beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<propertyname="systemPropertiesModeName"value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>

<propertyname="ignoreResourceNotFound"value="true"/>

<propertyname="locations">

<list>

<!--标准配置-->

<value>classpath*:/application.properties</value>

<!--本地开发环境配置-->

<value>classpath*:/application.local.properties</value>

<!--服务器生产环境配置-->

<!--<value>file:/var/myapp/application.server.properties</value>-->

</list>

</property>

</bean>

<!--使用annotation自动注册bean,并保证@Required,@Autowired的属性被注入-->

<context:component-scanbase-package="cn.puretext"/>

<!--数据源配置,使用应用服务器的数据库连接池-->

<jee:jndi-lookupid="dataSourceContent"jndi-name="jdbc/dataSourceContent"/>

<jee:jndi-lookupid="dataSourceIndex"jndi-name="jdbc/dataSourceIndex"/>

<!--Hibernate配置-->

<beanid="sessionFactoryContent"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceContent"/>

<propertyname="namingStrategy">

<beanclass="org.hibernate.cfg.ImprovedNamingStrategy"/>

</property>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>

<propkey="hibernate.show_sql">${hibernate.show_sql}</prop>

<propkey="hibernate.format_sql">${hibernate.format_sql}</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider

</prop>

<propkey="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>

</props>

</property>

<propertyname="packagesToScan"value="cn.puretext.entity.*"/>

</bean>

<beanid="sessionFactoryIndex"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceIndex"/>

<propertyname="namingStrategy">

<beanclass="org.hibernate.cfg.ImprovedNamingStrategy"/>

</property>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>

<propkey="hibernate.show_sql">${hibernate.show_sql}</prop>

<propkey="hibernate.format_sql">${hibernate.format_sql}</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider

</prop>

<propkey="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>

</props>

</property>

<propertyname="packagesToScan"value="cn.puretext.entity.*"/>

</bean>

<!--事务管理器配置,单数据源事务-->

<!--

<beanid="transactionManagerContent"class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<propertyname="sessionFactory"ref="sessionFactoryContent"/>

</bean>

<beanid="transactionManagerIndex"class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<propertyname="sessionFactory"ref="sessionFactoryIndex"/>

</bean>

-->

<!--事务管理器配置,多数据源JTA事务-->

<beanid="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"/>

<!--使用annotation定义事务-->

<tx:annotation-driventransaction-manager="transactionManager"/>

</beans>

最后,我得出的结论是:要想使用多个数据库,就必须使用JTATransactionMananger,必须使用GlassFish等应用服务器而不是Tomcat,必须使用JNDI来管理dataSource。

如果一定要使用Tomcat呢?

这确实是一个难题,但是并不代表着没有解决办法。经过广泛的Google一番之后,终于发现了一个好东东,那就是JOTM,它的全称就是JavaOpenTransactionMananger,它的作用就是可以单独提供JTA事务管理的功能,不需要应用服务器。JOTM的使用方法有两种,一种就是把它配置到项目中,和Spring结合起来使用,另外一种就是把它配置到Tomcat中,这时,Tomcat摇身一变就成了和GlassFish一样的能够提供JTA功能的服务器了。

JOTM的官方网站为http://jotm.ow2.org,这是它的新网站,旧网站为http://jotm.objectweb.org。

我选择了把JOTM2.0.11整合到Tomcat中的方法进行了测试,结果发现还是不能够正常运行,我使用的是JOTM2.0.11,Tomcat6.0.20,JKD6Update10。看来还得继续折腾下去了。

另外一个开源的JTA事务管理器是Atomikos,它供了事务管理和连接池,不需要应用服务器支持,其官方网站为http://www.atomikos.com/

相关推荐