多数据源的应用场景:主要是数据库拆分后,怎样让多个数据库结合起来来达到业务需求。
SSM框架(Spring+SpringMVC+MyBatis(MyBatis-Plus))是目前最常用的,此次仍然是maven工程。
关于这个多数据源例子,我已经上传到我的github上,地址为:https://github.com/youcong1996/study_simple_demo.git
不过需要注意的是,分支为demo1,不是主分支,如图所示:
如果下面的示例,你们看不懂或者不能理解,可以git clone我的地址
在编程的世界里,简洁即完美。
如何实现多数据源?
一句话,三个类加xml配置即可达到这个目的。
一、编写三个类
AbstractDynamicDataSource.java
packagecom.blog.datasource;importjava.util.Map;importjavax.sql.DataSource;importorg.apache.commons.collections.MapUtils;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.BeansException;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** 动态数据源父类
* @create ll
* @update
* @updateDate*/
public abstract class AbstractDynamicDataSource extendsAbstractRoutingDataSourceimplementsApplicationContextAware {/**日志*/
protected Logger logger =LoggerFactory.getLogger(getClass());/**默认的数据源KEY*/
protected static final String DEFAULT_DATASOURCE_KEY = "defaultDataSource";/**数据源KEY-VALUE键值对*/
public MaptargetDataSources;/**spring容器上下文*/
private staticApplicationContext ctx;public void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {
ctx=applicationContext;
}public staticApplicationContext getApplicationContext() {returnctx;
}public staticObject getBean(String name) {returnctx.getBean(name);
}/***@paramtargetDataSources the targetDataSources to set*/
public void setTargetDataSources(MaptargetDataSources) {this.targetDataSources =targetDataSources;super.setTargetDataSources(targetDataSources);//afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
super.afterPropertiesSet();
}/*** 创建数据源
*@paramdriverClassName 数据库驱动名称
*@paramurl 连接地址
*@paramusername 用户名
*@parampassword 密码
*@return数据源{@linkT}
* @Author : ll. create at 2017年3月27日 下午2:44:34*/
public abstractT createDataSource(String driverClassName, String url, String username,
String password);/*** 设置系统当前使用的数据源
*
数据源为空或者为0时,自动切换至默认数据源,即在配置文件中定义的默认数据源
*@seeorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()*/@OverrideprotectedObject determineCurrentLookupKey() {
logger.info("【设置系统当前使用的数据源】");
Map configMap =DBContextHolder.getDBType();
logger.info("【当前数据源配置为:{}】", configMap);if(MapUtils.isEmpty(configMap)) {//使用默认数据源
returnDEFAULT_DATASOURCE_KEY;
}//判断数据源是否需要初始化
this.verifyAndInitDataSource();
logger.info("【切换至数据源:{}】", configMap);returnconfigMap.get(DBContextHolder.DATASOURCE_KEY);
}/*** 判断数据源是否需要初始化
* @Author : ll. create at 2017年3月27日 下午3:57:43*/
private voidverifyAndInitDataSource() {
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键值对集合添加新的数据源
*@paramkey 新的数据源键
*@paramdataSource 新的数据源*/
private voidaddTargetDataSource(String key, T dataSource) {this.targetDataSources.put(key, dataSource);super.setTargetDataSources(this.targetDataSources);//afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
super.afterPropertiesSet();
}
}
DBContextHolder.java
packagecom.blog.datasource;importjava.util.HashMap;importjava.util.Map;public classDBContextHolder {/**数据源的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(MapdataSourceConfigMap) {
contextHolder.set(dataSourceConfigMap);
}public static MapgetDBType() {
Map dataSourceConfigMap =contextHolder.get();if (dataSourceConfigMap == null) {
dataSourceConfigMap= new HashMap();
}returndataSourceConfigMap;
}public static voidclearDBType() {
contextHolder.remove();
}
}
DruidDynamicDataSource.java
packagecom.blog.datasource;importjava.sql.SQLException;importjava.util.List;importorg.apache.commons.lang3.StringUtils;importcom.alibaba.druid.filter.Filter;importcom.alibaba.druid.pool.DruidDataSource;/*** Druid数据源
* @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 ListfilterList;/** 创建数据源
* @see com.cdelabcare.pubservice.datasource.IDynamicDataSource#createDataSource(java.lang.String, java.lang.String, java.lang.String, java.lang.String)*/@OverridepublicDruidDataSource createDataSource(String driverClassName, String url, String username,
String password) {
DruidDataSource parent= (DruidDataSource) super.getApplicationContext().getBean(
DEFAULT_DATASOURCE_KEY);
DruidDataSource ds= newDruidDataSource();
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 newRuntimeException(e);
}
addFilterList(ds);returnds;
}private voidaddFilterList(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);
}
}
}
}
二、修改配置文件
主要是修改spring-mybatis.xml
三、单元测试
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.blog.datasource.DBContextHolder;
import com.blog.entity.User;
import com.blog.mapper.PostDao;
import com.blog.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/spring.xml")
public class BlogTest {
@Autowired
private UserService ud;
@Test
public void testName() throws Exception {
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/blog_test?useUnicode=true&characterEncoding=UTF-8");
map.put(DBContextHolder.DATASOURCE_USERNAME, "root");
map.put(DBContextHolder.DATASOURCE_PASSWORD, "1234");
DBContextHolder.setDBType(map);
Listlist = ud.selectList(null);
for (User user : list) {
System.out.println(user);
}
}
}
测试后,控制台如图:
小结:
其实配置多数据源有很多方式,有aop,也有配置多个bean的方式,当然了,只要能达到目的就是王道,当然了,我也强调一点,不是实现完就不管了,背后的为什么比只要实现就好更重要。
其实,有一点我想说的是,有些时候遇到难题,最好的方式是迎面而上解决这个问题,而不是逃避或者独自焦躁。同时直面问题,也是解决焦躁的最好方式。这个我已经深有体会了。
另外补充到,上传至github上的多数据源示例同时也是ssm框架的搭建。有哪位朋友不会搭建框架,可以参考我的这个。希望能对你们有什么帮助。