博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA中使用代码创建多数据源,并实现动态切换(一)
阅读量:6833 次
发布时间:2019-06-26

本文共 11712 字,大约阅读时间需要 39 分钟。

hot3.png

    2017-06-06 11:31:57补充:近日,在本文的基础之上,扩展了下,使用atomikos来管理事务,保证多数据源操作时,事务一致性。()

    另外,感谢朋友对本文的关注和对博主的支持,最近有很多朋友联系我希望深入探讨下本文涉及内容,不过由于近日太忙,没有及时回复大家,请见谅。

    近日,博主有个业务需求,就是根据数据库存储的不同数据源信息,动态创建数据源并实现业务不同而转到不同的数据源上处理。

    数据库存储起来的数据源信息是不确定的,可以删除和添加,这些是业务前提。

    在网上找了下相关资料,对于使用Spring配置,直接配置多个数据源,使用AOP动态切换数据源的方式居多,这种方式博主以前也使用过,很强大。不过有个前提就是多个数据源的信息是预先就确定的。那么对于不确定数据源信息的业务需求,就只有使用代码动态实现数据源初始化、选择和销毁操作了。

    好了,有了这些思路,可以开始准备写代码了。

1、创建一个线程上下文对象(使用ThreadLocal,保证线程安全)。上下文对象中主要维护了数据源的KEY和数据源的地址等信息,当KEY对应的数据源找不到时,根据数据源地址、驱动和用户名等创建 一个数据源,这里也是业务中需要解决的一个核心问题(JAVA动态创建数据源)。

/** * Copyright (c) 2015 - 2016 eay Inc. * All rights reserved. */package com.eya.pubservice.datasource;import java.util.HashMap;import java.util.Map;/** * 当前正在使用的数据源信息的线程上线文 * @create ll * @createDate 2017年3月27日 下午2:37:07 * @update  * @updateDate  */public class DBContextHolder {    /** 数据源的KEY */    public static final String DATASOURCE_KEY = "DATASOURCE_KEY";    /** 数据源的URL */    public static final String DATASOURCE_URL = "DATASOURCE_URL";    /** 数据源的驱动 */    public static final String DATASOURCE_DRIVER = "DATASOURCE_DRIVER";    /** 数据源的用户名 */    public static final String DATASOURCE_USERNAME = "DATASOURCE_USERNAME";    /** 数据源的密码 */    public static final String DATASOURCE_PASSWORD = "DATASOURCE_PASSWORD";    private static final ThreadLocal
> contextHolder = new ThreadLocal
>(); public static void setDBType(Map
dataSourceConfigMap) { contextHolder.set(dataSourceConfigMap); } public static Map
getDBType() { Map
dataSourceConfigMap = contextHolder.get(); if (dataSourceConfigMap == null) { dataSourceConfigMap = new HashMap
(); } return dataSourceConfigMap; } public static void clearDBType() { contextHolder.remove(); }}

2、创建一个AbstractRoutingDataSource的子类,实现其determineCurrentLookupKey方法,用于决定使用哪一个数据源。说明一下,这里实现了ApplicationContextAware接口,用于在Spring加载完成后,注入Spring上下文对象,用于获取Bean。

/** * Copyright (c) 2015 - 2016 eya Inc. * All rights reserved. */package com.eya.pubservice.datasource;import java.util.Map;import javax.sql.DataSource;import org.apache.commons.collections.MapUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/** * 动态数据源父类 * @create ll * @createDate 2017年3月27日 下午2:38:05 * @update  * @updateDate  */public abstract class AbstractDynamicDataSource
extends AbstractRoutingDataSource implements ApplicationContextAware { /** 日志 */ protected Logger logger = LoggerFactory.getLogger(getClass()); /** 默认的数据源KEY,和spring配置文件中的id=druidDynamicDataSource的bean中配置的默认数据源key保持一致 */ protected static final String DEFAULT_DATASOURCE_KEY = "defaultDataSource"; /** 数据源KEY-VALUE键值对 */ public Map
targetDataSources; /** spring容器上下文 */ private static ApplicationContext ctx; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctx = applicationContext; } public static ApplicationContext getApplicationContext() { return ctx; } public static Object getBean(String name) { return ctx.getBean(name); } /** * @param targetDataSources the targetDataSources to set */ public void setTargetDataSources(Map
targetDataSources) { this.targetDataSources = targetDataSources; super.setTargetDataSources(targetDataSources); // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的 super.afterPropertiesSet(); } /** * 创建数据源 * @param driverClassName 数据库驱动名称 * @param url 连接地址 * @param username 用户名 * @param password 密码 * @return 数据源{@link T} * @Author : ll. create at 2017年3月27日 下午2:44:34 */ public abstract T createDataSource(String driverClassName, String url, String username, String password); /** * 设置系统当前使用的数据源 *

数据源为空或者为0时,自动切换至默认数据源,即在配置文件中定义的默认数据源 * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() */ @Override protected Object determineCurrentLookupKey() { logger.info("【设置系统当前使用的数据源】"); Map

configMap = DBContextHolder.getDBType(); logger.info("【当前数据源配置为:{}】", configMap); if (MapUtils.isEmpty(configMap)) { // 使用默认数据源 return DEFAULT_DATASOURCE_KEY; } // 判断数据源是否需要初始化 this.verifyAndInitDataSource(); logger.info("【切换至数据源:{}】", configMap); return configMap.get(DBContextHolder.DATASOURCE_KEY); } /** * 判断数据源是否需要初始化 * @Author : ll. create at 2017年3月27日 下午3:57:43 */ private void verifyAndInitDataSource() { Map
configMap = DBContextHolder.getDBType(); Object obj = this.targetDataSources.get(configMap.get(DBContextHolder.DATASOURCE_KEY)); if (obj != null) { return; } logger.info("【初始化数据源】"); T datasource = this.createDataSource(configMap.get(DBContextHolder.DATASOURCE_DRIVER) .toString(), configMap.get(DBContextHolder.DATASOURCE_URL).toString(), configMap.get(DBContextHolder.DATASOURCE_USERNAME).toString(), configMap.get(DBContextHolder.DATASOURCE_PASSWORD).toString()); this.addTargetDataSource(configMap.get(DBContextHolder.DATASOURCE_KEY).toString(), datasource); } /** * 往数据源key-value键值对集合添加新的数据源 * @param key 新的数据源键 * @param dataSource 新的数据源 * @Author : ll. create at 2017年3月27日 下午2:56:49 */ private void addTargetDataSource(String key, T dataSource) { this.targetDataSources.put(key, dataSource); super.setTargetDataSources(this.targetDataSources); // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的 super.afterPropertiesSet(); }}

3、编写AbstractDynamicDataSource的实现类,使用com.alibaba.druid.pool.DruidDataSource数据源。主要实现创建数据源的方法(createDataSource)

/** * Copyright (c) 2015 - 2016 eya Inc. * All rights reserved. */package com.eya.pubservice.datasource;import java.sql.SQLException;import java.util.List;import org.apache.commons.lang3.StringUtils;import com.alibaba.druid.filter.Filter;import com.alibaba.druid.pool.DruidDataSource;/** * Druid数据源 * 

摘抄自http://www.68idc.cn/help/buildlang/java/20160606618505.html * @create ll * @createDate 2017年3月27日 下午2:40:17 * @update * @updateDate */public class DruidDynamicDataSource extends AbstractDynamicDataSource

{ private boolean testWhileIdle = true; private boolean testOnBorrow = false; private boolean testOnReturn = false; // 是否打开连接泄露自动检测 private boolean removeAbandoned = false; // 连接长时间没有使用,被认为发生泄露时长 private long removeAbandonedTimeoutMillis = 300 * 1000; // 发生泄露时是否需要输出 log,建议在开启连接泄露检测时开启,方便排错 private boolean logAbandoned = false; // 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就会被自动设定为true,使用oracle时可以设定此值。 // private int maxPoolPreparedStatementPerConnectionSize = -1; // 配置监控统计拦截的filters private String filters; // 监控统计:"stat" 防SQL注入:"wall" 组合使用: "stat,wall" private List
filterList; /* * 创建数据源,这里创建的数据源是带有连接池属性的 * @see com.cdelabcare.pubservice.datasource.IDynamicDataSource#createDataSource(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ @Override public DruidDataSource createDataSource(String driverClassName, String url, String username, String password) { DruidDataSource parent = (DruidDataSource) super.getApplicationContext().getBean( DEFAULT_DATASOURCE_KEY); DruidDataSource ds = new DruidDataSource(); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); ds.setDriverClassName(driverClassName); ds.setInitialSize(parent.getInitialSize()); ds.setMinIdle(parent.getMinIdle()); ds.setMaxActive(parent.getMaxActive()); ds.setMaxWait(parent.getMaxWait()); ds.setTimeBetweenConnectErrorMillis(parent.getTimeBetweenConnectErrorMillis()); ds.setTimeBetweenEvictionRunsMillis(parent.getTimeBetweenEvictionRunsMillis()); ds.setMinEvictableIdleTimeMillis(parent.getMinEvictableIdleTimeMillis()); ds.setValidationQuery(parent.getValidationQuery()); ds.setTestWhileIdle(testWhileIdle); ds.setTestOnBorrow(testOnBorrow); ds.setTestOnReturn(testOnReturn); ds.setRemoveAbandoned(removeAbandoned); ds.setRemoveAbandonedTimeoutMillis(removeAbandonedTimeoutMillis); ds.setLogAbandoned(logAbandoned); // 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就会被自动设定为true,参照druid的源码 ds.setMaxPoolPreparedStatementPerConnectionSize(parent .getMaxPoolPreparedStatementPerConnectionSize()); if (StringUtils.isNotBlank(filters)) try { ds.setFilters(filters); } catch (SQLException e) { throw new RuntimeException(e); } addFilterList(ds); return ds; } private void addFilterList(DruidDataSource ds) { if (filterList != null) { List
targetList = ds.getProxyFilters(); for (Filter add : filterList) { boolean found = false; for (Filter target : targetList) { if (add.getClass().equals(target.getClass())) { found = true; break; } } if (!found) targetList.add(add); } } }}

4、使用Spring配置默认数据源。系统运行肯定有一套默认的数据源(否则动态创建的数据源信息从哪里来呢?上面提到的,动态创建的数据源信息是存放在数据库中的)。这里我贴出完整的Spring配置。

classpath:config/sqlMapConfig.xml

5、编写测试类。实际业务中应该使用AOP实现数据源的切换,这里只写了一个测试,AOP相关很简单,就不在这里单独写了。当调用该方法时,可以从日志信息中看到,首先初始化了datasource-2,并且切换到了datasource-2。图片效果不行,勉强看看

/** * 分页查询 * @return {@link Pagination} * @Author : ll. create at 2016年04月05日 下午01:43:19 */@RequestMapping(value = "/page.do", method = RequestMethod.POST)public Pagination
page(HttpServletRequest request) { logger.info("【分页查询】"); Map
map = new HashMap
(); map.put(DBContextHolder.DATASOURCE_KEY, "localhost"); map.put(DBContextHolder.DATASOURCE_DRIVER, "com.mysql.jdbc.Driver"); map.put(DBContextHolder.DATASOURCE_URL, "jdbc:mysql://127.0.0.1:3306/test_20170217?useUnicode=true&characterEncoding=UTF-8"); map.put(DBContextHolder.DATASOURCE_USERNAME, "root"); map.put(DBContextHolder.DATASOURCE_PASSWORD, ""); DBContextHolder.setDBType(map); return super.page(request, false);}

转载于:https://my.oschina.net/simpleton/blog/868608

你可能感兴趣的文章
Zabbix 4.0,监控Tomcat(4)
查看>>
javascript console使用说明
查看>>
jQuery1.9+版本的.on使用方法笔记
查看>>
我的友情链接
查看>>
1.MyBaits 3.2 简介
查看>>
hibernate缓存
查看>>
防火墙侦测一下地址是否有经过
查看>>
Jmeter录制app脚本
查看>>
解决java.lang.RuntimeException: mapped-name is required for xxx.xxx.xxx/xx of deployment xx.war
查看>>
impdp导入数据报错"ORA-39029"处理一例
查看>>
shell脚本--语法篇
查看>>
多态实现原理
查看>>
弱链接和链接期错误
查看>>
CentOS6.3上SSH远程登录实现无密码认证
查看>>
java起源和基本数据类型
查看>>
向Flash传值的两种方式
查看>>
CCNP学习笔记之EIGRP 上
查看>>
九、搭建织梦cms网站
查看>>
我的友情链接
查看>>
linux大于2T的磁盘使用GPT分区方式
查看>>