天天看点

SSM 最灵活实现动态切换操作多数据源

之前写过一篇极易上手的操作多数据基本看了就能整合实现多数据源了。

当前这篇文章,区别为,我们将会把多个数据源的信息放在一张数据库配置表 jdbc_config里面去,项目开始运行的时候,会从默认连接的数据库的这个配置表获取多数据源的信息,进行数据源的加载设置。  

OK,现在开始,首先我们把项目默认数据库写下,jdbc.properties:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/game_message?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=root

jdbc.initialSize=50
jdbc.maxActive=100
jdbc.minIdle=10
jdbc.maxIdle=100
jdbc.maxWait=60000
jdbc.validationQuery=SELECT 1
jdbc.testOnBorrow=false
jdbc.testOnReturn=false
jdbc.testWhileIdle=true
jdbc.timeBetweenEvictionRunsMillis=30000
jdbc.minEvictableIdleTimeMillis=180000
jdbc.removeAbandoned=true
jdbc.removeAbandonedTimeout=1800
jdbc.logAbandoned=true
jdbc.poolPreparedStatements=false
jdbc.maxOpenPreparedStatements=20
jdbc.filters=stat      

这个数据库里面有什么呢?就是我们的各个数据库的相关配置信息:

SSM 最灵活实现动态切换操作多数据源

jdbc_config表生成的SQL语句: 

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for jdbc_config
-- ----------------------------
DROP TABLE IF EXISTS `jdbc_config`;
CREATE TABLE `jdbc_config`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dbName` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `url` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `username` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `driverClassName` varchar(254) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9520 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;      

PS:当然这个表结构不是必须固定这样的,等你看完整篇文章后就知道可以怎么修改了,表仅仅作为数据源信息的提供。

OK,数据源的表已经完成(当然你要把你的数据源信息都录入这个表里),接下来是修改xml文件,

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-4.2.xsd
         http://www.springframework.org/schema/mvc
         http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd ">

    <!--配置和创建jdbc数据源 -->
    <context:property-placeholder location="classpath:jdbc.properties"
                                  ignore-unresolvable="true" />
    <bean id="Source" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="${jdbc.initialSize}" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="${jdbc.maxActive}" />
        <property name="maxIdle" value="${jdbc.maxIdle}"/>
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="${jdbc.minIdle}" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="${jdbc.maxWait}" />
        <property name="validationQuery" value="${jdbc.validationQuery}" />
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
        <property name="testOnReturn" value="${jdbc.testOnReturn}" />
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
        <!-- 打开removeAbandoned功能 -->
        <property name="removeAbandoned" value="${jdbc.removeAbandoned}" />
        <!-- 1800秒,也就是30分钟 -->
        <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}" />
        <!-- 关闭abanded连接时输出错误日志 -->
        <property name="logAbandoned" value="${jdbc.logAbandoned}" />
        <!-- 监控数据库 -->
        <property name="filters" value="${jdbc.filters}" />

    </bean>
    <bean class="com.springmvc.dynamicsource.MutiDataSourceBean" id="dataSource">
        <property name="defaultTargetDataSource" ref="Source"/>
    </bean>
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 配置会话工厂SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis.xml" />
        <property name="mapperLocations" value="classpath:sqlmap/*Mapper.xml"/>
        <property name="typeAliasesPackage" value="com.springmvc.entity" />
    </bean>
    <!-- 在spring容器中配置mapper的扫描器产生的动态代理对象在spring的容器中自动注册,bean的id就是mapper类名(首字母小写)-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 指定扫描包的路径,就是mapper接口的路径,多个包中间以 半角逗号隔开   -->
        <property name="basePackage" value="com.springmvc.dao"/>
        <!-- 配置sqlSessionFactoryBeanName -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

</beans>      

这里的配置信息,需要关注的是:

第一个蓝色框的 “Source”,是给数据库起的ID;

而第二个蓝框里面的“Source”,可以结合前面的defaultTargetDataSource,这里是配置项目跑起来默认连接的数据库;

红框的“dataSource”,非常重要,这是后面手动切换数据源获取信息的地方。 所以可以看到无论是事务管理器、sql工厂都是关联了这个dataSource。

SSM 最灵活实现动态切换操作多数据源

OK,接下来是我们手动实现多数据源加载切换的java类:

SSM 最灵活实现动态切换操作多数据源

后面提供手动切换数据库的方法类,DynamicDataSourceHolder.java:

public class DynamicDataSourceHolder {

  private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

  /**
   * @Description: 设置数据源类型 @param dataSourceType 数据库类型 @return void @throws
   */
  public static void setDataSourceType(String dataSourceType) {
    contextHolder.set(dataSourceType);
  }

  /**
   * @Description: 获取数据源类型 @param @return String @throws
   */
  public static String getDataSourceType() {

    return contextHolder.get();
  }

  /**
   * @Description: 清除数据源类型 @param @return void @throws
   */
  public static void clearDataSourceType() {
    contextHolder.remove();
  }

}      

用于jdbc连接数据库的类,JdbcPOJO.java:

import java.io.Serializable;

public class JdbcPOJO implements Serializable {

  private String driverClassName;

  private String id;
  private String url;
  private String username;
  private String password;

  public String getDriverClassName() {
    return driverClassName;
  }

  public void setDriverClassName(String driverClassName) {
    this.driverClassName = driverClassName;
  }

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getUrl() {
    return url;
  }

  public void setUrl(String url) {
    this.url = url;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

}      

关键的多数据源加载类,MutiDataSourceBean.java:

import com.alibaba.druid.pool.DruidDataSource;
import com.springmvc.util.PropertyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class MutiDataSourceBean extends AbstractRoutingDataSource implements ApplicationContextAware {

  private static Logger log = LoggerFactory.getLogger(MutiDataSourceBean.class);
  private static ApplicationContext ac;
  private static Map<String, String> buildDataSources = new HashMap();

  @Override
  public void afterPropertiesSet() {

    log.info("初始化多数据源");
    try {
      initailizeMutiDataSource();
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    log.info("多数据源加入spring容器中成功!");

    super.afterPropertiesSet();

  }

  @Override
  public void setApplicationContext(ApplicationContext ctx) throws BeansException {
    // TODO Auto-generated method stub
    ac = ctx;

  }

  private List<JdbcPOJO> getServers() {
    List<JdbcPOJO> list = new ArrayList();
    try {
      Class.forName(PropertyUtil.getPropery("jdbc.driverClassName"));
      Connection conn = DriverManager.getConnection(PropertyUtil.getPropery("jdbc.url").trim(),
          PropertyUtil.getPropery("jdbc.username").trim(), PropertyUtil.getPropery("jdbc.password").trim());

      PreparedStatement ps = conn.prepareStatement(
          "SELECT id,dbName,url,username,password,driverClassName FROM jdbc_config WHERE status=0");
      ResultSet rs = ps.executeQuery();

      while (rs.next()) {
        JdbcPOJO obj = new JdbcPOJO();
        obj.setId("Source" + rs.getString("dbName"));
        obj.setUrl(rs.getString("url"));
        obj.setUsername(rs.getString("username"));
        obj.setPassword(rs.getString("password"));
        obj.setDriverClassName(rs.getString("driverClassName"));

        list.add(obj);
      }

      if (rs != null) {
        rs.close();
      }

      if (ps != null) {
        ps.close();
      }

      if (conn != null) {
        conn.close();
      }

    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    return list;
  }

  public static boolean getSourcesExistsByKey(String key) {
    if (buildDataSources.containsKey(key)) {
      return true;
    } else {
      return false;
    }

  }

  private void initailizeMutiDataSource() throws Exception {

    List<JdbcPOJO> servers = getServers();
    
    DefaultListableBeanFactory acf = (DefaultListableBeanFactory) ac.getAutowireCapableBeanFactory();

    Map<Object, DruidDataSource> dsMap = new HashMap<Object, DruidDataSource>();

    for (JdbcPOJO obj : servers) {

      log.debug("初始化数据源:{}",obj.getId());
      System.out.println("数据源加载:   ----------"+obj.getId());
      DruidDataSource ds = new DruidDataSource();

      String id = obj.getId();
      ds.setDriverClassName(obj.getDriverClassName());
      ds.setUsername(obj.getUsername());
      ds.setUrl(obj.getUrl());
      ds.setPassword(obj.getPassword());

      ds.setInitialSize(Integer.parseInt(PropertyUtil.getPropery("jdbc.initialSize").trim()));
      ds.setMaxActive(Integer.parseInt(PropertyUtil.getPropery("jdbc.maxActive").trim()));
      ds.setMinIdle(Integer.valueOf(PropertyUtil.getPropery("jdbc.minIdle").trim()));
      ds.setMaxWait(Integer.valueOf(PropertyUtil.getPropery("jdbc.maxWait").trim()));
      ds.setTestOnBorrow(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.testOnBorrow").trim()));
      ds.setTestOnReturn(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.testOnReturn").trim()));
      ds.setTestWhileIdle(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.testWhileIdle").trim()));
      ds.setTimeBetweenEvictionRunsMillis(
          Long.valueOf(PropertyUtil.getPropery("jdbc.timeBetweenEvictionRunsMillis").trim()));
      ds.setMinEvictableIdleTimeMillis(
          Long.valueOf(PropertyUtil.getPropery("jdbc.minEvictableIdleTimeMillis").trim()));
      ds.setRemoveAbandoned(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.removeAbandoned").trim()));
      ds.setRemoveAbandonedTimeout(
          Integer.valueOf(PropertyUtil.getPropery("jdbc.removeAbandonedTimeout").trim()));
      ds.setLogAbandoned(Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.logAbandoned").trim()));
      ds.setPoolPreparedStatements(
          Boolean.parseBoolean(PropertyUtil.getPropery("jdbc.poolPreparedStatements").trim()));
      ds.setMaxOpenPreparedStatements(
          Integer.parseInt(PropertyUtil.getPropery("jdbc.maxOpenPreparedStatements").trim()));
      ds.setFilters(PropertyUtil.getPropery("jdbc.filters").trim());
      acf.registerSingleton(id, ds);
      dsMap.put(id, ds);
      buildDataSources.put(id, id);
    }
    this.setTargetDataSources(dsMap);
    // setDefaultTargetDataSource(dsMap.get("Source"));//设置默认数据源
  }

  @Override
  protected Object determineCurrentLookupKey() {
    return DynamicDataSourceHolder.getDataSourceType();
  }

  @Override
  public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
    super.setDataSourceLookup(dataSourceLookup);
  }

  @Override
  public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
    super.setDefaultTargetDataSource(defaultTargetDataSource);
  }

  @Override
  public void setTargetDataSources(Map targetDataSources) {
    super.setTargetDataSources(targetDataSources);
  }

}      

简单介绍下上面这个类,项目跑起来后,简单的几步是:

SSM 最灵活实现动态切换操作多数据源
SSM 最灵活实现动态切换操作多数据源
SSM 最灵活实现动态切换操作多数据源
SSM 最灵活实现动态切换操作多数据源
SSM 最灵活实现动态切换操作多数据源

然后读取数据库表jdbc_config里面的多数据源信息是:

SSM 最灵活实现动态切换操作多数据源

顺便说下, 这里的id, 我项目里面写的是 Source拼接dbName的值(这个就是用于给我们切换数据源的id,规则可以自己修改):

SSM 最灵活实现动态切换操作多数据源

OK,到这里多数据的整合其实已经完成了,我们来写个接口测试下,看看效果:

@Autowired
    MessageboardService messageboardServiceImpl;
    @Autowired
    JdbcConfigService jdbcConfigServiceImpl;

    @RequestMapping(value = "/testDbSource", produces = "application/json; charset=utf-8")
    public void testDbSource() {

        System.out.println("当前数据源ID:"+DynamicDataSourceHolder.getDataSourceType());
        List<JdbcConfig> list1 = jdbcConfigServiceImpl.getList();
        System.out.println("获取结果数:"+list1.size()); //默认是 默认连接的数据库
        DynamicDataSourceHolder.setDataSourceType("Source"+"db1".trim()); //切换到数据源db1
        System.out.println("当前数据源ID:"+DynamicDataSourceHolder.getDataSourceType());
        Messageboard  message = messageboardServiceImpl.selectByPrimaryKey(3);
        System.out.println(message.toString());
        DynamicDataSourceHolder.setDataSourceType("Source"); //切换回来默认数据库
        System.out.println("当前数据源ID:"+DynamicDataSourceHolder.getDataSourceType());
        List<JdbcConfig> list2 = jdbcConfigServiceImpl.getList();
        System.out.println("获取结果数:"+list2.size()); //默认数据库随便查询一个表信息

    }      

把项目跑起来:

SSM 最灵活实现动态切换操作多数据源

用postman运行一下测试接口:

SSM 最灵活实现动态切换操作多数据源

看下控制台输出情况:

刚开始,我们没有手动去切换数据源,可以看到从动态数据数据源控制器里获取的数据源为null,但是我们依然查出了数据,

这是因为我们项目里面配置了默认数据源,也就是这些数据是从默认数据库获取的。

SSM 最灵活实现动态切换操作多数据源

接着我们切换到了数据源db1(规则是我们前面的数据源id规则:Source拼接dbName的值),可以看到从db1数据库成功查询出了相关的值:

SSM 最灵活实现动态切换操作多数据源
SSM 最灵活实现动态切换操作多数据源

最后,我们再切换回来默认数据库(默认数据源的ID就是xml文件里面的“Source”),随意切换后,查询也是正常的:

继续阅读