2010.01.05——spring配置两个数据源
请参考:http://www.iteye.com/topic/78432
因为系统需要,有多个表空间,所以要给spring配置多个数据源,我们是spring+hibernate的系统,估计
spring配置文件如下:
spring.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:aop="http://www.springframework.org/schema/aop"
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.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-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/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- 数据库外部文件配置 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list><value>classpath:db.properties</value></list>
</property>
<property name="fileEncoding" value="utf-8" />
</bean>
<!-- 数据库外部文件配置 -->
<!-- 配置数据源 使用dbcp数据源 -->
<bean id="dataSource1"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName"
value="${jdbc.driverClassName1}" />
<property name="url" value="${jdbc.url1}" />
<property name="username" value="${jdbc.username1}" />
<property name="password" value="${jdbc.password1}" />
</bean>
<!-- 配置数据源2 -->
<bean id="dataSource2"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName"
value="${jdbc.driverClassName2}" />
<property name="url" value="${jdbc.url2}" />
<property name="username" value="${jdbc.username2}" />
<property name="password" value="${jdbc.password2}" />
</bean>
<!-- 配置数据源 使用dbcp数据源 -->
<!-- Hibernate SessionFactory配置 -->
<bean id="sessionFactory1"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource1" />
<property name="annotatedClasses">
<list>
<value>com.pojo.DT_RGNCD</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.OracleDialect
</prop>
<prop key="show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.use_sql_comments">true</prop>
</props>
</property>
</bean>
<!-- sessionFactory2 -->
<bean id="sessionFactory2"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource2" />
<property name="annotatedClasses">
<list>
<value>com.pojo.ST_STBPRP_B</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.OracleDialect
</prop>
<prop key="show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.use_sql_comments">true</prop>
</props>
</property>
</bean>
<!-- Hibernate SessionFactory配置 -->
<!-- Hibernate事务和hibernateTemplate -->
<bean id="transactionManager1"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory1" />
</bean>
<bean id="hibernateTemplate1"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory1" />
</bean>
<bean id="transactionManager2"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory2" />
</bean>
<bean id="hibernateTemplate2"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory2" />
</bean>
<!-- Hibernate事务和hibernateTemplate -->
<!-- Aop 事务管理控制 -->
<aop:config proxy-target-class="false">
<!-- com.service.*.* 下的类的方法使用事务控制 -->
<aop:advisor
pointcut="execution(* com.service.*.*(..))"
advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager1">
<tx:attributes>
<!-- get find等查询方法不使用事务 其他方法都使用事务控制,当发生异常时
,整个方法事务回滚 -->
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="*" propagation="REQUIRED"
rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<!-- Aop 事务管理控制 -->
<!-- Dao -->
<!-- query -->
<bean id="DT_RGNCDDao" class="com.dao.imp.DT_RGNCDDaoImp">
<property name="hibernateTemplate">
<ref bean="hibernateTemplate1"/>
</property>
</bean>
<!-- query -->
<!-- query_su9921 -->
<bean id="ST_STBPRP_BDao" class="com.dao.imp.ST_STBPRP_BDaoImp">
<property name="hibernateTemplate">
<ref bean="hibernateTemplate2"/>
</property>
</bean>
<!-- query_su9921 -->
<!-- Dao -->
<!-- Service -->
<bean id="yuliangService" class="com.service.imp.YuliangServiceImp">
<property name="DT_RGNCDDao">
<ref bean="DT_RGNCDDao"/>
</property>
</bean>
<!-- Service -->
</beans>
对了 db.properties的内容如下
#MySql
#jdbc.driverClassName=com.mysql.jdbc.Driver
#jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
#jdbc.username=root
#jdbc.password=123456
#oracle
#query
jdbc.driverClassName1=oracle.jdbc.driver.OracleDriver
jdbc.url1=jdbc:oracle:thin:@192.168.0.173:1521:KHMS
jdbc.username1=query
jdbc.password1=query
#query_su9921
jdbc.driverClassName2=oracle.jdbc.driver.OracleDriver
jdbc.url2=jdbc:oracle:thin:@192.168.0.173:1521:KHMS
jdbc.username2=query_su9921
jdbc.password2=query_su9921
上面的spring.xml的配置还是有很多问题:
1.我要两个dataSource,就必须有两个SessionFactory,两个transactionManager,两个HibernateTample,这
样不管成不成功都是有问题的
2.这样子写,老是没法通过junit的测试,在网上也找了好多办法但是总是不行
所以,就想改进一下,在这个过程中我遇到最多的异常就是,唯一的dataSource,找到了两个,我就想,能
不能把两个dataSource弄成一个那,一下就是基于这个出发点写的
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
Spring2.0.1以后的版本已经支持配置多数据源,并且可以在运行的时候动态加载不同的数据源。通过继承
AbstractRoutingDataSource就可以实现多数据源的动态转换。
CustomerContextHolder.java
/**
* 把当前使用的数据源保存在当前线程中,放在contextHolder中
* @author 谢 2010-1-5
*/
public class CustomerContextHolder {
private static final ThreadLocal contextHolder =
new ThreadLocal();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return (String) contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
AbstractRoutingDataSource.java
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 继承自AbstractRoutingDataSource的类可以用来选着使用哪个数据源
* 根据determineCurrentLookupKey返回的内容来判断
* 当前的做法是把保存在线程中的CustomerContextHolder.getCustomerType()值来判断。
* @author 谢 2010-1-5
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}
spring.xml
<!-- 配置数据源 使用dbcp数据源 -->
<!-- 配置数据源1 -->
<bean id="query" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="${jdbc.driverClassName1}" />
<property name="url" value="${jdbc.url1}" />
<property name="username" value="${jdbc.username1}" />
<property name="password" value="${jdbc.password1}" />
</bean>
<!-- 配置数据源2 -->
<bean id="query_su9921" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="${jdbc.driverClassName2}" />
<property name="url" value="${jdbc.url2}" />
<property name="username" value="${jdbc.username2}" />
<property name="password" value="${jdbc.password2}" />
</bean>
<bean id="dataSource" class="com.huitu.khms.util.DynamicDataSource">//这个就是我们上面自己
写的数据源
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="query" value-ref="query"/>
<entry key="query_su9921" value-ref="query_su9921"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="query"/>//这个是默认加载那个数据源
</bean>
<!-- 配置数据源 使用dbcp数据源 -->
后面sessionFactory和transactionManager的配置还是一样的
<!-- Hibernate事务和hibernateTemplate -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- Hibernate事务和hibernateTemplate -->
然后测试
@ContextConfiguration(locations={"classpath:spring.xml","classpath:mvc-
servlet.xml"},inheritLocations=false)
public class TestDao {
private DT_RGNCDDao DT_RGNCDDao;
private ST_RSVRFCCH_BDao ST_RSVRFCCH_BDao;
YuliangService yuliangService;
@Autowired
public void setDataSource(@Qualifier("dataSource") DataSource dataSource) {
super.setDataSource(dataSource);
}
@Test
public void test3(){
ST_RSVRFCCH_BDao = (ST_RSVRFCCH_BDao) ctx.getBean("ST_RSVRFCCH_BDao");
CustomerContextHolder.setCustomerType("query_su9921");
DT_RGNCDDao = (DT_RGNCDDao) ctx.getBean("DT_RGNCDDao");
System.out.println(ST_RSVRFCCH_BDao.findAll().size());
CustomerContextHolder.setCustomerType("query");
System.out.println(DT_RGNCDDao.findAll().size());
}
}
这样测试时,就遇到一个问题,每次总是只打印一个,换句话说,就是这个datasource没有切换,这个问题
花了好长时间,最后不用spring的@ContextConfiguration,如下
public class TestCase {
public ApplicationContext ctx = null;
@Before
public void setUp(){
ctx = new ClassPathXmlApplicationContext(new String[]
{"classpath:spring.xml","classpath:mvc-servlet.xml"});
}
@Test
public void testService(){
CustomerContextHolder.setCustomerType("query");
DT_RGNCDDao DT_RGNCDDao = (DT_RGNCDDao) ctx.getBean("DT_RGNCDDao");
System.out.println(DT_RGNCDDao.findAll().size());
CustomerContextHolder.setCustomerType("query_su9921");
ST_RSVRFCCH_BDao ST_RSVRFCCH_BDao = (ST_RSVRFCCH_BDao) ctx.getBean
("ST_RSVRFCCH_BDao");
System.out.println(ST_RSVRFCCH_BDao.findAll().size());
}
}
但是每次都写CustomerContextHolder.setCustomerType("query");是很麻烦的,所以应该配一个
BeforeAdvice
public class DaoAdvice implements MethodBeforeAdvice{
public void before(Method method, Object[] args, Object obj)
throws Throwable {
String classSimpleName = obj.getClass().getSimpleName();
String className = classSimpleName.substring(0, classSimpleName.length()-3);
if(className.equals("ST_RSVRFCCH_BDao")){
CustomerContextHolder.setCustomerType("query_su9921");
System.out.println("------------ST_RSVRFCCH_BDao---------query_su9921-
----------------");
}else if(className.equals("DT_RGNCDDao")){
System.out.println("------------DT_RGNCDDao---------query-------------
----");
CustomerContextHolder.setCustomerType("query");
}
}
}
这样就不用每次都写CustomerContextHolder了,spring.xml里面的配置如下:
<aop:config proxy-target-class="false">
<aop:advisor
pointcut="execution(* com.huitu.khms.dao..*.*(..))"
advice-ref="daoAdvice" />
</aop:config>
<bean id="daoAdvice" class="com.huitu.khms.util.DaoAdvice">
</bean>
但是还有一个问题,就是当我调用service时,就只会出现一个了,我的sevice如下
public class YuliangServiceImp implements YuliangService{
public void test() {
System.out.println(DT_RGNCDDao.findAll().size());
System.out.println(ST_RSVRFCCH_BDao.findAll().size());
}
public DT_RGNCDDao getDT_RGNCDDao() {
return DT_RGNCDDao;
}
public void setDT_RGNCDDao(DT_RGNCDDao dao) {
DT_RGNCDDao = dao;
}
public ST_RSVRFCCH_BDao getST_RSVRFCCH_BDao() {
return ST_RSVRFCCH_BDao;
}
public void setST_RSVRFCCH_BDao(ST_RSVRFCCH_BDao dao) {
ST_RSVRFCCH_BDao = dao;
}
private DT_RGNCDDao DT_RGNCDDao;
private ST_RSVRFCCH_BDao ST_RSVRFCCH_BDao;
}
最后发现,当我把implements YuliangService去掉时,它就可以测试了没有错误了,这个问题一直很郁闷,
不知道原因,后来发现当我把spring.xml里面的事务去掉后,就可以正常运行了,
<!-- Aop 事务管理控制 -->
<!--
<aop:config proxy-target-class="false">
<aop:advisor
pointcut="execution(* com.dao.*.*(..))"
advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="*" propagation="REQUIRED"
rollback-for="Exception" />
</tx:attributes>
</tx:advice>
-->
<!-- Aop 事务管理控制 -->
这个原因一直我也不是很明白,云里雾里的。。。。
上面那个BeforeAdvice还是有点问题,如果我的dao层方法很多呐,总不能全是if语句吧,所以,做了一下改
动
首先看spring.xml
<aop:config proxy-target-class="false">
<aop:advisor
pointcut="execution(* com.huitu.khms.dao..*.*(..))"
advice-ref="daoAdvice" />
</aop:config>
<!-- 拦截所有的dao,根据配置自动判断使用数据源 -->
<bean id="daoAdvice" class="com.huitu.khms.util.DaoAdvice">
<property name="dataSources">
<map>
<entry key="query">
<value>dao1,dao2,dao3</value>
</entry>
<entry key="query_su9921">
<value>dao11,dao22,dao33</value>
</entry>
</map>
</property>
</bean>
再看这个BeforeAdvice
/**
*
* 这个类拦截所有的dao的方法,根据dataSources中配置自动判断当前这个dao使用对应的数据源
* @author 谢 2010-1-7
*
*/
public class DaoAdvice implements MethodBeforeAdvice{
public void before(Method method, Object[] args, Object obj)
throws Throwable {
String classSimpleName = obj.getClass().getSimpleName();
//当前的dao的className
String className = classSimpleName.substring(0, classSimpleName.length()-3);
Set keys = dataSources.keySet();
for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
//dataSource数据源名称
String dataSource = (String) iterator.next();
//所有daos
String daos = ","+dataSources.get(dataSource)+",";
if(daos.indexOf(className)!=-1){
//设置当前dao使用的数据源
CustomerContextHolder.setCustomerType(dataSource);
break;
}
}
}
public Map getDataSources() {
return dataSources;
}
public void setDataSources(Map dataSources) {
this.dataSources = dataSources;
}
/*
* key 是数据源
* value 是daos,使用key的数据库的dao
*/
private Map dataSources;
}