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

简介: 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


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


image.png


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。


image.png


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


image.png


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


1.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);
  }
}


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


image.png


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


image.png


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


image.png


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()); //默认数据库随便查询一个表信息
    }


把项目跑起来:


image.png


用postman运行一下测试接口:


image.png


看下控制台输出情况:


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


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


image.png


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


image.pngimage.png


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


image.pngimage.png


好了,到此,该篇操作多数据源的教程完毕。

相关文章
|
8月前
java202304java学习笔记第六十六天-ssm-动态sql-一对一的配置实现1
java202304java学习笔记第六十六天-ssm-动态sql-一对一的配置实现1
40 0
|
8月前
java202304java学习笔记第六十七天-ssm-动态sql-多对多注解开发之1
java202304java学习笔记第六十七天-ssm-动态sql-多对多注解开发之1
37 0
|
8月前
java202304java学习笔记第六十七天-ssm-动态sql-多对多配置实现2
java202304java学习笔记第六十七天-ssm-动态sql-多对多配置实现2
43 1
|
8月前
java202304java学习笔记第六十七天-ssm-动态sql-知识小结
java202304java学习笔记第六十七天-ssm-动态sql-知识小结
40 0
|
8月前
java202304java学习笔记第六十七天-ssm-动态sql-一对多配置2
java202304java学习笔记第六十七天-ssm-动态sql-一对多配置2
40 0
|
8月前
java202304java学习笔记第六十七天-ssm-动态sql-一对多配置
java202304java学习笔记第六十七天-ssm-动态sql-一对多配置
46 0
|
8月前
java202304java学习笔记第六十七天-ssm-动态sql-一对一的配置实现2
java202304java学习笔记第六十七天-ssm-动态sql-一对一的配置实现2
44 0
java202304java学习笔记第六十七天-ssm-动态sql-一对一的配置实现2
|
8月前
java202304java学习笔记第六十六天-ssm-动态sql-一对一的配置实现2
java202304java学习笔记第六十六天-ssm-动态sql-一对一的配置实现2
46 0
|
8月前
java202304java学习笔记第六十七天-ssm-动态sql-多对多注解开发之2
java202304java学习笔记第六十七天-ssm-动态sql-多对多注解开发之2
39 0
java202304java学习笔记第六十七天-ssm-动态sql-多对多注解开发之2
|
8月前
java202304java学习笔记第六十七天-ssm-动态sql-一对多得注解开发2
java202304java学习笔记第六十七天-ssm-动态sql-一对多得注解开发2
40 0