zoukankan      html  css  js  c++  java
  • SSM框架之多数据源配置

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

    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<T extends DataSource> extends AbstractRoutingDataSource
                                                                                                    implements
                                                                                                    ApplicationContextAware {
    
        /** 日志 */
        protected Logger logger = LoggerFactory.getLogger(getClass());
        /** 默认的数据源KEY */
        protected static final String DEFAULT_DATASOURCE_KEY = "defaultDataSource";
    
        /** 数据源KEY-VALUE键值对 */
        public Map<Object, Object> 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<Object, Object> 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);
    
        /**
         * 设置系统当前使用的数据源
         * <p>数据源为空或者为0时,自动切换至默认数据源,即在配置文件中定义的默认数据源
         * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
         */
        @Override
        protected Object determineCurrentLookupKey() {
            logger.info("【设置系统当前使用的数据源】");
            Map<String, Object> 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<String, Object> 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<Map<String, Object>> contextHolder = new ThreadLocal<Map<String, Object>>();
    
        public static void setDBType(Map<String, Object> dataSourceConfigMap) {
            contextHolder.set(dataSourceConfigMap);
        }
    
        public static Map<String, Object> getDBType() {
            Map<String, Object> dataSourceConfigMap = contextHolder.get();
            if (dataSourceConfigMap == null) {
                dataSourceConfigMap = new HashMap<String, Object>();
            }
            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<DruidDataSource> {
    
        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<Filter> 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<Filter> 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

     <!-- 配置数据源 -->
        <bean name="defaultDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <property name="url" value="${jdbc_url}"/>
            <property name="username" value="${jdbc_username}"/>
            <property name="password" value="${jdbc_password}"/>
    
            <!-- 初始化连接大小 -->
            <property name="initialSize" value="0"/>
            <!-- 连接池最大使用连接数量 -->
            <property name="maxActive" value="20"/>
            <!-- 连接池最大空闲 -->
            <property name="maxIdle" value="20"/>
            <!-- 连接池最小空闲 -->
            <property name="minIdle" value="0"/>
            <!-- 获取连接最大等待时间 -->
            <property name="maxWait" value="60000"/>
    
            <property name="validationQuery" value="${validationQuery}"/>
            <property name="testOnBorrow" value="false"/>
            <property name="testOnReturn" value="false"/>
            <property name="testWhileIdle" value="true"/>
    
            <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
            <property name="timeBetweenEvictionRunsMillis" value="60000"/>
            <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
            <property name="minEvictableIdleTimeMillis" value="25200000"/>
    
            <!-- 打开removeAbandoned功能 -->
            <property name="removeAbandoned" value="true"/>
            <!-- 1800秒,也就是30分钟 -->
            <property name="removeAbandonedTimeout" value="1800"/>
            <!-- 关闭abanded连接时输出错误日志 -->
            <property name="logAbandoned" value="true"/>
    
            <!-- 监控数据库 -->
            <property name="filters" value="mergeStat"/>
        </bean>
    
    
       <bean id="druidDynamicDataSource" class="com.blog.datasource.DruidDynamicDataSource">
            <property name="defaultTargetDataSource" ref="defaultDataSource" />
            <property name="targetDataSources">
                <map>
                    <entry key="defaultDataSource" value-ref="defaultDataSource"/>
                    <!-- 这里还可以加多个dataSource -->
                </map>
            </property>
        </bean> 
    
    <!-- Spring整合Mybatis,更多查看文档:http://mp.baomidou.com -->
        <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
           <property name="dataSource" ref="druidDynamicDataSource" />
            <!-- 自动扫描Mapping.xml文件 -->
            <property name="mapperLocations" value="classpath:mybatis/system/*.xml"/>
            <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
            <property name="typeAliasesPackage" value="com.blog.entity"/>
            <property name="plugins">
                <array>
                    <!-- 分页插件配置 -->
                    <bean id="paginationInterceptor" class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
                    </bean>
                </array>
            </property>
            <!-- 全局配置注入 -->
            <property name="globalConfig" ref="globalConfig" />
        </bean>
    
        <!-- 配置事务管理 -->
        <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="druidDynamicDataSource"/>
        </bean>

    三、单元测试

    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<String, Object> map = new HashMap<String, Object>();
            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<User> list = ud.selectList(null);
            for (User user : list) {
                System.out.println(user);
            }
        }
    
    
    }

    测试后,控制台如图:

    小结:

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

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

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

  • 相关阅读:
    POJ 2431 Expedition(探险)
    POJ 3253 Fence Repair(修篱笆)
    POJ 3069 Saruman's Army(萨鲁曼军)
    POJ 3617 Best Cow Line(最佳奶牛队伍)
    [蓝桥杯] 排它平方数
    [蓝桥杯] 小朋友排队
    UVA315 Network 连通图割点
    POJ 1236 Network of Schools 连通图缩点
    poj1061 青蛙的约会
    拓展欧几里
  • 原文地址:https://www.cnblogs.com/youcong/p/9806848.html
Copyright © 2011-2022 走看看