美文网首页SpringJava核心技术程序员
Spring动态创建,加载,使用多数据源

Spring动态创建,加载,使用多数据源

作者: 安静点就睡吧 | 来源:发表于2016-11-29 18:18 被阅读2391次

问题

       最近公司项目中遇到了一个问题,那就是Spring整合MyBatis需要动态切换数据源来访问不同的数据库,因为是游戏的后台,而公司的数据库都部署在不同的机器上,所以需要配置不同的数据源来达到访问数据的目的。一般的思路就是使用Spring配置数据源,然后动态的切换达到访问不同的数据库。

解决方法

       自定义一个类继承自Spring的抽象类AbstractRoutingDataSource,然后实现其方法determineCurrentLookupKey()方法就可以了。上代码:
DynamicDataSource.java

package com.think.dao;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

/**
 * 动态切换数据库
 * Created by veion on 2016/10/23.
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
    private static DynamicDataSource instance;
    private static byte[] lock=new byte[0];
    private static Map<Object,Object> dataSourceMap=new HashMap<Object, Object>();

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        dataSourceMap.putAll(targetDataSources);
        //重要的方法,一定要加上不然会出现动态添加数据源的时候无法生效的情况
        super.afterPropertiesSet();
    }

    public Map<Object, Object> getDataSourceMap() {
        return dataSourceMap;
    }

    public static synchronized DynamicDataSource getInstance(){
        if(instance==null){
            synchronized (lock){
                if(instance==null){
                    instance=new DynamicDataSource();
                }
            }
        }
        return instance;
    }
    //必须实现其方法
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDBType();
    }
}

DataSourceContextHolder.java

package com.think.dao;

/**
 * 数据库切换类
 * Created by veion on 2016/10/23.
 */
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static synchronized void setDBType(String dbType){
        contextHolder.set(dbType);
    }

    public static String getDBType(){
        return contextHolder.get();
    }

    public static void clearDBType(){
        contextHolder.remove();
    }
}

通过上面的方法之后,我们就可以在自己的项目中不管是动态创建数据源还是静态配置数据源都没有问题了。上一段我的数据源配置。
spring-config-dao.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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 扫描注解Bean -->
    <context:component-scan base-package="com.think">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 开启AOP监听 只对当前配置文件有效 -->
    <aop:aspectj-autoproxy expose-proxy="true"/>

    <!-- 数据源 -->
    <bean id="thinkDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${think_public.url}"/>
        <property name="username" value="${think.username}"/>
        <property name="password" value="${think.password}"/>
        <property name="initialSize" value="${druid.initialSize}"/>
        <property name="minIdle" value="${druid.minIdle}"/>
        <property name="maxActive" value="${druid.maxActive}"/>
        <property name="maxWait" value="${druid.maxWait}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}"/>
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}"/>
        <property name="validationQuery" value="${druid.validationQuery}"/>
        <property name="testWhileIdle" value="${druid.testWhileIdle}"/>
        <property name="testOnBorrow" value="${druid.testOnBorrow}"/>
        <property name="testOnReturn" value="${druid.testOnReturn}"/>
        <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}"/>
        <property name="filters" value="${druid.filters}"/>
    </bean>

    <bean id="publicDataSource" parent="thinkDataSource">
        <property name="url" value="${think_publicdata.url}"/>
    </bean>

    <bean id="ironStatsDB" parent="thinkDataSource">
        <property name="url" value="${think_stats_url}"/>
    </bean>

    <!-- 动态数据源 -->
    <bean id="dataSource" class="com.think.dao.DynamicDataSource" factory-method="getInstance">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry value-ref="thinkDataSource" key="thinkDataSource"/>
                <entry value-ref="publicDataSource" key="publicDataSource"/>
                <entry value-ref="ironStatsDB" key="ironStatsDB"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="thinkDataSource"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="typeAliasesPackage" value="com.think.model"/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.think.**.dao"/>
        <property name="markerInterface" value="com.think.dao.MyMapper"/>
    </bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" scope="prototype">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <!--事务管理器配置-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="find*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <aop:config expose-proxy="true" proxy-target-class="true">
        <aop:advisor pointcut="execution(* com.think.service.*Service.insert*(..)) "
                     advice-ref="txAdvice"/>
        <aop:advisor pointcut="execution(* com.think.service.*Service.delete*(..)) "
                     advice-ref="txAdvice"/>
        <aop:advisor pointcut="execution(* com.think.service.*Service.update*(..)) "
                     advice-ref="txAdvice"/>
    </aop:config>
</beans>

动态创建数据源添加到动态数据源中去。

        Map<Object, Object> dataSourceMap = new HashMap<Object, Object>();
        Map<String, String> dbsMap = new HashMap<String, String>();
        for (int i = 0; i < serverConfigModelList.size(); i++) {
            ServerConfigModel model = serverConfigModelList.get(i);
            if (serverIdList.contains(model.getServer_id())) {
                continue;
            } else {
                serverIdList.add(model.getServer_id());
            }
            String localurl = "jdbc:mysql://" + model.getRemote_host() + ":"+ model.getData_port() + "/" +model.getDb_name()+ "?useUnicode=true&amp;characterEncoding=UTF-8";
            DynamicDataSourcePool dataSourcePool = new DynamicDataSourcePool(
                    model.getDataSourceName(), model.getDataSourcePassWord(),
                    localurl, "com.mysql.jdbc.Driver");

            if (dataSourcePool != null && dataSourcePool.getPool() != null) {
                /*
                 * dataSourcePool.getConnection(); factory.bind("" +
                 * model.getServer_id(), dataSourcePool);
                 */
                dataSourceMap.put(String.valueOf("game-" + model.getServer_id()),dataSourcePool.getPool());
                dbsMap.put(String.valueOf("game-" + model.getServer_id()),String.valueOf("game-" + model.getServer_id()));
            }
        }
        DBType.putDbMap(dbsMap);
        DynamicDataSource.getInstance().setTargetDataSources(dataSourceMap);

The end.

相关文章

网友评论

  • 爱吃土豆_bb08:源代码地址发一下好不 上面没看到呀
  • 7257e12a66cc:请问这个类:DynamicDataSourcePool 是哪来的啊?dataSourcePool.getPool(),获取的是什么?希望能回复一下,或者有懂的望普及一下,谢谢!
  • 382be2cf18f0:DynamicDataSourcePool 这个对象是怎么来的 有知道的吗
  • 卖蟑螂的小男孩:您好,AbstractRoutingDataSource 这个类里面 它的dataSource 不是当前spring 的dataSource吗? 我想保持 之前的数据源不动,再添加几个数据源,请问通过什么方法 才能获取到我配置的其他几个数据源呢?
  • 42bc074b4774:DBType.putDbMap(dbsMap);
    我想问一下这个DBType 是什么?在你的最后一段代码 没有声明 这个呀,最近我在搞 和你类似的功能,看了你的文章很受启发,但是 数据源 一致没有生效,super.afterPropertiesSet(); 这个的代码也加上了,也是没有作用,
    安静点就睡吧:@HelloWorld_af74 你在初始化的时候打个短点看看,以及在切换的时候短点跟踪看是不是覆盖了操作。
    42bc074b4774:@安静点就睡吧 现在加载完后再去切换数据源的时候查询到的resolvedDataSources 依然是 null 这个 我很不理解,难道 切换数据源的时候 重新初始化 了?
    安静点就睡吧:那个可以不用管它,你现在数据源已经动态添加进去没,然后打断点看看获取的数据源是哪一个,慢慢调试然后定位问题。
  • 2bc73569929a:有没有demo 可以参考一下的?大佬
    安静点就睡吧: @2bc73569929a 文章上面有的哦
  • c444d6ffe5f3:您好,我想请问下为什么没有动态切换呢
    c444d6ffe5f3:加了这段代码也没用,都没有调用determineCurrentLookupKey这个方法
    安静点就睡吧: @c444d6ffe5f3 super.afterPropertiesSet();需要确保动态添加数据源之后加了这一段代码。
  • 树根啦:之前做项目也用到了。不过好像数据源是从数据库中读取,然后进行切换的。
    安静点就睡吧:@树根啦 是的,我们的项目游戏服也是存储到数据库里面然后动态加载的。然后根据玩家的所在服来动态切换到不同的数据库。

本文标题:Spring动态创建,加载,使用多数据源

本文链接:https://www.haomeiwen.com/subject/muoapttx.html