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服务器上运行的项目,需要同时访问索引数据库和内容数据库。所以,需要创建索引数据库,如下:
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:
<beans?xmlns="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/beans?http://www.springframework.org/schema/beans/spring-beans-2.5.xsd?http://www.springframework.org/schema/tx?http://www.springframework.org/schema/tx/spring-tx-2.5.xsd?http://www.springframework.org/schema/jee?http://www.springframework.org/schema/jee/spring-jee-2.5.xsd?http://www.springframework.org/schema/context?http://www.springframework.org/schema/context/spring-context-2.5.xsd"
????default-lazy-init="true">
????<description>Spring公共配置文件?</description>
????<!--?定义受环境影响易变的变量?-->
????<bean?class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
????????<property?name="systemPropertiesModeName"?value="SYSTEM_PROPERTIES_MODE_OVERRIDE"?/>
????????<property?name="ignoreResourceNotFound"?value="true"?/>
????????<property?name="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-scan?base-package="cn.puretext"?/>
????<!--?数据源配置,使用应用内的DBCP数据库连接池?-->
????<bean?id="dataSourceContent"?class="org.apache.commons.dbcp.BasicDataSource"?destroy-method="close">
????????<!--?Connection?Info?-->
????????<property?name="driverClassName"?value="com.mysql.jdbc.Driver"?/>
????????<property?name="url"?value="${jdbc.urlContent}"?/>
????????<property?name="username"?value="${jdbc.username}"?/>
????????<property?name="password"?value="${jdbc.password}"?/>
????????<!--?Connection?Pooling?Info?-->
????????<property?name="initialSize"?value="5"?/>
????????<property?name="maxActive"?value="100"?/>
????????<property?name="maxIdle"?value="30"?/>
????????<property?name="maxWait"?value="1000"?/>
????????<property?name="poolPreparedStatements"?value="true"?/>
????????<property?name="defaultAutoCommit"?value="false"?/>
????</bean>
????<bean?id="dataSourceIndex"?class="org.apache.commons.dbcp.BasicDataSource"?destroy-method="close">
????????<!--?Connection?Info?-->
????????<property?name="driverClassName"?value="com.mysql.jdbc.Driver"?/>
????????<property?name="url"?value="${jdbc.urlIndex}"?/>
????????<property?name="username"?value="${jdbc.username}"?/>
????????<property?name="password"?value="${jdbc.password}"?/>
????????<!--?Connection?Pooling?Info?-->
????????<property?name="initialSize"?value="5"?/>
????????<property?name="maxActive"?value="100"?/>
????????<property?name="maxIdle"?value="30"?/>
????????<property?name="maxWait"?value="1000"?/>
????????<property?name="poolPreparedStatements"?value="true"?/>
????????<property?name="defaultAutoCommit"?value="false"?/>
????</bean>
????<!--?数据源配置,使用应用服务器的数据库连接池?-->
????<!--<jee:jndi-lookup?id="dataSource"?jndi-name="java:comp/env/jdbc/ExampleDB"?/>-->
????<!--?Hibernate配置?-->
????<bean?id="sessionFactory"?class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
????????<property?name="dataSource"?ref="dataSourceContent"?/>
????????<property?name="namingStrategy">
????????????<bean?class="org.hibernate.cfg.ImprovedNamingStrategy"?/>
????????</property>
????????<property?name="hibernateProperties">
????????????<props>
????????????????<prop?key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
????????????????<prop?key="hibernate.show_sql">${hibernate.show_sql}</prop>
????????????????<prop?key="hibernate.format_sql">${hibernate.format_sql}</prop>
????????????????<prop?key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider
????????????????</prop>
????????????????<prop?key="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>
????????????</props>
????????</property>
????????<property?name="packagesToScan"?value="cn.puretext.entity.*"?/>
????</bean>
????<!--?事务管理器配置,单数据源事务?-->
????<bean?id="transactionManager"?class="org.springframework.orm.hibernate3.HibernateTransactionManager">
????????<property?name="sessionFactory"?ref="sessionFactory"?/>
????</bean>
????<!--?事务管理器配置,多数据源JTA事务-->
????<!--
????????<bean?id="transactionManager"?class="org.springframework.transaction.jta.JtaTransactionManager?or
????????WebLogicJtaTransactionManager"?/>
????-->
????<!--?使用annotation定义事务?-->
????<tx:annotation-driven?transaction-manager="transactionManager"?/>
</beans>
这个时候运行上一篇文章中写好的单元测试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,如下:
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<Article>?page?=?new?Page<Article>();
????????page.setPageSize(10);
????????page.setPageNo(2);
????????page?=?articleDao.getAll(page);
????????List<Article>?articles?=?page.getResult();
????}
}
?改变的内容主要为重写了基类中的setDataSource方法,并使用@Resource注解指定使用的DataSource为dataSourceContent。经过修改后,单元测试成功运行。
第二步,配置多个SessionFactory,配置文件如下:
<beans?xmlns="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/beans?http://www.springframework.org/schema/beans/spring-beans-2.5.xsd?http://www.springframework.org/schema/tx?http://www.springframework.org/schema/tx/spring-tx-2.5.xsd?http://www.springframework.org/schema/jee?http://www.springframework.org/schema/jee/spring-jee-2.5.xsd?http://www.springframework.org/schema/context?http://www.springframework.org/schema/context/spring-context-2.5.xsd"
????default-lazy-init="true">
????<description>Spring公共配置文件?</description>
????<!--?定义受环境影响易变的变量?-->
????<bean?class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
????????<property?name="systemPropertiesModeName"?value="SYSTEM_PROPERTIES_MODE_OVERRIDE"?/>
????????<property?name="ignoreResourceNotFound"?value="true"?/>
????????<property?name="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-scan?base-package="cn.puretext"?/>
????<!--?数据源配置,使用应用内的DBCP数据库连接池?-->
????<bean?id="dataSourceContent"?class="org.apache.commons.dbcp.BasicDataSource"?destroy-method="close">
????????<!--?Connection?Info?-->
????????<property?name="driverClassName"?value="com.mysql.jdbc.Driver"?/>
????????<property?name="url"?value="${jdbc.urlContent}"?/>
????????<property?name="username"?value="${jdbc.username}"?/>
????????<property?name="password"?value="${jdbc.password}"?/>
????????<!--?Connection?Pooling?Info?-->
????????<property?name="initialSize"?value="5"?/>
????????<property?name="maxActive"?value="100"?/>
????????<property?name="maxIdle"?value="30"?/>
????????<property?name="maxWait"?value="1000"?/>
????????<property?name="poolPreparedStatements"?value="true"?/>
????????<property?name="defaultAutoCommit"?value="false"?/>
????</bean>
????<bean?id="dataSourceIndex"?class="org.apache.commons.dbcp.BasicDataSource"?destroy-method="close">
????????<!--?Connection?Info?-->
????????<property?name="driverClassName"?value="com.mysql.jdbc.Driver"?/>
????????<property?name="url"?value="${jdbc.urlIndex}"?/>
????????<property?name="username"?value="${jdbc.username}"?/>
????????<property?name="password"?value="${jdbc.password}"?/>
????????<!--?Connection?Pooling?Info?-->
????????<property?name="initialSize"?value="5"?/>
????????<property?name="maxActive"?value="100"?/>
????????<property?name="maxIdle"?value="30"?/>
????????<property?name="maxWait"?value="1000"?/>
????????<property?name="poolPreparedStatements"?value="true"?/>
????????<property?name="defaultAutoCommit"?value="false"?/>
????</bean>
????<!--?数据源配置,使用应用服务器的数据库连接池?-->
????<!--<jee:jndi-lookup?id="dataSource"?jndi-name="java:comp/env/jdbc/ExampleDB"?/>-->
????<!--?Hibernate配置?-->
????<bean?id="sessionFactoryContent"?class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
????????<property?name="dataSource"?ref="dataSourceContent"?/>
????????<property?name="namingStrategy">
????????????<bean?class="org.hibernate.cfg.ImprovedNamingStrategy"?/>
????????</property>
????????<property?name="hibernateProperties">
????????????<props>
????????????????<prop?key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
????????????????<prop?key="hibernate.show_sql">${hibernate.show_sql}</prop>
????????????????<prop?key="hibernate.format_sql">${hibernate.format_sql}</prop>
????????????????<prop?key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider
????????????????</prop>
????????????????<prop?key="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>
????????????</props>
????????</property>
????????<property?name="packagesToScan"?value="cn.puretext.entity.*"?/>
????</bean>
????<bean?id="sessionFactoryIndex"?class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
????????<property?name="dataSource"?ref="dataSourceIndex"?/>
????????<property?name="namingStrategy">
????????????<bean?class="org.hibernate.cfg.ImprovedNamingStrategy"?/>
????????</property>
????????<property?name="hibernateProperties">
????????????<props>
????????????????<