Multi-environment configuration and multi-data source configuration scheme in Spring Boot

1. SpringBoot multi-environment configuration

1.1 Preface

For SpringBoot projects, there may be different configuration information (configured in application.yml or application.properties) in different environments (such as dev, test, prod, uat, etc.), such as the variable swagger.enable, in dev and test environment values It is true, and the value in the prod environment is false.
In SpringBoot, there are two ways to implement multi-environment configuration files:
one is to directly configure the configuration information of multiple environments in one configuration file (that is, multi-document blocks, separated by —), which only supports application.yml files ;
One is a main configuration file (application.yml or application.properties) and multiple environment configuration files (application-dev.yml, application-test.yml, application-prod.yml, application-uat.yml, etc.) .

1.2 Single configuration to achieve multi-environment configuration

Configured in the application.yml file, the same configuration information in different environments can be configured in the top-level document block, and different configuration information in different environments can be configured in different environment document blocks. The configuration of which document block is used in different environments can be specified through the spring.profiles.active variable.

server:
  port: 8080
spring:
  profiles:
    active: dev # 激活指定的环境
---
# 开发环境
server:
  port: 8081
spring:
  profiles: dev
swagger:
  enable: true

---
# 测试环境
server:
  port: 8082
spring:
  profiles: test
swagger:
  enable: true 
  
# 验收环境
server:
  port: 8083
spring:
  profiles: uat
swagger:
  enable: false

 # 生产环境
server:
  port: 8084
spring:
  profiles: prod
swagger:
  enable: false

1.3 Multiple Configuration File Formats

This form is to write the configuration of a single file into multiple files, which looks clearer and more concise (personal opinion).

One point to note here is that in Spring Boot 2.4 and above, a line is drawn on the spring.profiles.active configuration item, that is to say, the configuration item spring.profiles that supports multiple environments in Spring Boot .active has been deprecated. Therefore, the latest configuration method spring.config.activate.on-profile is used here.
There are two main motivations for Spring Boot's large-scale changes. One is to support Kubernetes compatibility, and the other is to fix file processing problems caused by the ConfigFileApplicationListener class. As a result, two major changes have occurred in the way files are loaded: documents will be loaded in the order they are defined, and profiles activation switches cannot be configured in specific environments.

Create a main configuration file and sub-configuration files in different environments, as shown in the figure below:
insert image description here
Among them, the application.yml main configuration file is as follows:

# 不同环境相同的配置信息可以配置在这个文件
server:
  port: 8080

# 激活指定使用哪个环境配置文件
spring:
  profiles:
    active: dev

Dev environment: application-dev.yml

server:
  port: 8081
spring:
  config:
    activate:
      on-profile: dev

Test environment: application-test.yml

server:
  port: 8082
spring:
  config:
    activate:
      on-profile: test

uat environment: application-uat.yml

server:
  port: 8083
spring:
  config:
    activate:
      on-profile: uat

Prod environment: application-prod.yml

server:
  port: 8084
spring:
  config:
    activate:
      on-profile: prod

1.4 How to activate the configuration file

1.在主配置文件中(application.yml或application.properties)指定变2.spring.profiles.active的值,例如spring.profiles.active=dev
命令行指定:java -jar springboot-demo.jar --spring.profiles.active=dev
3.虚拟机参数指定:-Dspring.profiles.active=dev

1.5 Loading order of configuration files

The springBoot startup will scan and read the configuration files in the following locations, and the priority is from high to low:
-file:./config/, which is the config folder under the current project (src directory at the same level)
-file:./, That is, under the current project
-classpath:./config/, that is, the config folder under the resources resource folder in the current project
-classpath:./, that is, under the resources resource folder in the current project

2. SpringBoot multiple data source configuration

2.1 What is a data source?

Data Source (Data Source) As the name implies, the source of data is a device or original media that provides some required data. All information for establishing a database connection is stored in the data source. Just like you can find a file in the file system by specifying the file name, you can find the corresponding database connection by providing the correct data source name. Simply put, a data source is a database or database server used by a database application.
Multiple data sources can be understood as multiple databases, or even multiple databases of different types, such as MySql and Oracle. As the project expands, sometimes it is necessary to split the database or introduce another database, and then it is necessary to configure multiple data sources.

2.2 Data preparation

It is relatively simple to use multiple data sources in SpringBoot. For the convenience of demonstration, we create two databases in MySql: ds1 and ds2, and create a student table in the ds1 database and a teacher table in the ds2 database. The database script is as follows:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------

-- Table structure for student

-- ----------------------------

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` varchar(16) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `class` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

-- ----------------------------

-- Records of student

-- ----------------------------

INSERT INTO `student` VALUES ('123456', 'zhangsan', '北京');
INSERT INTO `student` VALUES ('123457', 'lisi', '上海');

SET FOREIGN_KEY_CHECKS = 1;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------

-- Table structure for teacher

-- ----------------------------

DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher`  (
  `id` varchar(16) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `class` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

-- ----------------------------

-- Records of teacher

-- ----------------------------

INSERT INTO `teacher` VALUES ('0000001', 'wangwu', '上海');

SET FOREIGN_KEY_CHECKS = 1;

2.3 springboot+mybatis uses subpackage integration

2.3.1 Project Structure Diagram

The project structure diagram is as follows:
Please add a picture description

2.3.2 Database connection configuration

Since there are multiple data sources, the database connection information may be different, so the connection information of multiple data sources needs to be configured in the configuration file. Here we take the druid database connection pool as an example.

spring: 
  datasource:
    ds1: #数据源1,默认数据源
      url: jdbc:mysql://localhost:3306/ds1?serverTimezone=GMT&useSSL=false&useUnicode=true&characterEncoding=utf8
      username: root
      password: root
      typ: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      filters: stat
      maxActive: 2
      initialSize: 1
      maxWait: 60000
      minIdle: 1
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxOpenPreparedStatements: 20

    ds2: #数据源2
      url: jdbc:mysql://localhost:3306/ds2?serverTimezone=GMT&useSSL=false&useUnicode=true&characterEncoding=utf8
      username: root
      password: root
      typ: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      filters: stat
      maxActive: 2
      initialSize: 1
      maxWait: 60000
      minIdle: 1
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxOpenPreparedStatements: 20
mybatis:
  mapper-locations: classpath:mapper/*.xml      

2.3.3 Rewrite SpringBoot's data source configuration

The difference here from the single data source is that dataSource, sqlSessionFactory, sqlSessionTemplate, and transactionManager are all configured separately. In addition, there are two main differences between data source 1 and data source 2:
1. The packet scanning path in @MapperScan is different. Data source 1 only scans the Mapper under the com.demo.multipledatasource.dao.ds1 path, and data source 2 only scans Scan the Mapper under the com.demo.multipledatasource.dao.ds2 path, so we need to separate StudentMapper and TeacherMapper when we created it earlier. In addition, since @MapperScan has been configured in the configuration class, the @MapperScan annotation must not exist in the startup class
2. There is one more @Primary annotation in data source 1, which tells Spring the default data source we use, and there are many Indispensable in data source projects.

  • Configuration of data source 1
package com.demo.multipledatasource.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.config.Datasource1Configuration
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:20
 * @Version 1.0
 * @Description:
 */
@Configuration
@MapperScan(basePackages = {
    
    "com.demo.multipledatasource.dao.ds1"}, sqlSessionFactoryRef = "sqlSessionFactory1")
public class Datasource1Configuration {
    
    
    @Value("${mybatis.mapper-locations}")
    private String mapperLocation;
    @Value("${spring.datasource.ds1.url}")
    private String jdbcUrl;
    @Value("${spring.datasource.ds1.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.ds1.username}")
    private String username;
    @Value("${spring.datasource.ds1.password}")
    private String password;
    @Value("${spring.datasource.ds1.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.ds1.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.ds1.maxActive}")
    private int maxActive;

    @Bean(name = "dataSource1")
    @Primary
    public DataSource dataSource() {
    
    
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(jdbcUrl);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setInitialSize(initialSize);
        dataSource.setMinIdle(minIdle);
        dataSource.setMaxActive(maxActive);

        return dataSource;
    }

    @Bean("sqlSessionFactory1")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource1") DataSource dataSource) throws Exception {
    
    
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources(mapperLocation));

        return sqlSessionFactoryBean.getObject();
    }

    @Bean("sqlSessionTemplate1")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory1") SqlSessionFactory sqlSessionFactory) {
    
    
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean("transactionManager1")
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource1")DataSource dataSource) {
    
    
        return new DataSourceTransactionManager(dataSource);
    }
}

  • Configuration of data source 2
package com.demo.multipledatasource.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.config.Datasource2Configuration
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:26
 * @Version 1.0
 * @Description:
 */
@Configuration
@MapperScan(basePackages = {
    
    "com.demo.multipledatasource.dao.ds2"}, sqlSessionFactoryRef = "sqlSessionFactory2")
public class Datasource2Configuration {
    
    
    @Value("${mybatis.mapper-locations}")
    private String mapperLocation;
    @Value("${spring.datasource.ds2.url}")
    private String jdbcUrl;
    @Value("${spring.datasource.ds2.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.ds2.username}")
    private String username;
    @Value("${spring.datasource.ds2.password}")
    private String password;
    @Value("${spring.datasource.ds2.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.ds2.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.ds2.maxActive}")
    private int maxActive;

    @Bean(name = "dataSource2")
    public DataSource dataSource() {
    
    
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(jdbcUrl);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setInitialSize(initialSize);
        dataSource.setMinIdle(minIdle);
        dataSource.setMaxActive(maxActive);

        return dataSource;
    }

    @Bean("sqlSessionFactory2")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource) throws Exception {
    
    
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources(mapperLocation));

        return sqlSessionFactoryBean.getObject();
    }

    @Bean("sqlSessionTemplate2")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) {
    
    
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean("transactionManager2")
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource2") DataSource dataSource) {
    
    
        return new DataSourceTransactionManager(dataSource);
    }
}

2.4 Write three layers of code

Write the corresponding Controller and Service layer codes, query all the Student and Teacher information, and open the browser directly for testing. Of course, swagger, postman, apifox, etc. can also be used for testing.
The corresponding code is as follows:
Student

package com.demo.multipledatasource.entity;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.entity.Student
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:30
 * @Version 1.0
 * @Description:
 */
public class Student {
    
    
    private String id;
    private String name;
    private String address;

    public Student() {
    
    
    }

    public Student(String id, String name, String address) {
    
    
        this.id = id;
        this.name = name;
        this.address = address;
    }

    public String getId() {
    
    
        return id;
    }

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

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getAddress() {
    
    
        return address;
    }

    public void setAddress(String address) {
    
    
        this.address = address;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

Teacher

package com.demo.multipledatasource.entity;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.entity.Teacher
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:32
 * @Version 1.0
 * @Description:
 */
public class Teacher {
    
    
    private String id;
    private String name;
    private String address;

    public Teacher() {
    
    
    }

    public Teacher(String id, String name, String address) {
    
    
        this.id = id;
        this.name = name;
        this.address = address;
    }

    public String getId() {
    
    
        return id;
    }

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

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getAddress() {
    
    
        return address;
    }

    public void setAddress(String address) {
    
    
        this.address = address;
    }

    @Override
    public String toString() {
    
    
        return "Teacher{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

StudentController

package com.demo.multipledatasource.controller;

import com.demo.multipledatasource.entity.Student;
import com.demo.multipledatasource.service.StudentService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.controller.StudentController
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:29
 * @Version 1.0
 * @Description:
 */
@RestController
@RequestMapping("student")
public class StudentController {
    
    
    @Resource
    private StudentService studentService;

    @GetMapping("selectAllStudent")
    public List<Student> selectAllStudent(){
    
    
        return studentService.selectAllStudent();
    }
}

TeacherController

package com.demo.multipledatasource.controller;

import com.demo.multipledatasource.entity.Student;
import com.demo.multipledatasource.entity.Teacher;
import com.demo.multipledatasource.service.TeacherService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.controller.TeacherController
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:29
 * @Version 1.0
 * @Description:
 */
@RestController
@RequestMapping("teacher")
public class TeacherController {
    
    
    @Resource
    private TeacherService teacherService;

    @GetMapping("selectAllTeacher")
    public List<Teacher> selectAllTeacher(){
    
    
        return teacherService.selectAllTeacher();
    }
}

StudentService

package com.demo.multipledatasource.service;

import com.demo.multipledatasource.entity.Student;

import java.util.List;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.service.StudentService
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:34
 * @Version 1.0
 * @Description:
 */
public interface StudentService {
    
    
    /**
     * 查询所有学生
     * @return
     */
    List<Student> selectAllStudent();
}

TeacherService

package com.demo.multipledatasource.service;

import com.demo.multipledatasource.entity.Teacher;

import java.util.List;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.service.TeacherService
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:44
 * @Version 1.0
 * @Description:
 */
public interface TeacherService {
    
    
    /**
     * 查询所有教师
     * @return
     */
    List<Teacher> selectAllTeacher();
}

StudnetServiceImpl

package com.demo.multipledatasource.service.impl;

import com.demo.multipledatasource.dao.ds1.StudentDao;
import com.demo.multipledatasource.entity.Student;
import com.demo.multipledatasource.service.StudentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.service.impl.StudnetServiceImpl
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:35
 * @Version 1.0
 * @Description:
 */
@Service
public class StudnetServiceImpl implements StudentService {
    
    

    @Resource
    private StudentDao studentDao;

    @Override
    public List<Student> selectAllStudent() {
    
    
        return studentDao.selectAllStudent();
    }
}

TeacherServiceImpl

package com.demo.multipledatasource.service.impl;

import com.demo.multipledatasource.dao.ds2.TeacherDao;
import com.demo.multipledatasource.entity.Teacher;
import com.demo.multipledatasource.service.TeacherService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.service.impl.TeacherServiceImpl
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:44
 * @Version 1.0
 * @Description:
 */
@Service
public class TeacherServiceImpl implements TeacherService {
    
    

    @Resource
    private TeacherDao teacherDao;

    @Override
    public List<Teacher> selectAllTeacher() {
    
    
        return teacherDao.selectAllTeacher();
    }
}

StudentDao

package com.demo.multipledatasource.dao.ds1;

import com.demo.multipledatasource.entity.Student;

import java.util.List;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.dao.ds1.StudentDao
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:36
 * @Version 1.0
 * @Description:
 */
public interface StudentDao {
    
    
    /**
     * 查询所有学生
     * @return
     */
    List<Student> selectAllStudent();
}

TeacherDao

package com.demo.multipledatasource.dao.ds2;

import com.demo.multipledatasource.entity.Teacher;

import java.util.List;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multipledatasource.dao.ds2.TeacherDao
 * @Author: Mr.Wang
 * @Create: 2022/10/31 22:42
 * @Version 1.0
 * @Description:
 */
public interface TeacherDao {
    
    
    /**
     * 查询所有教师
     * @return
     */
    List<Teacher> selectAllTeacher();
}

StudentDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.demo.multipledatasource.dao.ds1.StudentDao">

    <resultMap type="com.demo.multipledatasource.entity.Student" id="StudentMap">
        <result property="id" column="id" jdbcType="VARCHAR"/>
        <result property="name" column="name" jdbcType="VARCHAR"/>
        <result property="address" column="address" jdbcType="VARCHAR"/>
    </resultMap>


    <!--查询指定行数据-->
    <select id="selectAllStudent" resultMap="StudentMap">
        select
        id, name, address
        from student
    </select>
</mapper>

TeacherDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.demo.multipledatasource.dao.ds2.TeacherDao">

    <resultMap type="com.demo.multipledatasource.entity.Teacher" id="TeacherMap">
        <result property="id" column="id" jdbcType="VARCHAR"/>
        <result property="name" column="name" jdbcType="VARCHAR"/>
        <result property="address" column="address" jdbcType="VARCHAR"/>
    </resultMap>


    <!--查询指定行数据-->
    <select id="selectAllTeacher" resultMap="TeacherMap">
        select
        id, name, address
        from teacher
    </select>
</mapper>

2.5 Mybatis subcontract integration test

Enter the address respectively:
http://localhost:8083/student/selectAllStudent
http://localhost:8083/teacher/selectAllTeacher
to visit, all visits are successful, indicating that MyBatis automatically switches to the corresponding data source for us. .
insert image description here
insert image description here

2.6 Custom Annotation Ways to Realize Integration

Above we improved to the automatic switching of data sources mainly relying on MyBatis. What should I do if MyBatis is not used in the project?

Here is a method based on custom annotations to realize dynamic switching of multiple data sources. There is an AbstractRoutingDataSource abstract class in SpringBoot, we can implement its abstract method determineCurrentLookupKey() to specify the data source. And write a custom annotation processing class through AOP, before the SQL statement is executed, switch to the data source set in the custom annotation to realize automatic switching of the data source.

2.6.1 Configure two database connection information

The configuration information is the same as 2.3, and the code will not be repeated here.

2.6.2 Create a data source storage class

DataSource is tied to threads, therefore, we need a thread-safe class to store DataSource, and obtain the data source through this class in determineCurrentLookupKey().
In the AbstractRoutingDataSource class, DataSource is saved in the form of key-value pairs, and ThreadLocal can be used to save the key, so as to realize automatic switching of multiple data sources

package com.demo.multidatasource.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multidatasource.util.DataSourceContextHolder
 * @Author: Mr.Wang
 * @Create: 2022/11/1 9:26
 * @Version 1.0
 * @Description:
 */
public class DataSourceContextHolder {
    
    
    private static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);

    // 使用ThreadLocal线程安全的使用变量副本
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>();

    /**
     * 设置数据源
     * */
    public static void setDataSource(String dataSource) {
    
    
        logger.info("切换到数据源:{}", dataSource);
        CONTEXT_HOLDER.set(dataSource);
    }

    /**
     * 获取数据源
     * */
    public static String getDataSource() {
    
    
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源
     * */
    public static void clearDataSource() {
    
    
        CONTEXT_HOLDER.remove();
    }
}

2.6.3 Create a data source enumeration class

package com.demo.multidatasource.util;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multidatasource.util.DataSourceEnum
 * @Author: Mr.Wang
 * @Create: 2022/11/1 9:29
 * @Version 1.0
 * @Description:
 */
public enum  DataSourceEnum {
    
    
    PRIMARY,
    DATASOURCE1
}

2.6.4 Create a dynamic data source class

The DynamicDataSource class inherits the AbstractRoutingDataSource class, and rewrites the determineCurrentLookupKey method to specify the data source.

package com.demo.multidatasource.config;

import com.demo.multidatasource.util.DataSourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multidatasource.config.DynamicDataSource
 * @Author: Mr.Wang
 * @Create: 2022/11/1 9:30
 * @Version 1.0
 * @Description:
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    
    
    @Override
    protected Object determineCurrentLookupKey() {
    
    
        return DataSourceContextHolder.getDataSource();
    }
}

2.6.5 Create a data source configuration class

package com.demo.multidatasource.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.demo.multidatasource.util.DataSourceEnum;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;


import javax.sql.DataSource;
import java.util.HashMap;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multidatasource.config.DynamicDataSourceConfiguration
 * @Author: Mr.Wang
 * @Create: 2022/11/1 9:33
 * @Version 1.0
 * @Description:
 */
@Configuration
public class DynamicDataSourceConfiguration {
    
    
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.ds1")
    public DataSource primaryDataSource(){
    
    
        return new DruidDataSource();
    }

    @Bean(name = "dataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.ds2")
    public DataSource dataSource1(){
    
    
        return new DruidDataSource();
    }

    @Bean("dynamicDataSource")
    @Primary
    public DataSource dynamicDataSource() {
    
    
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //配置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());

        //配置多数据源
        HashMap<Object, Object> dataSourceMap = new HashMap();
        dataSourceMap.put(DataSourceEnum.PRIMARY.name(),primaryDataSource());
        dataSourceMap.put(DataSourceEnum.DATASOURCE1.name(),dataSource1());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        dynamicDataSource.afterPropertiesSet();
        return dynamicDataSource;

    }
}

2.6.6 Create custom annotations

package com.demo.multidatasource.annotation;

import com.demo.multidatasource.util.DataSourceEnum;

import java.lang.annotation.*;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multidatasource.annotation.DataSource
 * @Author: Mr.Wang
 * @Create: 2022/11/1 9:52
 * @Version 1.0
 * @Description:
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceAnnotation {
    
    
    DataSourceEnum value() default DataSourceEnum.PRIMARY;
}

2.6.7 Create AOP aspect class

Intercept before executing the sql statement through AOP, and switch to the data source specified by the custom annotation. One thing to note is that @Transaction will be executed first when the custom data source annotation and the @Transaction annotation are the same method, that is, the data source is acquired before the data source is switched, so the custom annotation will become invalid, so you need to use @Order (@Order The smaller the value, the earlier the execution), to ensure that the AOP is executed before @Transactional.

package com.demo.multidatasource.aop;

import com.demo.multidatasource.annotation.DataSourceAnnotation;
import com.demo.multidatasource.util.DataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


import java.lang.reflect.Method;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multidatasource.aop.DataSourceAspect
 * @Author: Mr.Wang
 * @Create: 2022/11/1 9:53
 * @Version 1.0
 * @Description:
 */
@Aspect
@Component
@Order(-1)
public class DataSourceAspect {
    
    
    @Pointcut("@annotation(com.demo.multidatasource.annotation.DataSourceAnnotation)")
    public void dataSourcePointCut() {
    
    

    }

    @Around("dataSourcePointCut()")
    public Object dataSourceArround(ProceedingJoinPoint proceed) throws Throwable {
    
    
        MethodSignature methodSignature = (MethodSignature) proceed.getSignature();
        Method method = methodSignature.getMethod();
        System.out.println("method是"+method);
        DataSourceAnnotation dataSourceAnnotation = method.getAnnotation(DataSourceAnnotation.class);
        if(dataSourceAnnotation != null) {
    
    
            System.out.println("设置的是"+dataSourceAnnotation.value().name());
            DataSourceContextHolder.setDataSource(dataSourceAnnotation.value().name());
        }

        try {
    
    
            return proceed.proceed();
        } finally {
    
    
            // 方法执行后销毁数据源
            DataSourceContextHolder.clearDataSource();
        }
    }
}

2.6.8 Write three layers of code

The dao layer implementation class uses JdbcTemplate query. Patients with obsessive-compulsive disorder will List转为了List<实体>。

package com.demo.multidatasource.dao.ds1.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import com.demo.multidatasource.dao.ds1.StudentDao;
import com.demo.multidatasource.entity.Student;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multidatasource.dao.ds1.impl.StudentDaoImpl
 * @Author: Mr.Wang
 * @Create: 2022/11/1 10:10
 * @Version 1.0
 * @Description:
 */
@Repository
public class StudentDaoImpl implements StudentDao {
    
    
    @Resource
    JdbcTemplate jdbcTemplate;

    @Override
    public List<Student> selectAllStudent() {
    
    
        String sql="select * from student";
        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
        ArrayList<Student> studentList = new ArrayList<>();
        for (Map<String, Object> map : list) {
    
    
            Student student = Convert.convert(new TypeReference<Student>() {
    
    },map);
            studentList.add(student);
        }
        return studentList;
    }
}

package com.demo.multidatasource.dao.ds2.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import com.demo.multidatasource.dao.ds2.TeacherDao;
import com.demo.multidatasource.entity.Teacher;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Program: IntelliJ IDEA
 * @ClassName: com.demo.multidatasource.dao.ds2.impl.TeacherDaoImpl
 * @Author: Mr.Wang
 * @Create: 2022/11/1 10:19
 * @Version 1.0
 * @Description:
 */
@Repository
public class TeacherDaoImpl implements TeacherDao {
    
    
    @Resource
    JdbcTemplate jdbcTemplate;

    @Override
    public List<Teacher> selectAllTeacher() {
    
    
        String sql="select * from teacher";
        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
        ArrayList<Teacher> teacherList = new ArrayList<>();
        for (Map<String, Object> map : list) {
    
    
            Teacher teacher = Convert.convert(new TypeReference<Teacher>() {
    
    },map);
            teacherList.add(teacher);
        }
        return teacherList;
    }
}


The rest of the code is the same as the integration code of mybatis subcontracting method, and will not be repeated.

2.6.9 Remove the DataSource automatic configuration class

Remove the DataSource automatic configuration class in the @SpringBootApplication annotation of the startup class, otherwise it will be automatically configured by default instead of using our custom DataSource, and there will be a circular dependency error at startup.

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

2.7 Custom Annotation Method Test

Enter the address respectively:
http://localhost:8003/student/selectAllStudent
http://localhost:8003/teacher/selectAllTeacher
to visit, all visits are successful, indicating that the custom annotation method realizes the switching of multiple data sources.
insert image description here
insert image description here
insert image description here

Guess you like

Origin blog.csdn.net/weixin_44834205/article/details/127635253