zoukankan      html  css  js  c++  java
  • spring动态创建数据源

    在最近的项目业务中,需要在程序的运行过程中,添加新的数据库添链接进来,然后从新数据库链接中读取数据。

    网上查阅了资料,发现spring为多数据源提供了一个抽象类AbstractRoutingDataSource,该类中只有一个抽象方法determineCurrentLookupKey()需要由我们实现。

    以下是使用方法

    假设我们创建一个类DynimaticDataSource,继承AbstractRoutingDataSource,并重写determineCurrentLookupKey()方法。

    spring启动初始化DynimaticDataSource:

    1、可以通过spring的自动注入,对AbstractRoutingDataSource类注入相应的属性,注入属性不是必须的,可以通过继承的子类重写这些方法来重新设置对这些属性的调用。

    2、注入后spring会执行protected方法afterPropertiesSet(),该方法先判断属性targetDataSources不能为null,即必须初始化时注入至少一个数据源,否则抛出异常"Property 'targetDataSources' is required"。然后将该属性(类型为map<object,object>)的值全部转换到属性resolvedDataSources(类型为map<Object, DataSource>)中去。如果属性defaultTargetDataSource不为null,即已经设置默认数据源,将其转换为DataSource类型并赋值给属性defaultTargetDataSource。

    经过以上处理后,属性resolvedDataSources中会被存放我们添加的数据源,该属性是一个map集合,key为Object类型,value为数据源。同时可能会有一个默认数据源(可以注入也可以不注入)。

    使用DynimaticDataSource类获取连接:

    1、调用该类的public方法getConnection()来获取连接。

    2、getConnection在抽象类中被重写,会先调用protected方法determineTargetDataSource()。该方法先判断属性resolvedDataSources不为null,即初始化时候注入了至少一个数据源,否则抛出异常"DataSource router not initialized"。然后调用由子类重写的抽象方法determineCurrentLookupKey()获取dataSource在resolvedDataSources中对应的key,判断使用哪个数据源。

    3、根据key从resolvedDataSources中获取数据源,如果resolvedDataSources中不存在,再判断lenientFallback为true(默认为true,可以设置)或key为null,返回默认数据源resolvedDefaultDataSource。否则抛出异常"Cannot determine target DataSource for lookup key [" + key+ "]"。

    4、调用获取数据源的getConnection()方法获取连接。

    在初始化时指定多数据源案例代码:

    1、创建一个类DynimaticDataSource,继承AbstractRoutingDataSource,并重写determineCurrentLookupKey()方法。该方法负责判断当前线程使用哪一种数据源。这是最简单的一种实现方法,不重写任何非抽象方法,但必须在初始化时配置至少一个的数据源。

    public class DynamicDatasource extends AbstractRoutingDataSource{
    
        private static Map<Object, Object> targetDataSources;
        
        protected DruidDataSource dataSource;
        
        @Override
        protected String determineCurrentLookupKey() {
          //指定使用哪个数据源
    return DataSourceUtils.getDbtype(); } }

    2、srping配置文件(部分)

        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
            <property name="url" value="${jdbc.url}" />
            <property name="username" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
            <property name="driverClassName" value="${jdbc.driver}" />
            <property name="maxActive" value="50" /><!-- 最大连接池数 -->
            <property name="minIdle" value="5" /><!-- 最小连接池数 -->
        </bean>
        
        <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
            <property name="url" value="${jdbc.url2}" />
            <property name="username" value="${jdbc.username2}" />
            <property name="password" value="${jdbc.password2}" />
            <property name="driverClassName" value="${jdbc.driver}" />
            <property name="maxActive" value="50" /><!-- 最大连接池数 -->
            <property name="minIdle" value="5" /><!-- 最小连接池数 -->
        </bean>
        
        <bean id="multDataSource" class="com.ftpSystem.dao.DynamicDatasource">
            <property name="targetDataSources">
                <map >
                    <entry value-ref="dataSource" key="masterDataSource"></entry>
                    <entry value-ref="dataSource2" key="yangDataSource"></entry>
                </map>
            </property>
        <property name="defaultTargetDataSource" ref="dataSource"></property>
    </bean>

    3、使用数据源,我这里使用jdbcTemple,可以使用其他持久层框架,方式同单数据源配置一致。

        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="multDataSource"></property>
        </bean>

    4、切换数据源类

    public class DataSourceUtils {
    
        private static final ThreadLocal<String> local = new ThreadLocal<String>();
        
        public static String getDbtype() {
            return local.get();
        }
        
        public static void setDbtype(String dbtype) {
            local.set(dbtype);
        }
        
        public static void clear() {
            local.remove();
        }
    }

    5、客户端代码

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext-dao.xml")
    public class TestDao {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        @Test
        public void Test(){
            System.out.println("ok");
            int i = (Integer) jdbcTemplate.queryForObject("select count(*) from t_gg_zsdw", Integer.class);
            System.out.println("i="+i);
            DataSourceUtils.setDbtype("dataSource2");//切换数据源至dataSource2
            i = (Integer) jdbcTemplate.queryForObject("select count(*) from t_gg_zsdw", Integer.class);
            System.out.println("i="+i);
        }
    }

    由于我们已经在spring的配置文件中指定了属性defaultTargetDataSource,因此程序会默认使用该数据源。然后我们执行一次后切换为dataSource2,之后的执行会改为使用dataSource2的数据源。

    以上方式可以对程序配置多数据源,但是缺点在程序初始化之时就指定所有的数据源,无法在运行时动态添加。

    程序动态配置多数据源:

    通过最上面我们对获取DynimaticDataSource对象的数据源连接的过程分析可知,AbstractRoutingDataSource类是通过determineTargetDataSource()方法来获取数据源。在该方法里面,又使用了我们重写的抽象方法determineCurrentLookupKey()来判断使用哪一个数据源的key,通过这个key,在数据源初始化后存放的集合resolvedDataSources中获取想要的数据源。

    因此,如果我们想要动态的添加数据源进去,有两种思路可以考虑,一是重写determineTargetDataSource()方法,并且我们自己配置一个数据源集合,通过该方法,调用我们自己的数据源集合中对应的数据源。二是在resolvedDataSources属性中动态添加key-value进去,可以在determineTargetDataSource()方法中获取该数据源即可。

    通过对源码查看可知,第二种思路所需的属性resolvedDataSources是私有的,且类中没有提供相应的get方法获取。因此只能采用第一种方式。

    样例如下:

    先是继承AbstractRoutingDataSource的实体类

    /**
     * 数据源集合管理类
     * 使用数据源之前必须先添加,然后指定使用哪个数据源
     * @author yangxiangfeng
     *
     */
    public class DynamicDatasource extends AbstractRoutingDataSource {
    
        private static Map<String, DataSource> dataSourceMap = new HashMap<String, DataSource>();
        
        private final static Map<DataSourceEntity, String> dseMap = new HashMap<>();
        
      //spring注入需要set方法,不是必要的,可以注入也可以不注入
    public static void setDataSourceMap(Map<String, DataSource> dataSourceMap) { DynamicDatasource.dataSourceMap = dataSourceMap; }
      //获取数据源信息集合
    public static Map<DataSourceEntity, String> getDsemap() { return dseMap; }

      //检查是否包含指定id的数据源
    public static boolean checkDbKey(String dbKey){ if(dataSourceMap.get(dbKey) != null)return true; return false; } //抽象方法,必须重写,用来判断使用哪个数据源 @Override protected String determineCurrentLookupKey() { return DataSourceUtils.getDbKey(); } /** * 对数据源的初始化方法,由于这里已经将数据源集合放在本类中,如果不重写将会由于父类参数为null而抛出异常。 */ @Override public void afterPropertiesSet() {} /** * 确定使用哪一个数据源 * 这里不做null判断,因为是经过null判断后再进入的。 */ @Override protected DataSource determineTargetDataSource() { System.out.println("tttt"); String dsKey = determineCurrentLookupKey(); DataSource dds = dataSourceMap.get(dsKey); return dds; } /** * 添加数据源 * 为了防止多线程添加同一个数据源,这里采用同步,同时会判断是否已存在 * @param dbkey * @param ip * @param port * @param service 实例名 * @param username * @param password * @return String 新建数据源对应的key,如果已经存在,则返回之前的key。 */ public synchronized String addDataSource(String dbkey, String ip, int port, String service, String username, String password){ DataSourceEntity d1 = new DataSourceEntity(ip, port, service, username); String value = dseMap.get(d1); if(dseMap.get(d1) != null){ return value;//已存在则返回该数据源的id } DataSource ds = createDataSource(ip, port, service, username, password); dataSourceMap.put(dbkey, ds);//存储数据源集合 dseMap.put(d1, dbkey);//保存已经存储了哪些数据源 return dbkey; } /** * 创建一个数据源 * @param ip * @param port * @param service * @param username * @param password * @return */ private DataSource createDataSource(String ip, int port, String service, String username, String password){ DruidDataSource dds = new DruidDataSource(); dds.setDriverClassName("oracle.jdbc.driver.OracleDriver"); dds.setUrl("jdbc:oracle:thin:@"+ip+":"+port+":"+service); dds.setUsername(username); dds.setPassword(password); return dds; }

    2、切换数据源的工具类

    public class DataSourceUtils {
    
        private static final ThreadLocal<String> local = new ThreadLocal<String>();
        
        public static String getDbKey() {
            return local.get();
        }
        
        public static void setDbKey(String dbKey) {
            if(DynamicDatasource.checkDbKey(dbKey)){
                local.set(dbKey);
            } else {
                throw new NullPointerException("不存在id为""+dbKey+""的数据源!");
         } }
    public static void clear() { local.remove(); } }

    客户端测试类

        @Test
        public void Test(){
            System.out.println("ok");
            //增加数据源
            dynamicDatasource.addDataSource("dataSource", "***.***.**.***", ****, "***", "***", "*****");
            //指定使用的数据源
            DataSourceUtils.setDbKey("dataSource");
            //执行sql
            int i = (Integer) jdbcTemplate.queryForObject("select count(*) from tablename", Integer.class);
            System.out.println("i="+i);
        }    

    以上代码是经过测试后可以直接运行的,由于本人能力有限,综合考虑后对spring中动态创建数据源的实现如上。欢迎各位指点可以优化改进的地方。

  • 相关阅读:
    reason: ‘Could not instantiate class named NSLayoutConstraint’
    wzplayer 成功支持IOS
    【搜索】P1219 八皇后
    原生JS的移入溢出控制div的显示与隐藏
    es3设置属性不能修改
    插槽在父组件和子组件间的使用(vue3.0推荐)
    es5设置属性不能修改
    jquery操作css样式的方法
    基于jQuery的AJAX和JSON的实例 模版
    C#实现根据IP 查找真实地址
  • 原文地址:https://www.cnblogs.com/yxth/p/8342787.html
Copyright © 2011-2022 走看看