在Spring 2.0.1中引入了AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。java
Spring动态配置多数据源,即在大型应用中对数据进行切分,而且采用多个数据库实例进行管理,这样能够有效提升系统的水平伸缩性。而这样的方案就会不一样于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求及系统状态来动态的决定将数据存储在哪一个数据库实例中,以及从哪一个数据库提取数据。spring
Spring对于多数据源,以数据库表为参照,大致上能够分红两大类状况:
一是,表级上的跨数据库。即,对于不一样的数据库却有相同的表(表名和表结构彻底相同)。
二是,非表级上的跨数据库。即,多个数据源不存在相同的表。
Spring2.x的版本中采用Proxy模式,就是咱们在方案中实现一个虚拟的数据源,而且用它来封装数据源选择逻辑,这样就能够有效地将数据源选择逻辑从Client中分离出来。Client提供选择所需的上下文(由于这是Client所知道的),由虚拟的DataSource根据Client提供的上下文来实现数据源的选择。
具体的实现就是,虚拟的DataSource仅需继承AbstractRoutingDataSource实现determineCurrentLookupKey()在其中封装数据源的选择逻辑。sql
1、原理数据库
首先看下AbstractRoutingDataSource类结构,继承了AbstractDataSourcesession
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
既然是AbstractDataSource,固然就是javax.sql.DataSource的子类,因而咱们天然地回去看它的getConnection方法:oracle
public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); }
原来关键就在determineTargetDataSource()里:dom
/** * Retrieve the current target DataSource. Determines the * {@link #determineCurrentLookupKey() current lookup key}, performs * a lookup in the {@link #setTargetDataSources targetDataSources} map, * falls back to the specified * {@link #setDefaultTargetDataSource default target DataSource} if necessary. * @see #determineCurrentLookupKey() */ protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; }
这里用到了咱们须要进行实现的抽象方法determineCurrentLookupKey(),该方法返回须要使用的DataSource的key值,而后根据这个key从resolvedDataSources这个map里取出对应的DataSource,若是找不到,则用默认的resolvedDefaultDataSource。ide
public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); } if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } }
2、Spring配置多数据源的方式和具体使用过程ui
一、数据源的名称常量类this
public enum DynamicDataSourceGlobal { ORCL, ISC }
二、创建一个得到和设置上下文环境的类,主要负责改变上下文数据源的名称
public class DynamicDataSourceHolder { // 线程本地环境 private static final ThreadLocal<DynamicDataSourceGlobal> contextHolder = new ThreadLocal<DynamicDataSourceGlobal>(); // 设置数据源类型 public static void setDataSourceType(DynamicDataSourceGlobal dataSourceType) { Assert.notNull(dataSourceType, "DataSourceType cannot be null"); contextHolder.set(dataSourceType); } // 获取数据源类型 public static DynamicDataSourceGlobal getDataSourceType() { return (DynamicDataSourceGlobal) contextHolder.get(); } // 清除数据源类型 public static void clearDataSourceType() { contextHolder.remove(); }
三、创建动态数据源类,注意,这个类必须继承AbstractRoutingDataSource,且实现方法 determineCurrentLookupKey,该方法返回一个Object,通常是返回字符串
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDataSourceType(); } }
四、编写spring的配置文件配置多个数据源
<!-- 数据源相同的内容 -->
<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClass" value="oracle.jdbc.pool.OracleConnectionPoolDataSource" /> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl" /> <property name="user" value="isc_v10" /> <property name="password" value="isc" /> </bean> <!-- 数据源 --> <bean id="orclDataSource" parent="parentDataSource"> <property name="user" value="orcl" /> <property name="password" value="orcl" /> </bean> <!-- 数据源 --> <bean id="iscDataSource" parent="parentDataSource"> <property name="user" value="isc_v10" /> <property name="password" value="isc" /> </bean> <!-- 编写spring 配置文件的配置多数源映射关系 --> <bean id="dataSource" class="com.wy.config.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="ORCL" value-ref="orclDataSource"></entry> <entry key="ISC" value-ref="iscDataSource"></entry> </map> </property> <property name="defaultTargetDataSource" ref="orclDataSource"> </property> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>
五、使用
public void testSave() { // hibernate建立实体 DynamicDataSourceHolder.setDataSourceType(DynamicDataSourceGlobal.ORCL);// 设置为另外一个数据源 com.wy.domain.Test user = new com.wy.domain.Test(); user.setName("WY"); user.setAddress("BJ"); testDao.save(user);// 使用dao保存实体 DynamicDataSourceHolder.setDataSourceType(DynamicDataSourceGlobal.ISC);// 设置为另外一个数据源 testDao.save(user);// 使用dao保存实体到另外一个库中 }