Spring Boot 多数据源自动切换

时间:2019-03-19
本文章向大家介绍Spring Boot 多数据源自动切换,主要包括Spring Boot 多数据源自动切换使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

在Spring Boot中使用单数据源的配置很简单,我们简单回忆下:只需要在application.properties进行基本的连接配置,在pom.xml引入基本的依赖即可。


那么多数据源的原理呢?其实很简单,就是读取配置文件,根据配置文件中的配置的数据源数量,动态创建dataSource并注册到Spring中。在上一节我们介绍了使用Java代码将对象注册到Spring中,多数据源就是基于这儿基础进行动态创建的。本节大概需要这么几个步骤:

(1)新建maven java project;

(2)在pom.xml添加相关依赖;

(3)编写app.java启动类;

(4)编写application.properties配置文件;

(5)编写多数据源注册文件;

(6)编写测试类

(7)测试
接下来让我们按照这个步骤来进行编写我们的代码吧。


(1)新建maven java project;

 我们新建一个maven project进行测试,取名为:spring-boot-multids
(2)在pom.xml添加相关依赖;
  在pom.xml文件中加入依赖的库文件,主要是spring boot基本的,数据库驱动,spring-jpa支持即可,具体pom.xml文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.kfit</groupId>
  <artifactId>spring-boot-multids</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>spring-boot-multids</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- jdk版本号,这里需要你本地进行的jdk进行修改,这里angel使用的是1.8的版本. -->
    <java.version>1.8</java.version>
  </properties>
 
 
   <!--
       spring boot 父节点依赖,
       引入这个之后相关的引入就不需要添加version配置,
       spring boot会自动选择最合适的版本进行添加。
       在这里使用的1.3.3版本,可能目前官方有最新的版本了,大家可以
       使用最新的版本。
     -->
    <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>1.3.3.RELEASE</version>
    </parent>
 
  <dependencies>
    <!-- 单元测试包,在这里没有使用到. -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
   
    <!-- spring boot web支持:mvc,aop...
        这个是最基本的,基本每一个基本的demo都是需要引入的。
    -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
   
    <!-- mysql驱动.
       我们的demo是多数据源,在这里使用Mysql数据库.
    -->
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
    </dependency>
   
   
    <!-- spring jpa
       spring jpa中带有自带的tomcat数据连接池;
       在代码中我们也需要用到.
     -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
   
  </dependencies>
</project>
在上面的配置文件中都有相应的解释,大家可以自己解读下。

(3)编写app.java启动类;
编写spring boot的启动类:
com.kfit.App:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kfit;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
/**
 *
 *
 * @version v.0.1
 */
@SpringBootApplication
publicclass App {
    publicstaticvoid main(String[] args) {
       SpringApplication.run(App.class, args);
    }
}


(4)编写application.properties配置文件;

在这里主要是多数据源的配置:
src/main/resources/application.properties:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
########################################################
###配置文件包括1个主数据源和多个数据源,
###其中主数据源在Spring中的beanName默认为dataSource,
###另外几个数据源的beanName分包为:ds1、ds2、ds3
###其中datasource的type属性可以具体指定到我们需要的数据源上面,
###不指定情况下默认为:org.apache.tomcat.jdbc.pool.DataSource
###当然你也可以把这些数据源配置到主dataSource数据库中,然后读取数据库生成多数据源。当然这样做的必要性并不大,难不成数据源还会经常变吗。
########################################################
 
# 主数据源,默认的
#spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
 
 
# 更多数据源
custom.datasource.names=ds1,ds2,ds3
#custom.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds1.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
custom.datasource.ds1.username=root
custom.datasource.ds1.password=root
 
#custom.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds2.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test
custom.datasource.ds2.username=root
custom.datasource.ds2.password=root
 
#custom.datasource.ds3.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds3.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds3.url=jdbc:mysql://localhost:3306/test
custom.datasource.ds3.username=root
custom.datasource.ds3.password=root
 
 
# 下面为连接池的补充设置,应用到上面所有数据源中
spring.datasource.maximum-pool-size=100
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validation-query=SELECT 1
spring.datasource.test-on-borrow=false
spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=18800

(5)编写多数据源注册文件;

 这个注入是最核心的部分,我们先看代码:
com.kfit.config.datasource.multids.MultipleDataSourceBeanDefinitionRegistryPostProcessor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package com.kfit.config.datasource.multids;
 
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
 
import javax.sql.DataSource;
 
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ScopeMetadata;
import org.springframework.context.annotation.ScopeMetadataResolver;
import org.springframework.core.env.Environment;
 
/**
 * 动态创建多数据源注册到Spring中
 *
  接口:BeanDefinitionRegistryPostProcessor只要是注入bean,
  在上一节介绍过使用方式;
 
 接口:接口 EnvironmentAware 重写方法 setEnvironment
 可以在工程启动时,获取到系统环境变量和application配置文件中的变量。
这个第24节介绍过.
 
 方法的执行顺序是:
 
 setEnvironment()-->postProcessBeanDefinitionRegistry() --> postProcessBeanFactory()
 
 
 *
 *
 * @version v.0.1
 */
@Configuration
publicclass MultipleDataSourceBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor,EnvironmentAware{
 
   
    //作用域对象.
    private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
    //bean名称生成器.
    private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
      
    //如配置文件中未指定数据源类型,使用该默认值
    privatestaticfinal Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
    //  private static final Object DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
      
    // 存放DataSource配置的集合;
    private Map<String, Map<String, Object>> dataSourceMap = new HashMap<String, Map<String, Object>>();
   
   
    @Override
    publicvoid postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.postProcessBeanFactory()");
       //设置为主数据源;
       beanFactory.getBeanDefinition("dataSource").setPrimary(true);
      
       if(!dataSourceMap.isEmpty()){
           //不为空的时候.
           BeanDefinition bd = null;
            Map<String, Object> dsMap = null;
            MutablePropertyValues mpv = null;
            for (Entry<String, Map<String, Object>> entry : dataSourceMap.entrySet()) {
                 bd = beanFactory.getBeanDefinition(entry.getKey());
                 mpv = bd.getPropertyValues();
                 dsMap = entry.getValue();
                 mpv.addPropertyValue("driverClassName", dsMap.get("driverClassName"));
                 mpv.addPropertyValue("url", dsMap.get("url"));
                 mpv.addPropertyValue("username", dsMap.get("username"));
                 mpv.addPropertyValue("password", dsMap.get("password"));
            }
       }
    }
   
 
    @SuppressWarnings("unchecked")
    @Override
    publicvoid postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()");
       try {
           if(!dataSourceMap.isEmpty()){
              //不为空的时候,进行注册bean.
              for(Entry<String,Map<String,Object>> entry:dataSourceMap.entrySet()){
                  Object type = entry.getValue().get("type");//获取数据源类型,没有设置为默认的数据源.
                  if(type == null){
                     type= DATASOURCE_TYPE_DEFAULT;
                  }
                  registerBean(registry, entry.getKey(),(Class<? extends DataSource>)Class.forName(type.toString()));
              }
           }
       } catch (ClassNotFoundException  e) {
           //异常捕捉.
           e.printStackTrace();
       }
    }
   
   
    /**
     * 注意重写的方法 setEnvironment 是在系统启动的时候被执行。
     * 这个方法主要是:加载多数据源配置
     * 从application.properties文件中进行加载;
     */
    @Override
    publicvoid setEnvironment(Environment environment) {
    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.setEnvironment()");
      
       /*
        * 获取application.properties配置的多数据源配置,添加到map中,之后在postProcessBeanDefinitionRegistry进行注册。
        */
       //获取到前缀是"custom.datasource." 的属性列表值.
       RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment,"custom.datasource.");
       //获取到所有数据源的名称.
       String dsPrefixs = propertyResolver.getProperty("names");
       String[] dsPrefixsArr = dsPrefixs.split(",");
       for(String dsPrefix:dsPrefixsArr){
           /*
            * 获取到子属性,对应一个map;
            * 也就是这个map的key就是
            *
            * type、driver-class-name等;
            *
            *
            */
           Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
           //存放到一个map集合中,之后在注入进行使用.
           dataSourceMap.put(dsPrefix, dsMap);
       }
    }
 
   
   
    /**
     * 注册Bean到Spring
     *
     * @param registry
     * @param name
     * @param beanClass
     * @author SHANHY
     * @create 2016年1月22日
     */
    privatevoid registerBean(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
       
        //单例还是原型等等...作用域对象.
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
        // 可以自动生成name
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, registry));
 
        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
 
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }
}
在代码中已经加入了,注释,大家可以好好看,在这里简单说明下。

以上代码的执行顺序是:
setEnvironment()-->postProcessBeanDefinitionRegistry() --> postProcessBeanFactory()
在setEnvironment()方法中主要是读取了application.properties的配置;

在postProcessBeanDefinitionRegistry()方法中主要注册为spring的bean对象;

在postProcessBeanFactory()方法中主要是注入从setEnvironment方法中读取的application.properties配置信息。
需要注意的是这里并没有读取其它相同的数据源公共配置,这里我们不做介绍,在下节介绍,主要是因为这节在实际中我们并不会这么使用,这里只是过渡下,方便下节进行讲解。


(6)编写测试类

我们编写一个简单的类进行测试下,到底我们的多数据源是否注入成功了。
com.kfit.controller.TestController:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.kfit.controller;
 
import java.sql.ResultSet;
import java.sql.SQLException;
 
import javax.sql.DataSource;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
/**
 * 测试;
 *
 * @version v.0.1
 */
@RestController
publicclass TestController {
   
    //没有指定为主数据源.
    @Autowired
    private DataSource dataSource;
   
    @Autowired
    @Qualifier("ds1")
    private DataSource dataSource1;
   
    @Autowired
    @Qualifier("ds2")
    private DataSource dataSource2;
   
    private JdbcTemplate jdbcTemplate;
   
   
    @Autowired
    publicvoid setJdbcTemplate(JdbcTemplate jdbcTemplate) {
       System.out.println("TestController.setJdbcTemplate()");
       jdbcTemplate.setDataSource(dataSource1);//设置dataSource
       this.jdbcTemplate = jdbcTemplate;
    }
 
    @RequestMapping("/get")
    public String get(){
       //观察控制台的打印信息.
       System.out.println(dataSource);
       return"ok";
    }
   
    @RequestMapping("/get1")
    public String get1(){
       //观察控制台的打印信息.
       System.out.println(dataSource1);
       return"ok.1";
    }
   
    @RequestMapping("/get2")
    public String get2(){
       //观察控制台的打印信息.
       System.out.println(dataSource2);
       return"ok.2";
    }
   
    @RequestMapping("/get3")
    public String get3(){
       //观察控制台的打印信息.
       JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource1);
       System.out.println(jdbcTemplate.getDataSource());
       System.out.println(jdbcTemplate);
        
       /*
         * Demo1只在test1中存在,test并没有此数据库;
         * 需要自己自己进行复制,不然会报错:Table 'test1.demo1' doesn't exist
         */
       String sql = "select *from Demo1";
        jdbcTemplate.query(sql, new RowMapper<String>(){
 
            @Override
            public String mapRow(ResultSet rs, introwNum) throws SQLException {
                System.out.println(rs.getLong("id")+"---"+rs.getString("name"));
                return"";
            }
 
        });
      
       return"ok.3";
    }
   
   
    @RequestMapping("/get4")
    public String get4(){
       //观察控制台的打印信息.
       System.out.println(jdbcTemplate.getDataSource());
       System.out.println(jdbcTemplate);
        
       /*
         * Demo1只在test1中存在,test并没有此数据库;
         * 需要自己自己进行复制,不然会报错:Table 'test1.demo1' doesn't exist
         */
       String sql = "select *from Demo1";
        jdbcTemplate.query(sql, new RowMapper<String>(){
 
            @Override
            public String mapRow(ResultSet rs, introwNum) throws SQLException {
                System.out.println(rs.getLong("id")+"---"+rs.getString("name"));
                return"";
            }
 
        });
      
       return"ok.4";
    }
}
以上代码在实际开发中,是绝对不能这么编写的,这里只是为了方便进行测试。


 (7)测试

Run As 运行app.java进行测试:
访问:

http://127.0.0.1:8080/get
http://127.0.0.1:8080/get1
http://127.0.0.1:8080/get2
http://127.0.0.1:8080/get3
http://127.0.0.1:8080/get4
查看控制台的打印信息。
然而我们在项目中不一定需要直接使用dataSource的,大家都习惯使用JDBC的jdbcTemplate、Mybatis的sqlSessionTemplate,再或者就是以Mybatis为例直接动态代理到Mapper接口上。