SSM框架之多数据源配置
发布日期:2021-05-09 08:58:01 浏览次数:9 分类:博客文章

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

多数据源的应用场景:主要是数据库拆分后,怎样让多个数据库结合起来来达到业务需求。

SSM框架(Spring+SpringMVC+MyBatis(MyBatis-Plus))是目前最常用的,此次仍然是maven工程。

关于这个多数据源例子,我已经上传到我的github上,地址为:https://github.com/youcong1996/study_simple_demo.git

不过需要注意的是,分支为demo1,不是主分支,如图所示:

 

如果下面的示例,你们看不懂或者不能理解,可以git clone我的地址

在编程的世界里,简洁即完美。

如何实现多数据源?

一句话,三个类加xml配置即可达到这个目的。

 

一、编写三个类

 

AbstractDynamicDataSource.java

package com.blog.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 * @update  * @updateDate  */public abstract class AbstractDynamicDataSource
extends AbstractRoutingDataSource implements ApplicationContextAware { /** 日志 */ protected Logger logger = LoggerFactory.getLogger(getClass()); /** 默认的数据源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 新的数据源 */ private void addTargetDataSource(String key, T dataSource) { this.targetDataSources.put(key, dataSource); super.setTargetDataSources(this.targetDataSources); // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的 super.afterPropertiesSet(); }}

 

DBContextHolder.java

package com.blog.datasource;import java.util.HashMap;import java.util.Map;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(); }}

 

DruidDynamicDataSource.java

package com.blog.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数据源 * @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); } } }}

 

二、修改配置文件

主要是修改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); List
list = ud.selectList(null); for (User user : list) { System.out.println(user); } }}

 

测试后,控制台如图:

 

小结:

其实配置多数据源有很多方式,有aop,也有配置多个bean的方式,当然了,只要能达到目的就是王道,当然了,我也强调一点,不是实现完就不管了,背后的为什么比只要实现就好更重要。

其实,有一点我想说的是,有些时候遇到难题,最好的方式是迎面而上解决这个问题,而不是逃避或者独自焦躁。同时直面问题,也是解决焦躁的最好方式。这个我已经深有体会了。

另外补充到,上传至github上的多数据源示例同时也是ssm框架的搭建。有哪位朋友不会搭建框架,可以参考我的这个。希望能对你们有什么帮助。

上一篇:编程规范定义
下一篇:开源项目之架构分享

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2025年04月16日 22时38分01秒