之前参加了一个项目,此项目涉及到多个数据源的定位与切换。此项目使用Spring的AbstractRoutingDataSource抽象类来进行完成的,下面看一下源码:
package org.springframework.jdbc.datasource.lookup;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.util.Assert;
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;//多数据源目标key-value
private Object defaultTargetDataSource;//默认当前数据源
/**
* 宽容回退.
*在通过key来切换数据源如果没有找到,或者key本身就是null这个时候是否要使 用默认数据源。
* true:使用默认数据源;
* false:抛出异常Cannot determine target DataSource * for lookup key lookupKey
* 默认值:true ,表示使用宽容回退
*/
private boolean lenientFallback = true;
/**
* JNDI通过key来查找数据源。
* DataSourceLookup 包含一个方法:
* DataSource getDataSource(String dataSourceName) throws
* DataSourceLookupFailureException;
*/
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
/**
* 所有备选的数据库。
* key-value(简称-DataSource对象)
*/
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource; //默认的数据源
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
}
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
}
@Override
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<Object, Object> 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);
}
}
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
//通过数据源真实名称,查询DataSource对象
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
return determineTargetDataSource().unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
}
//通过简称查询DataSource对象
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()方法,返回要使用的数据源的key
protected abstract Object determineCurrentLookupKey();
}
类DynamicRoutingDataSource
继承抽象类
AbstractRoutingDataSource
package com.cpic.auap.core.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态路由DataSource,根据在ThreadLocal中设置的DataSourceType动态取得不同的数据源。
*
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.determineCurrentLookupKey();
}
}
/**
* 在ThreadLocal中保存当前线程需要使用的String。
*
* @see {@link DynamicRoutingDataSource}
*/
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataSource(String type) {
// 不使用mycat时这里切换数据源的地方进行屏蔽
contextHolder.set(type);
}
public static String determineCurrentLookupKey() {
String dbtype = (String)contextHolder.get();
return dbtype;
}
public static String getDataSource() {
String dbtype = (String)contextHolder.get();
if(dbtype == null ){//获取默认数据源
dbtype = getDefaultDataSource();
}
return dbtype;
}
public static String getDefaultDataSource() {
//获取默认数据源
String defaultDbtype = (String) SpringContextHolder.getBean("defaultDbtype");
defaultDbtype = defaultDbtype.trim();
SpringContextHolder.getBean("dbtypeMap");
return defaultDbtype;
}
public static void clearString() {
contextHolder.remove();
}
}
context-datasource.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<bean id="dataSourceB01"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="defaultAutoCommit" value="false" />
</bean>
<bean id="dataSourceB02"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="defaultAutoCommit" value="false" />
</bean>
<bean id="dataSourceB03"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="defaultAutoCommit" value="false" />
</bean>
<bean id="dataSourceC01"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="defaultAutoCommit" value="false" />
</bean>
<bean id="defaultDbtype" class="java.lang.String">
<constructor-arg><!-- 默认数据源的dbtype -->
<value>102</value>
</constructor-arg>
</bean>
<!-- 数据库分库编码映射Map -->
<bean id="dbtypeMap" class="java.util.HashMap">
<constructor-arg>
<map key-type="java.lang.String">
<!-- 业务数据库 -->
<entry key="101" value-ref="dataSourceB01" />
<entry key="102" value-ref="dataSourceB02" />
<entry key="103" value-ref="dataSourceB03" />
<entry key="104" value-ref="dataSourceB04" />
<!-- 配置数据库 -->
<entry key="201" value-ref="dataSourceC01" />
<!-- 备份数据库 -->
<entry key="301" value-ref="dataSourceD01" />
</map>
</constructor-arg>
</bean>
<bean id="dataSource" class="com.cpic.auap.core.datasource.DynamicRoutingDataSource">
<property name="targetDataSources" ref="dbtypeMap"/>
<!-- 默认数据源为配置库 -->
<property name="defaultTargetDataSource" ref="dataSourceB03"/>
</bean>
</beans>