`
tansitongba
  • 浏览: 503795 次
文章分类
社区版块
存档分类
最新评论

Spring 多数据源事务配置问题

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

第一步、测试能否配置多个DataSource
第二步、测试能否配置多个SessionFactory
第三步、测试能否配置多个TransactionManager
第四步、测试能否使用多个TransactionManager,也就是看能否配置多个<annotation-driven><br><br>基本上到第四步就应该走不通了,因为Spring中似乎不能配置多个<annotation-driven>,而且@transactional注解也无法让用户选择具体使用哪个TransactionManager。也就是说,在SpringSide的应用中,不能让不同的数据源分别属于不同的事务管理器,多数据源只能使用分布式事务管理器,那么测试思路继续如下进行:<br><br>第五步、测试能否配置JTATransactionManager<br><br>如果到这一步,项目还能顺利在Tomcat中运行的话,我们就算大功告成了。但我总认为事情不会那么顺利,我总觉得JTATransactionManager需要应用服务器的支持,而且需要和JNDI配合使用,具体是不是这样,那只有等测试后才知道。如果被我不幸言中,那么进行下一步:<br><br>第六步、更换Tomcat为GlassFish,更换JDBC的DataSource为JNDI查找的DataSource,然后配置JTATransactionManager<br><br>下面测试开始,先假设场景,还是继续用上一篇中提到的简单的文章发布系统,假设该系统运行一段时间后非常火爆,单靠一台服务器已经无法支持巨大的用户数, 这时候,站长想到了把数据进行水平划分,于是,需要建立一个索引数据库,该索引数据库需保存每一篇文章的Subject及其内容所在的Web服务器,而每 一个Web服务器上运行的项目,需要同时访问索引数据库和内容数据库。所以,需要创建索引数据库,如下:<br><textarea readonly name="code" class="java">create database puretext_index; use puretext_index; create table articles( id int primary key auto_increment, subject varchar(256), webserver varchar(30) ); </textarea><br>第一步测试,配置多个DataSource,配置文件如下:<br>application.properties:<br><textarea readonly name="code" class="java">jdbc.urlContent=jdbc:mysql://localhost:3306/PureText useUnicode=true&amp;characterEncoding=utf8 jdbc.urlIndex=jdbc:mysql://localhost:3306/PureText_Index useUnicode=true&amp;characterEncoding=utf8 </textarea><br>applicationContext.xml:<br><textarea readonly name="code" class="java">&lt; xml version="1.0" encoding="UTF-8" &gt; <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" default-lazy-init="true" 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"><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><!-- 服务器生产环境配置 --><!-->file:/var/myapp/application.server.properties --&gt; </!--></list></property></property></property></bean><!-- 使用annotation 自动注册bean,并保证@Required,@Autowired的属性被注入 --><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"></property></property></property></property></property></property></property></property></property></property></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"></property></property></property></property></property></property></property></property></property></property></bean><!-- 数据源配置,使用应用服务器的数据库连接池 --><!--<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/ExampleDB">--&gt; <!-- 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"></bean></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.*"></property></property></bean><!-- 事务管理器配置,单数据源事务 --><bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory"></property></bean><!-- 事务管理器配置,多数据源JTA事务--><!-- id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager or WebLogicJtaTransactionManager"> --&gt; <!-- 使用annotation定义事务 --><annotation-driven transaction-manager="transactionManager"></annotation-driven>!--&gt;!--<jndi-lookup></jndi-lookup></component-scan></beans></textarea><br>这个时候运行上一篇文章中写好的单元测试DaoTest.java,结果发现还是会出错,错误原因如下:<br>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]<br><br>经过分析,发现是测试类的基类需要注入DataSource,而现在配置了多个DataSource,所以Spring不知道哪个DataSource匹配了,所以需要改写DaoTest.java,如下:<br><textarea readonly name="code" class="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<article> page = new Page<article>(); page.setPageSize(10); page.setPageNo(2); page = articleDao.getAll(page); List<article> articles = page.getResult(); } } </article></article></article></textarea><br>改变的内容主要为重写了基类中的setDataSource方法,并使用@Resource注解指定使用的DataSource为dataSourceContent。经过修改后,单元测试成功运行。<br><br>第二步,配置多个SessionFactory,配置文件如下:<br><textarea readonly name="code" class="java">&lt; xml version="1.0" encoding="UTF-8" &gt; <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" default-lazy-init="true" 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"><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><!-- 服务器生产环境配置 --><!-->file:/var/myapp/application.server.properties --&gt; </!--></list></property></property></property></bean><!-- 使用annotation 自动注册bean,并保证@Required,@Autowired的属性被注入 --><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"></property></property></property></property></property></property></property></property></property></property></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"></property></property></property></property></property></property></property></property></property></property></bean><!-- 数据源配置,使用应用服务器的数据库连接池 --><!--<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/ExampleDB">--&gt; <!-- 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"></bean></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.*"></property></property></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"></bean></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.*"></property></property></bean><!-- 事务管理器配置,单数据源事务 --><bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactoryContent"></property></bean><!-- 事务管理器配置,多数据源JTA事务--><!-- id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager or WebLogicJtaTransactionManager"> --&gt; <!-- 使用annotation定义事务 --><annotation-driven transaction-manager="transactionManager"></annotation-driven>!--&gt;!--<jndi-lookup></jndi-lookup></component-scan></beans></textarea><br>运行单元测试,报错,错误代码如下:<br>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]<br><br>这和上面出现的错误是异曲同工的,只不过这次是ArticleDao类里面不知道注入哪一个SessionFactory,因此,需要修改ArticleDao类,重写setSessionFactory方法,并用@Resource注解指定,如下:<br><textarea readonly name="code" class="java">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<article long=""> { @Override @Resource(name = "sessionFactoryContent") public void setSessionFactory(SessionFactory sessionFactory) { // TODO Auto-generated method stub super.setSessionFactory(sessionFactory); } } </article></textarea><br>运行单元测试,成功。<br><br>第三步、配置多个TransactionManager,如下:<br><textarea readonly name="code" class="java">&lt; xml version="1.0" encoding="UTF-8" &gt; <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" default-lazy-init="true" 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"><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><!-- 服务器生产环境配置 --><!-->file:/var/myapp/application.server.properties --&gt; </!--></list></property></property></property></bean><!-- 使用annotation 自动注册bean,并保证@Required,@Autowired的属性被注入 --><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"></property></property></property></property></property></property></property></property></property></property></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"></property></property></property></property></property></property></property></property></property></property></bean><!-- 数据源配置,使用应用服务器的数据库连接池 --><!--<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/ExampleDB">--&gt; <!-- 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"></bean></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.*"></property></property></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"></bean></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.*"></property></property></bean><!-- 事务管理器配置,单数据源事务 --><bean id="transactionManagerContent" class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactoryContent"></property></bean><bean id="transactionManagerIndex" class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactoryIndex"></property></bean><!-- 事务管理器配置,多数据源JTA事务--><!-- id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager or WebLogicJtaTransactionManager"> --&gt; <!-- 使用annotation定义事务 --><annotation-driven transaction-manager="transactionManagerContent"></annotation-driven>!--&gt;!--<jndi-lookup></jndi-lookup></component-scan></beans> 这个时候运行还是会出错,出错的原因为 org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'transactionManager' is defined,因为该出错信息很短,我也难以找出究竟是哪个地方需要名为“transactionManager”的事务管理器 ,改个名字都不行,看来Spring的自动注入有时候也错综复杂害人不浅。不过,如果把上面的其中一个名字改成“transactionManger”, 另外一个名字不改,运行是成功的,如下: <!-- 事务管理器配置,单数据源事务 --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactoryContent"></property></bean><bean id="transactionManagerIndex" class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactoryIndex"></property></bean></textarea><br>这个时候得出结论是,可以配置多个TransactionManager,但是必须有一个的名字是transactionManager。<br><br>第四步、配置多个<annotation-driven>,如下:<br><textarea readonly name="code" class="java"><!-- 使用annotation定义事务 --><annotation-driven transaction-manager="transactionManager"><annotation-driven transaction-manager="transactionManagerIndex"></annotation-driven></annotation-driven></textarea><br>运行测试,天啦,竟然成功了。和我之前预料的完全不一样,居然在一个配置文件中配置多个<annotation-driven>一点 问题都没有。那么在使用@Transactional的地方,它真的能够选择正确的事务管理器吗?我不得不写更多的代码来进行测试。那就针对索引数据库中 的表写一个Entity,写一个Dao测试一下吧。<br><br>代码如下:<br><textarea readonly name="code" class="java">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; } } </textarea><br><textarea readonly name="code" class="java">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<articleindex long=""> { @Override @Resource(name = "sessionFactoryIndex") public void setSessionFactory(SessionFactory sessionFactory) { // TODO Auto-generated method stub super.setSessionFactory(sessionFactory); } }</articleindex></textarea><br><textarea readonly name="code" class="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.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<article> page = new Page<article>(); page.setPageSize(10); page.setPageNo(2); page = articleDao.getAll(page); List<article> 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("测试事务回滚"); } } </article></article></article></textarea><br>运行测试,结果还是成功的。到目前,发现在一个项目中使用多个TransactionManager可以正常运行,但是有两个问题需要考虑:<br>1、为什么必须得有一个TransactionManager名字为transactionManager?<br>2、这两个TransactionManager真的能正常工作吗?<br>3、OpenSessionInView的问题怎么解决?<br><br>以上的三个问题在单元测试中是不能找出答案的,我只好再去写Action层的代码,期望能够从中得到线索。经过一天艰苦的努力,终于真相大白:<br>1、并不是必须有一个TransactionManager的名字为transactionMananger,这只是单元测试在搞鬼,在真实的Web环境 中,无论两个TransactionManager取什么名字都可以,运行不会报错。所以这个答案很明确,是因为单元测试的基类需要一个名为 transactionMananger的事务管理器。<br>2、在单元测试中,只能测试Dao类和Entity类能否正常工作,但是由于单元测试结束后事务会自动回滚,不会把数据写入到数据库中,所以没有办法确定 两个TransactionManager能否正常工作。在真实的Web环境中,问题很快就浮出水面,只有一个数据库中有数据,另外一个数据库中没有,经 过调整<annotation-driven>的位置并对比分析,发现只有放在前面的TransactionMananger的事务 能够正常提交,放在后面的TransactionManager的事务不能提交,所以永远只有一个数据库里面有数据。<br>3、如果早一点脱离单元测试而进入真实的Web环境,就会早一点发现OpenSessionInViewFilter的问题,因为只要配置多个 SessionFactory,运行的时候OpenSessionInViewFilter就会报错。为了解决这个问题,我只能去阅读 OpenSessionInViewFilter的源代码,发现它在将Session绑定到线程的时候用的是Map,而且使用 SessionFactory作为Map的key,这就说明在线程中绑定多个Session不会冲突,也进一步说明可以在web.xml中配置多个 OpenSessionInViewFilter。而我也正是通过配置多个OpenSessionInViewFilter来解决问题的。我的 web.xml文件如下:<br><textarea readonly name="code" class="java">&lt; xml version="1.0" encoding="UTF-8" &gt; <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"><display-name>PureText</display-name><!-- Spring ApplicationContext配置文件的路径,可使用通配符,多个路径用,号分隔 此参数用于后面的Spring Context Loader --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath*:/applicationContext*.xml</param-value></context-param><!-- Character Encoding filter --><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><!-- SpringSecurity filter--><filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><!-- Struts2 filter --><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></textarea><br>经过上面的分析,发现使用多个TransactionManager是不可行的(这个时候我在想,也许不使用Annotation就可以使用多个 TransactionMananger吧,毕竟Spring的AOP应该是可以把不同的TransactionManager插入到不同的类和方法中, 但是谁愿意走回头路呢?毕竟都已经是@Transactional的年代了),虽然运行不会报错,但是只有一个TransactionManager的事 务能够正常提交。所以测试进入下一步:<br><br>第五步、使用JTATransactionManager<br><br>简单地修改配置文件,使用JTATransactionManager做为事务管理器,配置文件我就不列出来了,运行,结果抱错,错误信息如下:<br>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'<br><br>通过分析,发现其中最关键的一句是No JTA UserTransaction available,看来,我们只能进入到第六步,使用GlassFish了。<br><br>第六步、将项目部署到GlassFish中<br><br>将项目简单地部署到GlassFish中之后,项目可以成功运行,没有报错,说明JTA UserTransaction问题解决了,但是检查数据库却发现依然没有数据,看来JTATransactionManager不仅要和应用服务器配合 使用,还要和JNDI数据源一起使用。将数据源的配置修改为JNDI后,问题解决。下面是我的配置文件:<br><textarea readonly name="code" class="java">&lt; xml version="1.0" encoding="UTF-8" &gt; <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" default-lazy-init="true" 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"><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><!-- 服务器生产环境配置 --><!-->file:/var/myapp/application.server.properties --&gt; </!--></list></property></property></property></bean><!-- 使用annotation 自动注册bean,并保证@Required,@Autowired的属性被注入 --><component-scan base-package="cn.puretext"><!-- 数据源配置,使用应用服务器的数据库连接池 --><jndi-lookup id="dataSourceContent" jndi-name="jdbc/dataSourceContent"><jndi-lookup id="dataSourceIndex" jndi-name="jdbc/dataSourceIndex"><!-- 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"></bean></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.*"></property></property></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"></bean></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.*"></property></property></bean><!-- 事务管理器配置,单数据源事务 --><!-- id="transactionManagerContent" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactoryContent"> <bean id="transactionManagerIndex" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactoryIndex"> </property></bean> --&gt; <!-- 事务管理器配置,多数据源JTA事务--><bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"><!-- 使用annotation定义事务 --><annotation-driven transaction-manager="transactionManager"></annotation-driven></bean>!--&gt;</jndi-lookup></jndi-lookup></component-scan></beans></textarea><br>最后,我得出的结论是:要想使用多个数据库,就必须使用JTATransactionMananger,必须使用GlassFish等应用服务器而不是Tomcat,必须使用JNDI来管理dataSource。<br><br>如果一定要使用Tomcat呢?<br><br>这确实是一个难题,但是并不代表着没有解决办法。经过广泛的Google一番之后,终于发现了一个好东东,那就是JOTM,它的全称就是Java Open Transaction Mananger,它的作用就是可以单独提供JTA事务管理的功能,不需要应用服务器。JOTM的使用方法有两种,一种就是把它配置到项目中,和 Spring结合起来使用,另外一种就是把它配置到Tomcat中,这时,Tomcat摇身一变就成了和GlassFish一样的能够提供JTA功能的服 务器了。<br><br>JOTM的官方网站为http://jotm.ow2.org,这是它的新网站,旧网站为http://jotm.objectweb.org。<br><br>我选择了把JOTM 2.0.11整合到Tomcat中的方法进行了测试,结果发现还是不能够正常运行,我使用的是JOTM2.0.11,Tomcat 6.0.20,JKD 6 Update10。看来还得继续折腾下去了。<br><br>另外一个开源的JTA事务管理器是Atomikos,它供了事务管理和连接池,不需要应用服务器支持,其官方网站为http://www.atomikos.com/。有兴趣的朋友可以试试。 </annotation-driven></annotation-driven></annotation-driven></annotation-driven></annotation-driven>
分享到:
评论

相关推荐

    spring数据源配置

    ### Spring 数据源配置详解 #### 一、Spring与数据源简介 在Java企业级应用开发中,数据库操作是必不可少的一部分。Spring框架作为一种流行的轻量级Java应用开发框架,提供了强大的数据库访问支持,其中包括对数据...

    spring boot多数据源配置

    一、配置多数据源 1. 引入依赖 首先,我们需要在`pom.xml`或`build.gradle`文件中添加相应的Spring Boot数据源依赖,例如MySQL和Oracle: ```xml &lt;groupId&gt;org.springframework.boot &lt;artifactId&gt;spring-boot-...

    Spring多数据源分布式事务管理

    在大型分布式系统中,往往需要处理多个数据源,这就涉及到了Spring多数据源的配置和管理。同时,为了保证数据的一致性,分布式事务的管理也是必不可少的。在这个场景下,Atomikos作为一款开源的JTA(Java ...

    spring 动态多数据源配置代码

    下面将详细介绍Spring动态多数据源配置的相关知识点。 1. **为什么要使用多数据源**: 在实际项目中,可能需要连接到不同的数据库,例如,一个用于存储主业务数据,另一个用于日志记录或数据分析。通过多数据源...

    Spring Boot多数据源(支持Spring声明式事务切换和回滚).pdf

    6. **多数据源事务回滚**: - 提供了跨数据源的事务回滚能力,简化了系统设计,降低了处理多数据源时的复杂性。在除了分布式事务外的大多数情况下,只需考虑这个机制,就能保证事务的正确性。 在多租户SaaS架构中...

    Spring 数据源不同配置

    Spring支持多种数据源配置方式,包括基本的JDBC数据源、连接池数据源以及事务管理策略等。下面将详细介绍Spring中常见的数据源配置及其相关知识点。 1. **基本JDBC数据源** Spring提供了一个名为`BasicDataSource`...

    springboot多数据源配置

    接下来,我们将详细讨论如何在Spring Boot中配置多个数据源。 首先,我们需要理解数据源的概念。数据源(DataSource)是Java中用于存储和管理数据库连接的接口,它允许应用程序与数据库进行交互。在Spring Boot中,...

    SSM(Spring+SpringMVC+MyBatis)多数据源配置框架

    9. **安全性**:多数据源配置也可能涉及安全问题,如权限控制和数据隔离,需要确保不同数据源间的访问控制得到妥善处理。 综上所述,SSM多数据源配置框架是一种强大的工具,它为企业级应用提供了灵活的数据处理能力...

    java spring 多数据源

    1. **配置多数据源** - 创建两个或更多的数据源配置类,每个数据源对应一个配置类,例如:`PrimaryDataSourceConfig` 和 `SecondaryDataSourceConfig`。 - 在配置类中,使用`@ConfigurationProperties`注解绑定...

    springboot实现多数据源而且加上事务不会使aop切换数据源失效

    首先,我们需要配置多数据源。在Spring Boot中,可以使用`DataSource`接口的实现类,如`HikariCP`或`Druid`,创建两个不同的数据源。在`application.properties`或`application.yml`中,为每个数据源定义不同的...

    基于注解和Spring的多数据源配置和使用

    5. **配置事务管理器**:对于多数据源,我们需要配置多个事务管理器,并通过`@Transactional`注解指定使用哪个事务管理器。例如: ```java @Configuration @EnableTransactionManagement public class ...

    SpringBoot配置多数据源实现动态切换数据源

    本文将深入探讨如何在SpringBoot项目中配置多数据源,并实现数据源的动态切换,帮助你理解和掌握这一核心技能。 首先,我们理解"多数据源"的概念。在SpringBoot应用中,多数据源意味着系统能够连接并操作多个不同的...

    spring多数据源

    3. **配置多数据源**:在Spring的XML配置文件中,我们需要定义多个DataSource bean,分别代表不同的数据库连接。这些数据源可以是Apache Commons DBCP、HikariCP、Druid等连接池的实现。例如: ```xml &lt;!-- 配置...

    Spring Boot使用spring-data-jpa配置Mysql多数据源

    本教程将详细介绍如何在Spring Boot项目中配置多个数据源,并利用`spring-data-jpa`进行数据操作。 首先,我们需要在`pom.xml`中添加必要的依赖。对于`spring-boot-starter-data-jpa`和MySQL的驱动,你需要如下依赖...

    Spring多数据源配置

    本文将详细探讨如何在Spring环境中配置多个数据源,包括DataSource、SessionFactory、TransactionManager以及JTATransactionManager的设置。 #### 第一步:配置多个DataSource 在Spring中,配置多个数据源主要是...

    Spring配置多个数据源

    4. **配置多数据源事务管理器** 为了处理涉及多个数据源的事务,我们需要使用`PlatformTransactionManager`的实现,例如`DataSourceTransactionManager`,并指定对应的数据源: ```java @Bean(name = ...

    Springcloud 多数库 多数据源整合,查询动态切换数据库

    Spring的PlatformTransactionManager接口可以扩展以支持多数据源事务。我们可以使用`AbstractRoutingDataSource`作为基础,创建一个动态路由数据源,根据业务逻辑决定事务所涉及的数据源。 7. **数据库读写分离**:...

    Springboot 动态多数据源 jta分布式事务

    配置多数据源通常涉及到以下几个步骤: 1. 引入依赖:在`pom.xml`或`build.gradle`文件中添加相应的数据库驱动依赖,如MySQL、Oracle等。 2. 配置数据源:创建多个DataSource Bean,每个Bean对应一个数据库连接配置...

    基于Spring多数据源实例

    1. **配置数据源**:在Spring的配置文件中,我们需要定义多个DataSource bean,每个bean对应一个数据源。例如,我们可以创建名为`dataSource1`和`dataSource2`的bean,分别配置它们的连接信息(如URL、用户名、密码...

    Spring Boot多数据源(JdbcTemplate)配置与使用

    配置多数据源在Spring Boot中主要涉及以下几个步骤: 1. 引入依赖:在`pom.xml`或`build.gradle`文件中添加Spring Boot的JDBC和数据源相关依赖。例如,如果你使用的是MySQL,可以添加如下Maven依赖: ```xml ...

Global site tag (gtag.js) - Google Analytics