Spring项目配置多数据源

Spring是支持多数据源管理的,称为数据源路由(RoutingDataSource),原理大概如下图所示

图片.png


具体实现如下

DynamicDataSource:多数据源的工具类

determineCurrentLookupKey:返回当前需要切换的数据源的key

addDataSource:创建数据源,并存入数据源路由中

updateDataSource:更新数据源

removeDataSource:从路由中删除数据源

package com.jeedev.jfast.datasource;
import java.lang.reflect.Field;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.LockedAccountException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import sun.org.mozilla.javascript.internal.Undefined;
import com.alibaba.druid.pool.DruidDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.initialSize}")
    private Integer initialSize;
    @Value("${jdbc.maxActive}")
    private Integer maxActive;
    @Value("${jdbc.minIdle}")
    private Integer minIdle;
    @Value("${jdbc.maxWait}")
    private Integer maxWait;
    @Value("${jdbc.timeBetweenEvictionRunsMillis}")
    private Integer timeBetweenEvictionRunsMillis;
    @Value("${jdbc.minEvictableIdleTimeMillis}")
    private Integer minEvictableIdleTimeMillis;
    @Value("${jdbc.validationQuery}")
    private String validationQuery;
    @Value("${jdbc.testWhileIdle}")
    private boolean testWhileIdle;
    @Value("${jdbc.testOnBorrow}")
    private boolean testOnBorrow;
    @Value("${jdbc.testOnReturn}")
    private boolean testOnReturn;
    @Value("${jdbc.poolPreparedStatements}")
    private boolean poolPreparedStatements;
    @Value("${jdbc.maxPoolPreparedStatementPerConnectionSize}")
    private Integer maxPoolPreparedStatementPerConnectionSize;
    @Value("${jdbc.filters}")
    private String filters;
    @Value("${jdbc.url.type}")
    private String urlType;
    
    public static boolean useSession = false;
    
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getRouteKey();
}
/**
     * 根据用户创建数据源
     */
    public void addDataSource(String dataSourceId,String dbUrl,String dbUser,String dbPwd) {
    if(dataSourceId == null || "".equals(dataSourceId)){
    return;
    }
    if(dbUrl == null || "".equals(dbUrl)){
    return;
    }
    if(dbUser == null || "".equals(dbUser)){
    return;
    }
    if(dbPwd == null || "".equals(dbPwd)){
    return;
    }
    try {
            Field targetDataSources = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
            targetDataSources.setAccessible(true);
            Map<Object, Object> dataSources = (Map<Object, Object>) targetDataSources.get(this);
            if (dataSources.get(dataSourceId) != null)
                return;
            DruidDataSource dds = dataSource(dbUrl, dbUser, dbPwd);
            dataSources.put(dataSourceId, dds);
            setTargetDataSources(dataSources);
            super.afterPropertiesSet();
        } 
    catch (Exception e) {
        e.printStackTrace();
        throw new LockedAccountException(); //账号无效
}
    }
    
    /**
     * 根据用户更新覆盖数据源
     */
    public void updateDataSource(String dataSourceId,String dbUrl,String dbUser,String dbPwd) {
    if(dataSourceId == null || "".equals(dataSourceId)){
    return;
    }
    if(dbUrl == null || "".equals(dbUrl)){
    return;
    }
    if(dbUser == null || "".equals(dbUser)){
    return;
    }
    if(dbPwd == null || "".equals(dbPwd)){
    return;
    }
    try {
            Field targetDataSources = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
            targetDataSources.setAccessible(true);
            Map<Object, Object> dataSources = (Map<Object, Object>) targetDataSources.get(this);
            if (dataSources.get(dataSourceId) != null) {
            removeDataSource(dataSourceId);
            addDataSource(dataSourceId, dbUrl, dbUser, dbPwd);
            }else{
            addDataSource(dataSourceId, dbUrl, dbUser, dbPwd);
            }
        } 
    catch (Exception e) {
        e.printStackTrace();
        throw new LockedAccountException(); //账号无效
}
    }
    
    /**
     * 删除数据源
     */
    public void removeDataSource(String dataSourceId) {
    if(dataSourceId == null || "".equals(dataSourceId)){
    return;
    }
    try {
    Field targetDataSources = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
    targetDataSources.setAccessible(true);
    Map<Object, Object> dataSources = (Map<Object, Object>) targetDataSources.get(this);
    if (dataSources.get(dataSourceId) == null)
    return;
    dataSources.remove(dataSourceId);
    setTargetDataSources(dataSources);
    super.afterPropertiesSet();
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    
    public String getDataSourceLink(String ip, String port, String exmpNm){
    if(port != null && !"".equals(port)&& !"undefined".equals(port)){
    port = ":" + port;
    }else port = "";
if("service_name".equals(urlType)){
    return "jdbc:oracle:thin:@//" + ip + port + "/" + exmpNm;
    }else {
    return "jdbc:oracle:thin:@" + ip + port + ":" + exmpNm;
    }
    
    }
    
    public DruidDataSource dataSource(String dbUrl,String dbUser,String dbPwd) throws Exception {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(dbUrl);
        druidDataSource.setUsername(dbUser);
        druidDataSource.setPassword(dbPwd);
        druidDataSource.setDriverClassName(driver);//oracle.jdbc.driver.OracleDriver
        druidDataSource.setInitialSize(initialSize);//1
        druidDataSource.setMaxActive(maxActive);//30
        druidDataSource.setMinIdle(minIdle);//0
        druidDataSource.setMaxWait(maxWait);//60000
        druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);//60000
        druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);//300000
        druidDataSource.setValidationQuery(validationQuery);//SELECT 1 FROM DUAL
        druidDataSource.setTestWhileIdle(testWhileIdle);//true
        druidDataSource.setTestOnBorrow(testOnBorrow);//false
        druidDataSource.setTestOnReturn(testOnReturn);//false
        druidDataSource.setPoolPreparedStatements(poolPreparedStatements);//true
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);//20
        druidDataSource.setFilters(filters);//stat,wall
        try {
            if (null != druidDataSource) {
                druidDataSource.setFilters("wall,stat");
                druidDataSource.setUseGlobalDataSourceStat(true);
                druidDataSource.setDbType("oracle");
                druidDataSource.init();
            }
        } catch (Exception e) {
            throw new RuntimeException(
                    "load datasource error, dbProperties is :", e);
        }
        return druidDataSource;
    }
}



DataSourceContextHolder:设置数据源得key

package com.jeedev.jfast.datasource;
public class DataSourceContextHolder {
private static ThreadLocal<String> routeKey = new ThreadLocal<String>();    
    /**
     * 获取当前线程的数据源路由的key
     */
    public static String getRouteKey()    {
        String key = routeKey.get();        
        return key;
    }    
    /**
     * 绑定当前线程数据源路由的key
     * 使用完成后必须调用removeRouteKey()方法删除
     */
    public static void  setRouteKey(String key)    {
        routeKey.set(key);
    }    
    /**
     * 删除与当前线程绑定的数据源路由的key
     */
    public static void removeRouteKey()    {
        routeKey.remove();
    }
    
    public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable(){  
            public void run(){
            DataSourceContextHolder.setRouteKey("1");
            System.out.println("t1=" + DataSourceContextHolder.getRouteKey());
            }}); 
    Thread t2 = new Thread(new Runnable(){  
            public void run(){
            System.out.println("t2=" + DataSourceContextHolder.getRouteKey());
            }}); 
        t1.start();
        t2.start();
    
}
}

用的是ThreadLocal,每个线程都会去切换对应的数据源,在登录请求或filter中,设置当前请求的路由

DataSourceContextHolder.setRouteKey(session.getOrgLglpsnId());


配置文件的修改

jdbc数据源配置

<!-- 配置数据源 -->
<bean name="defaultDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
    <!-- 基本属性 url、user、password -->
    <property name="driverClassName" value="${jdbc.driver}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${jdbc.initialSize}" />
    <property name="minIdle" value="${jdbc.minIdle}" />
    <property name="maxActive" value="${jdbc.maxActive}" />
    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="${jdbc.maxWait}" />
    <!-- 超过时间限制是否回收 -->
    <property name="removeAbandoned" value="${jdbc.removeAbandoned}" />
    <!-- 超过时间限制多长; -->
    <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}" />
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
    <property name="validationQuery" value="${jdbc.validationQuery}" />
    <property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
    <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
    <property name="testOnReturn" value="${jdbc.testOnReturn}" />
    <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
    <property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}" />
    <property name="maxPoolPreparedStatementPerConnectionSize"
    value="${jdbc.maxPoolPreparedStatementPerConnectionSize}" />
    <!-- 配置监控统计拦截的filters -->
    <property name="filters" value="${jdbc.filters}" />
    <property name="timeBetweenLogStatsMillis" value="${jdbc.timeBetweenLogStatsMillis}" />
    </bean>
    <!-- 输出可执行的SQL -->
    <bean id="log-filter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter">
    <property name="statementExecutableSqlLogEnable" value="true" />
</bean>
<!-- 多法人多数据源配置 -->
<bean id="dynamicdatasource" class="com.jeedev.jfast.datasource.DynamicDataSource">  
        <property name="targetDataSources">  
            <map key-type="java.lang.String">  
                 <entry key="defaultDataSource" value-ref="defaultDataSource" />   
            </map>  
        </property>  
        <property name="defaultTargetDataSource" ref="defaultDataSource" />  
    </bean>

主要是数据路由(dynamicdatasource),然后配置一个默认数据源

mybatis的sqlSessionFactory引用dynamicdatasource,其他不变

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dynamicdatasource" />
。。。

jpa也是,entityManagerFactory引用dynamicdatasource,其他不变

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!-- 指定数据源 -->
        <property name="dataSource" ref="dynamicdatasource"/>
。。。


很早以前使用springMVC做得
项目会配置一个初始化的数据源,即为默认法人。 当用户登录后,会去默认数据库查询用户法人信息,根据法人信息查询法人对应数据库信息,然后创建数据源或get数据源,然后切换数据源,进入首页加载数据

单数据源多法人架构

{context}