spring中JTA事物的配置

来源:互联网 发布:大淘客cms建站利弊 编辑:程序博客网 时间:2024/05/21 14:46
Spring 通过AOP技术可以让我们在脱离EJB的情况下享受声明式事务的丰盛大餐,脱离Java EE应用服务器使用声明式事务的道路已经畅通无阻。但是很大部分人都还认为脱离Java EE应用服务器就无法使用JTA事务,这是一个误解。其实,通过配合使用ObjectWeb的JOTM开源项目,不需要Java EE应用服务器,Spring也可以提供JTA事务。

正因为AOP让Spring拥有了脱离EJB容器的声明式事务能力,而JOTM让我们在脱离Java EE应用服务器下拥有JTA事务能力。所以,人们将AOP和JOTM称为Java软件开发的两个圣杯。

本文将讲解Spring在不同环境下提供JTA事务的配置过程,这包括:Spring中直接集成JOTM提供JTA事务管理、将JOTM集成到 Tomcat中,Spring通过引用Tomcat JNDI数据源提供JTA事务管理、引用其他功能完善JavaEE应用服务器所提供的JTA事务管理。

  通过集成JOTM,直接在Spring中使用JTA事务

   JOTM(Java Open Transaction Manager)是ObjectWeb的一个开源JTA实现,它本身也是开源应用程序服务器JOnAS(Java Open Application Server)的一部分,为其提供JTA分布式事务的功能。

  Spring 2.0附带的依赖类库中虽然包含jotm类库,但是并不完整,你可以到http://jotm.objectweb.org下载完全版的JOTM。

  Spring为JOTM提供了一个org.springframework.transaction.jta.JotmFactoryBean支持类,通过该支持类可以方便地创建JOTM本地实例。

下面,我们通过配置,使上节中BbtForumImpl#addTopic()方法工作在JTA事务的环境下。addTopic()内部使用两个 DAO类(TopicDao和PostDao)分别访问不同数据库中的表。通过下面的步骤说明了使addTopic()方法拥有JTA事务的整个过程:



  1. 将JOTM以下类库添加到类路径中:

  jotm.jar

  xapool.jar

  jotm_jrmp_stubs.jar

  jta-spec1_0_1.jar

  connector-1_5.jar

   2. 编写JOTM配置文件,放到类路径下

  carol.properties

  #JNDI调用协议

  carol.protocols=jrmp

  #不使用CAROL JNDI封装器

  carol.start.jndi=false

  #不启动命名服务器

  carol.start.ns=false

   3. 在MySQL上建立两个数据库

  在MySQL数据库中运行SQL脚本,建立topicdb和postdb两个数据库,在topicdb数据库中创建t_topic表,在postdb数据库中创建t_post表。我们希望在这两个数据库上进行JTA事务。SQL脚本如下所示:

/**//*=========创建topicdb数据源及t_topic表==============*/
DROP DATABASE IF EXISTS topicdb;
CREATE DATABASE topicdb DEFAULT CHARACTER SET utf8;
USE topicdb;
drop table if exists t_topic;
create table t_topic
(
topic_id int(11) not null auto_increment,
forum_id int(11) not null default 0,
topic_title varchar(100) not null default '',
user_id int(11) not null default 0,
topic_time datetime default NULL,
topic_views int(11) default 1,
topic_replies int(11) default 0,
primary key (topic_id)
);
create index IDX_TOPIC_USER_ID on t_topic
(
user_id
);


/*=========创建postdb数据源及t_ postdb表==============*/


DROP DATABASE IF EXISTS postdb;
CREATE DATABASE postdb DEFAULT CHARACTER SET utf8;
USE postdb;
create table t_post
(
post_id int(11) not null auto_increment,
topic_id int(11) not null default 0,
forum_id int(11) not null default 0,
user_id int(11) not null default 0,
post_text text,
post_attach blob,
post_time datetime default NULL,
primary key (post_id)
);
create index IDX_POST_TOPIC_ID on t_post
(
topic_id
);



   4. 在Spring配置文件中配置JOTM

代码清单 1 applicationContext-jta.xml

  …

  <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean" />①JOTM本地实例

  ②JTA事务管理器

  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager">

  <property name="userTransaction" ref="jotm" /> ②-1:指定userTransaction属性

  </bean>

  ③XAPool配置,内部包含了一个XA数据源,对应topicdb数据库

  <bean id="topicDS" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"

  destroy-method="shutdown">

  <property name="dataSource">

  ③-1:内部XA数据源

  <bean class="org.enhydra.jdbc.standard.StandardXADataSource"

  destroy-method="shutdown">

  <property name="transactionManager" ref="jotm" />

  <property name="driverName" value="com.MySQL.jdbc.Driver" />

  <property name="url" value="jdbc:MySQL://localhost:3309/topicdb" />

  </bean>

  </property>

  <property name="user" value="root" />

  <property name="password" value="1234" />

  </bean>

  ④按照③相似的方式配置另一个XAPool,对应postdb数据库,



<bean id="postDS" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"
destroy-method="shutdown">
<property name="dataSource">
<bean class="org.enhydra.jdbc.standard.StandardXADataSource"
destroy-method="shutdown">
<property name="transactionManager" ref="jotm" />
<property name="driverName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3309/postdb" />
</bean>
</property>
<property name="user" value="root" />
<property name="password" value="1234" />
</bean>
⑤配置访问topicDB数据源的Spring JDBC模板

  <bean id="topicTemplate"

  class="org.springframework.jdbc.core.JdbcTemplate">

  <property name="dataSource" ref="topicDS" />

  </bean>

  ⑥配置访问postDB数据源的Spring JDBC模板

  <bean id="postTemplate"

  class="org.springframework.jdbc.core.JdbcTemplate">

  <property name="dataSource" ref="postDS" />

  </bean>

  ⑦基于topicTemplate数据源的topicDao

  <bean id="topicDao" class="com.baobaotao.dao.jdbc.TopicJdbcDao">



  <property name="jdbcTemplate" ref="topicTemplate" />

  </bean>

  ⑧基于postTemplate数据源的postDao

  <bean id="postDao" class="com.baobaotao.dao.jdbc.PostJdbcDao">

  <property name="jdbcTemplate" ref="postTemplate" />

  </bean>

  ⑨进行跨数据库JTA事务的业务类

  <bean id="bbtForum" class="com.baobaotao.service.impl.BbtForumImpl">

  <property name="topicDao" ref="topicDao" />

  <property name="postDao" ref="postDao" />

  </bean>

⑩对BbtForumImpl业务类中的@Transaction注解进行驱动

  <tx:annotation-driven transaction-manager="txManager" />

首 先,我们在①处通过Spring所提供的JotmFactoryBean创建一个本地JOTM实例,该实例同时实现了  javax.transaction.UserTransaction和javax.transaction.TransactionManager接 口,它可以和ObjectWeb的XAPool一起工作。

   JTA事务管理器通过userTransaction属性引用本地JOTM实例,Spring的JtaTransactionManager会自动探测 到传入的javax.transaction.UserTransaction引用也实现了 javax.transaction.TransactionManager,所以我们无需再配置JtaTransactionManager的 transactionManager属性,如②所示。

   在Spring中配置JOTM的另一个关键问题是配置XAPool,支持JTA事务的数据源必须封装成XAPool。首先,我们通过 org.enhydra.jdbc.standard.StandardXADataSource 配置一个XA数据源,它指向topicdb数据库,如③-1所示。而后,通过 org.enhydra.jdbc.pool.StandardXAPoolDataSource将其封装成一个XAPool,如③所示。按照相同的方 式,配置指向postdb数据库的XAPool,如④所示。



   接下来的配置就顺理成章了,分别使用Spring JDBC的模板类配置DAO类,然后再配置引用DAO类的业务类。关于Spring JDBC的详细内容,参见第10章的内容。

这里,我们使用@Transaction注解对业务类BbtForumImpl进行事务声明,所以通过<tx:annotation-driven/>对此进行驱动,BbtForumImpl的代码如下所示:

  代码清单 2 BbtForumImpl

package com.baobaotao.service.impl;
import org.springframework.transaction.annotation.Transactional;
import com.baobaotao.dao.PostDao;
import com.baobaotao.dao.TopicDao;
import com.baobaotao.domain.Forum;
import com.baobaotao.domain.Topic;
import com.baobaotao.service.BbtForum;
@Transactional ①事务注解,以便Spring动态织入事务管理功能
public class BbtForumImpl implements BbtForum ...{
private TopicDao topicDao;
private PostDao postDao;
public void addTopic(Topic topic) throws Exception ...{ ②将方法将被施加JTA事务的增强
topicDao.addTopic(topic);
postDao.addPost(topic.getPost());
}
}

  BbtForumImpl将Dao类组织起来,PostDao和TopicDao分别访问不同数据库中表,通过Spring注解驱动事务切面的增强后,它们将工作于同一个JTA事务中。

  5. 在Spring中运行测试

  代码清单 3 TestBbtForumJta

package com.baobaotao.service;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;

public class TestBbtForumJta extends AbstractDependencyInjectionSpringContextTests...{
private BbtForum bbtForum;
private final Logger logger = Logger.getLogger(getClass());
public void setBbtForum(BbtForum bbtForum) ...{
this.bbtForum = bbtForum;
}
protected String[] getConfigLocations() ...{
return new String[]...{"classpath:applicationContext-jta.xml"};
}
public void testAddPost() throws Exception...{
logger.info("begin........");
Topic topic = new Topic();
topic.setTopicTitle("Title -pfb");
Post post = new Post();
post.setPostText("post content -pfb");
topic.setPost(post);
bbtForum.addTopic(topic); ①使用了JTA事务的业务方法
logger.info("end........");
}
}



   通过Spring测试类AbstractDependencyInjectionSpringContextTests的支持,很容易编写一个测试类, 对启用了JTA事务的BbtForum#addTopic()方法进行测试。建议你将Log4J设置为DEBUG,这样就可以通过丰富的输出日志观测到 JTA事务的执行情况。运行这个测试类后,你将可以看到JTA事务被正确实施。

  Spring引用Tomcat的 JTA事务

Tomcat是Servlet容器,但它提供了JNDI的实现,因此用户可以象在Java EE应用程序服务器中一样,在Tomcat中使用JNDI查找JDBC数据源。在事务处理方面,Tomcat本身并不支持JTA,但是可以通过集成JOTM达到目的。

如果你的应用最终部署到一个功能齐备的Java EE应用服务器上,也许你更希望使用Java EE应用服务器的JTA功能,这样可以利用应用服务器本身许多优化措施。下面,我们让Tomcat通过JNDI开放JOTM的JTA的数据源,进而在 Spring容器引用这个JNDI数据源,并在此基础上提供JTA事务。我们所使用的环境是:Tomcat 5.5+JOTM 2.3。

  1. 添加所需的JAR文件

  将JOTM以下类包添加到<Tomcat安装目录>/common/lib目录中:

  jotm.jar

  jotm_jrmp_stubs.jar

  jotm_iiop_stubs.jar

  ow_carol.jar

  jta-spec1_0_1.jar

  jts1_0.jar

  objectweb-datasource.jar

  xapool.jar

  howl.jar

  connector-1_5.jar

  同时,还需要添加相应数据库的JDBC驱动类包,例如MySQL的mysql.jar。

  2. 配置JOTM

  新建一个carol.properties配置文件,放置到<Tomcat安装目录>/common/classes目录下,配置文件内容如下:

  #JNDI调用协议

  carol.protocols=jrmp

  # 本地RMI调用

  carol.jvm.rmi.local.call=true



 # 不使用CAROL的JNDI封装器

  carol.start.jndi=false

  # 不启用命名服务器

  carol.start.ns=false

  # 命名工厂类

  carol.jndi.java.naming.factory.url.pkgs=org.apache.naming

  将carol.start.jndi设置为false,让JOTM不使用CAROL JNDI wrapper,从而可以避免类装载错误的发生。

  3. 配置Tomcat环境,配置JNDI的数据源

  在<Tomcat安装目录>/conf/context.xml文件中添加以下内容:

  <Resource name="jdbc/topicDS" auth="Container" type="javax.sql.DataSource" ①-1:JNDI数据源

  factory="org.objectweb.jndi.DataSourceFactory"

  username="root" password="1234"

  driverClassName="com.mysql.jdbc.Driver"

  url="jdbc:mysql://localhost:3309/topicdb"

  maxActive="30" maxIdle="30"/>

  <Resource name="jdbc/postDS" auth="Container" type="javax.sql.DataSource" ①-2:JNDI数据源

  factory="org.objectweb.jndi.DataSourceFactory"

  username="root" password="1234"

  driverClassName="com.mysql.jdbc.Driver"

  url="jdbc:mysql://localhost:3309/postdb"

  maxActive="30" maxIdle="30"/>

  ②JOTM JTA事务管理

  <Transaction factory="org.objectweb.jotm.UserTransactionFactory" jotm.timeout="60"/>

  在Tomcat中配置两个JNDI数据源,它们分别指向topicdb和postdb数据库,如①处所示。最后配置JOTM的JTA事务管理器,该事务管理器自动对两个JNDI中的数据源应用JTA事务。



   4. Spring中相应的配置

  让应用服务器提供JNDI数据源管理和JTA事务后,Spring肩上的担子减轻了许多,Spring要做的只是简单地引用JNDI的数据源,并启用JtaTransactionManager就可以了。

代码清单 4 applicationContext-jta-tomcat.xml:使用应用服务器的JTA支持

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">

 ①使用Tomcat JNDI的数据源

<jee:jndi-lookup id="topicDS" jndi-name="java:comp/env/jdbc/topicDS" />
<jee:jndi-lookup id="postDS" jndi-name="java:comp/env/jdbc/postDS" />
<bean id="topicTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="topicDS" />
</bean>
<bean id="postTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="postDS" />
</bean>
<bean id="topicDao" class="com.baobaotao.dao.jdbc.TopicJdbcDao">
<property name="jdbcTemplate" ref="topicTemplate" />
</bean>
<bean id="postDao" class="com.baobaotao.dao.jdbc.PostJdbcDao">
<property name="jdbcTemplate" ref="postTemplate" />
</bean>
<bean id="bbtForum" class="com.baobaotao.service.impl.BbtForumImpl">
<property name="topicDao" ref="topicDao" />
<property name="postDao" ref="postDao" />
</bean>

②只需要指定一个JTA事务管理器就可以了,Spring会自动使用Tomcat中的JTA事务功能

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
<tx:annotation-driven transaction-manager="txManager" />
</beans>



  在①处,我们通过Spring jee命名空间提供的<jee:jndi-lookup>标签获取应用服务器中的JNDI资源,并将它们声明为一个Bean以供持久化模板类引用。

和直接在Spring中使用JOTM不一样,这时,我们仅需要简单地配置一个JtaTransactionManager就可以了,该事务管理器将自动将JTA事务委托给应用程序器。

Spring引用Java EE应用服务器 JTA事务功能和 Tomcat+JOTM提供JTA事务功能的配置步骤基本相似,相信大家可以对照这个实例完成相应的配置。另外,这里的实例采用了Spring JDBC作为持久层实现技术,你完全可以通过少量的调整将其应用到JPA、Hibernate、iBatis等持久化实现技术中。

   在特定应用服务器使用JTA

   一般来说,Spring的事务抽象与应用服务器是无关的。不过,如果你如果希望事务管理器使用特定的UserTransaction 和 TransactionManager 对象(可以被自动探测),以获取更多的增强事务语义。这时,针对不同的Java EE应用服务器,Spring的事务管理器可以采取不同的配置。

   BEA WebLogic

在一个使 用WebLogic 7.0、8.1或更高版本的环境中,你一般会优先选用特定于WebLogic的 WebLogicJtaTransactionManager 类来取代基础的 JtaTransactionManager 类,因为在WebLogic环境中,该类提供了对Spring事务定义的完全支持,超过了标准的JTA语义。你可以使用以下的配置达到目的:

  <bean id="txManager" class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"/>

它的特性包括:支持事务名,支持为每个事务定义隔离级别,以及在任何环境下正确地恢复事务的能力。

   IBM WebSphere

   在WebSphere 5.1、5.0和4.x环境下,你可以使用Spring的 WebSphereTransactionManagerFactoryBean 类。这是一个工厂类,通过WebSphere的 静态访问方法(每个版本的WebSphere中都不同)获取到JTA TransactionManager 实例。一旦通过工厂bean获取到JTA TransactionManager 实例,就可以使用该实例装配一个Spring的 JtaTransactionManager bean,它封装了JTA UserTransaction,提供增强的事务语义。你可以按以下方式进行配置:

<bean id="wsJtaTm" class="org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean"/>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager ref="wsJtaTm"/>①引用WebSphere的JTA事务管理器
</bean>

  小结

你既可以在没有任务应用服务器支持的情况下,直接通过集成JOTM在Spring中使用JTA事务管理,也可以通过引用Java EE应用服务器的JNDI数据源,利用应用服务器提供的JTA事务功能间接实现Spring 中的JTA事务管理。为了利用一些高级Java EE应用服务器的JTA事务高级功能,你可以通过Spring所提供的特定于应用服务器的JTA事务管理器进行配置
====================================================================================================================================
在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:
<?xml version="1.0" encoding="UTF-8"?>
<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,如下:
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();
    }
}

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

第二步,配置多个SessionFactory,配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<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>
                
<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="sessionFactoryContent" />
    
</bean>

    
<!-- 事务管理器配置,多数据源JTA事务-->
    
<!--
        <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager or
        WebLogicJtaTransactionManager" />
    
-->

    
<!-- 使用annotation定义事务 -->
    
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

运行单元测试,报错,错误代码如下:
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<Article, Long> {

    @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"?>
<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>
                
<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="transactionManagerContent" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        
<property name="sessionFactory" ref="sessionFactoryContent" />
    
</bean>
    
<bean id="transactionManagerIndex" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        
<property name="sessionFactory" ref="sessionFactoryIndex" />
    
</bean>

    
<!-- 事务管理器配置,多数据源JTA事务-->
    
<!--
        <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager or
        WebLogicJtaTransactionManager" />
    
-->

    
<!-- 使用annotation定义事务 -->
    
<tx:annotation-driven transaction-manager="transactionManagerContent" />
</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" />
    
</bean>
    
<bean id="transactionManagerIndex" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        
<property name="sessionFactory" ref="sessionFactoryIndex" />
    
</bean>

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

第四步、配置多个<tx:annotation-driven/>,如下:
    <!-- 使用annotation定义事务 -->
    
<tx:annotation-driven transaction-manager="transactionManager" />
    
<tx:annotation-driven transaction-manager="transactionManagerIndex" />

运行测试,天啦,竟然成功了。和我之前预料的完全不一样,居然在一个配置文件中配置多个<tx:annotation-driven/>一点问题都没有。那么在使用@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<ArticleIndex, Long> {
    @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
<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("测试事务回滚");
    }
}


运行测试,结果还是成功的。到目前,发现在一个项目中使用多个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文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="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/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>

经过上面的分析,发现使用多个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:\Temp\1-PureText\WEB-INF\classes\applicationContext-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:\Temp\1-PureText\WEB-INF\classes\applicationContext.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"?>
<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" />

    
<!-- 数据源配置,使用应用服务器的数据库连接池 -->
    
<jee:jndi-lookup id="dataSourceContent" jndi-name="jdbc/dataSourceContent" />
    
<jee: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" />
        
</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>
                
<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="transactionManagerContent" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactoryContent" />
    </bean>
    <bean id="transactionManagerIndex" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactoryIndex" />
    </bean>
    
-->
    
    
<!-- 事务管理器配置,多数据源JTA事务-->
    
    
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
    

    
<!-- 使用annotation定义事务 -->
    
<tx:annotation-driven transaction-manager="transactionManager" />
    
</beans>

最后,我得出的结论是:要想使用多个数据库,就必须使用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/。有兴趣的朋友可以试试。