项目中我们经常会遇到多数据源的问题,尤其是数据同步或定时任务等项目更是如此。多数据源让人最头痛的,不是配置多个数据源,而是如何能灵活动态的切换数 据源。例如在一个spring和hibernate的框架的项目中,我们在spring配置中往往是配置一个dataSource来连接数据库,然后绑定 给sessionFactory,在dao层代码中再指定sessionFactory来进行数据库操作。
正如上图所示,每一块都是指定绑死的,如果是多个数据源,也只能是下图中那种方式。
可看出在Dao层代码中写死了两个SessionFactory,这样日后如果再多一个数据源,还要改代码添加一个SessionFactory,显然这并不符合开闭原则。
那么正确的做法应该是
代码如下:
1. applicationContext.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 4 xmlns:cache="http://www.springframework.org/schema/cache" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" 7 xmlns:jms="http://www.springframework.org/schema/jms" xmlns:lang="http://www.springframework.org/schema/lang" 8 xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:oxm="http://www.springframework.org/schema/oxm" 9 xmlns:p="http://www.springframework.org/schema/p" xmlns:task="http://www.springframework.org/schema/task" 10 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" 11 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 12 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 13 http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd 14 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd 15 http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd 16 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd 17 http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.1.xsd 18 http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.1.xsd 19 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd 20 http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.1.xsd 21 http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd 22 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd 23 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"> 24 25 <context:annotation-config /> 26 27 <context:component-scan base-package="com"></context:component-scan> 28 29 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 30 <property name="locations"> 31 <list> 32 <value>classpath:com/resource/config.properties</value> 33 </list> 34 </property> 35 </bean> 36 37 <bean id="dataSourceOne" class="com.mchange.v2.c3p0.ComboPooledDataSource" 38 destroy-method="close"> 39 <property name="driverClass" value="${dbOne.jdbc.driverClass}" /> 40 <property name="jdbcUrl" value="${dbOne.jdbc.url}" /> 41 <property name="user" value="${dbOne.jdbc.user}" /> 42 <property name="password" value="${dbOne.jdbc.password}" /> 43 <property name="initialPoolSize" value="${dbOne.jdbc.initialPoolSize}" /> 44 <property name="minPoolSize" value="${dbOne.jdbc.minPoolSize}" /> 45 <property name="maxPoolSize" value="${dbOne.jdbc.maxPoolSize}" /> 46 </bean> 47 48 <bean id="dataSourceTwo" class="com.mchange.v2.c3p0.ComboPooledDataSource" 49 destroy-method="close"> 50 <property name="driverClass" value="${dbTwo.jdbc.driverClass}" /> 51 <property name="jdbcUrl" value="${dbTwo.jdbc.url}" /> 52 <property name="user" value="${dbTwo.jdbc.user}" /> 53 <property name="password" value="${dbTwo.jdbc.password}" /> 54 <property name="initialPoolSize" value="${dbTwo.jdbc.initialPoolSize}" /> 55 <property name="minPoolSize" value="${dbTwo.jdbc.minPoolSize}" /> 56 <property name="maxPoolSize" value="${dbTwo.jdbc.maxPoolSize}" /> 57 </bean> 58 59 <bean id="dynamicDataSource" class="com.core.DynamicDataSource"> 60 <property name="targetDataSources"> 61 <map key-type="java.lang.String"> 62 <entry value-ref="dataSourceOne" key="dataSourceOne"></entry> 63 <entry value-ref="dataSourceTwo" key="dataSourceTwo"></entry> 64 </map> 65 </property> 66 <property name="defaultTargetDataSource" ref="dataSourceOne"> 67 </property> 68 </bean> 69 70 <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 71 <property name="dataSource" ref="dynamicDataSource" /> 72 <property name="hibernateProperties"> 73 <props> 74 <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> 75 <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop> 76 <prop key="hibernate.show_sql">false</prop> 77 <prop key="hibernate.format_sql">true</prop> 78 <prop key="hbm2ddl.auto">create</prop> 79 </props> 80 </property> 81 <property name="packagesToScan"> 82 <list> 83 <value>com.po</value> 84 </list> 85 </property> 86 </bean> 87 88 <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> 89 <property name="sessionFactory" ref="sessionFactory" /> 90 </bean> 91 92 <aop:config> 93 <aop:pointcut id="transactionPointCut" expression="execution(* com.dao..*.*(..))" /> 94 <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointCut" /> 95 </aop:config> 96 97 <tx:advice id="txAdvice" transaction-manager="transactionManager"> 98 <tx:attributes> 99 <tx:method name="add*" propagation="REQUIRED" /> 100 <tx:method name="save*" propagation="REQUIRED" /> 101 <tx:method name="update*" propagation="REQUIRED" /> 102 <tx:method name="delete*" propagation="REQUIRED" /> 103 <tx:method name="*" read-only="true" /> 104 </tx:attributes> 105 </tx:advice> 106 107 <aop:config> 108 <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor"> 109 <aop:pointcut id="daoOne" expression="execution(* com.dao.one.*.*(..))" /> 110 <aop:pointcut id="daoTwo" expression="execution(* com.dao.two.*.*(..))" /> 111 <aop:before pointcut-ref="daoOne" method="setdataSourceOne" /> 112 <aop:before pointcut-ref="daoTwo" method="setdataSourceTwo" /> 113 </aop:aspect> 114 </aop:config> 115 </beans>
2. DynamicDataSource.class
1 package com.core; 2 3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 4 5 public class DynamicDataSource extends AbstractRoutingDataSource{ 6 7 @Override 8 protected Object determineCurrentLookupKey() { 9 return DatabaseContextHolder.getCustomerType(); 10 } 11 12 }
3. DatabaseContextHolder.class
1 package com.core; 2 3 public class DatabaseContextHolder { 4 5 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); 6 7 public static void setCustomerType(String customerType) { 8 contextHolder.set(customerType); 9 } 10 11 public static String getCustomerType() { 12 return contextHolder.get(); 13 } 14 15 public static void clearCustomerType() { 16 contextHolder.remove(); 17 } 18 }
4. DataSourceInterceptor.class
1 package com.core; 2 3 import org.aspectj.lang.JoinPoint; 4 import org.springframework.stereotype.Component; 5 6 @Component 7 public class DataSourceInterceptor { 8 9 public void setdataSourceOne(JoinPoint jp) { 10 DatabaseContextHolder.setCustomerType("dataSourceOne"); 11 } 12 13 public void setdataSourceTwo(JoinPoint jp) { 14 DatabaseContextHolder.setCustomerType("dataSourceTwo"); 15 } 16 }
5. po实体类
1 package com.po; 2 3 import javax.persistence.Column; 4 import javax.persistence.Entity; 5 import javax.persistence.Id; 6 import javax.persistence.Table; 7 8 @Entity 9 @Table(name = "BTSF_BRAND", schema = "hotel") 10 public class Brand { 11 12 private String id; 13 private String names; 14 private String url; 15 16 @Id 17 @Column(name = "ID", unique = true, nullable = false, length = 10) 18 public String getId() { 19 return this.id; 20 } 21 22 public void setId(String id) { 23 this.id = id; 24 } 25 26 @Column(name = "NAMES", nullable = false, length = 50) 27 public String getNames() { 28 return this.names; 29 } 30 31 public void setNames(String names) { 32 this.names = names; 33 } 34 35 @Column(name = "URL", length = 200) 36 public String getUrl() { 37 return this.url; 38 } 39 40 public void setUrl(String url) { 41 this.url = url; 42 } 43 }
1 package com.po; 2 3 import javax.persistence.Column; 4 import javax.persistence.Entity; 5 import javax.persistence.Id; 6 import javax.persistence.Table; 7 8 @Entity 9 @Table(name = "CITY", schema = "car") 10 public class City { 11 12 private Integer id; 13 14 private String name; 15 16 @Id 17 @Column(name = "ID", unique = true, nullable = false) 18 public Integer getId() { 19 return id; 20 } 21 22 public void setId(Integer id) { 23 this.id = id; 24 } 25 26 @Column(name = "NAMES", nullable = false, length = 50) 27 public String getName() { 28 return name; 29 } 30 31 public void setName(String name) { 32 this.name = name; 33 } 34 }
6. BrandDaoImpl.class
1 package com.dao.one; 2 3 import java.util.List; 4 5 import javax.annotation.Resource; 6 7 import org.hibernate.Query; 8 import org.hibernate.SessionFactory; 9 import org.springframework.stereotype.Repository; 10 11 import com.po.Brand; 12 13 @Repository 14 public class BrandDaoImpl implements IBrandDao { 15 16 @Resource 17 protected SessionFactory sessionFactory; 18 19 @SuppressWarnings("unchecked") 20 @Override 21 public List<Brand> findAll() { 22 String hql = "from Brand"; 23 Query query = sessionFactory.getCurrentSession().createQuery(hql); 24 return query.list(); 25 } 26 }
7. CityDaoImpl.class
1 package com.dao.two; 2 3 import java.util.List; 4 5 import javax.annotation.Resource; 6 7 import org.hibernate.Query; 8 import org.hibernate.SessionFactory; 9 import org.springframework.stereotype.Repository; 10 11 import com.po.City; 12 13 @Repository 14 public class CityDaoImpl implements ICityDao { 15 16 @Resource 17 private SessionFactory sessionFactory; 18 19 @SuppressWarnings("unchecked") 20 @Override 21 public List<City> find() { 22 String hql = "from City"; 23 Query query = sessionFactory.getCurrentSession().createQuery(hql); 24 return query.list(); 25 } 26 }
8. DaoTest.class
1 package com.test; 2 3 import java.util.List; 4 5 import javax.annotation.Resource; 6 7 import org.junit.Test; 8 import org.junit.runner.RunWith; 9 import org.springframework.test.context.ContextConfiguration; 10 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 import org.springframework.test.context.transaction.TransactionConfiguration; 12 13 import com.dao.one.IBrandDao; 14 import com.dao.two.ICityDao; 15 import com.po.Brand; 16 import com.po.City; 17 18 @RunWith(SpringJUnit4ClassRunner.class) 19 @ContextConfiguration(locations = "classpath:com/resource/applicationContext.xml") 20 @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false) 21 public class DaoTest { 22 23 @Resource 24 private IBrandDao brandDao; 25 26 @Resource 27 private ICityDao cityDao; 28 29 @Test 30 public void testList() { 31 List<Brand> brands = brandDao.findAll(); 32 System.out.println(brands.size()); 33 34 List<City> cities = cityDao.find(); 35 System.out.println(cities.size()); 36 } 37 }
利用aop,达到动态更改数据源的目的。当需要增加数据源的时候,我们只需要在applicationContext配置文件中添加aop配置,新建个DataSourceInterceptor即可。而不需要更改任何代码。