1.SpringBoot多环境配置

1.1 前言

对于SpringBoot工程,在不同环境(例如dev,test,prod、uat等)可能有不同的配置信息(配置在application.yml或application.properties中),例如swagger.enable这个变量,在dev和test环境值为true,在prod环境的值为false。
在SpringBoot中,有两种方式可以实现多环境配置文件:
一种是直接在一个配置文件中配置多个环境的配置信息(即多文档块,通过—分割),这种仅支持application.yml文件;
一种是一个主配置文件(application.yml或application.properties)和多个环境配置文件(application-dev.yml,application-test.yml,application-prod.yml,application-uat.yml等)。

1.2 单个配置实现多环境配置

在application.yml文件中配置,不同环境相同的配置信息可以配置在顶层文档块,不同环境不同配置信息配置在不同环境文档块中。通过spring.profiles.active变量可以指定在不同环境使用哪个文档块的配置。

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 多个配置文件形式

这种形式就是将单个文件的配置写到了多个文件中,看起来更清晰明了简洁(个人愚见)。

这里有需要注意的一个点就是:在Spring Boot 2.4及以上版本中,关于spring.profiles.active配置项都被划上一道线,也就是说,Spring Boot中对多环境支持的配置项spring.profiles.active已经被废弃。因此,这里采用了最新配置方式spring.config.activate.on-profile。
Spring Boot之所以进行大范围的改动,最主要的动机有两个,一个是对Kubernetes的兼容支持,一个是修复因ConfigFileApplicationListener类导致的文件处理问题。因此,在文件的加载方式上发生了两个重大变化:文档将按定义的顺序加载、profiles激活开关不能被配置在特定环境中。

创建一个主配置文件,和不同环境的子配置文件,如下图:
在这里插入图片描述
其中,application.yml主配置文件如下:

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

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

dev环境:application-dev.yml

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

test环境:application-test.yml

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

uat环境:application-uat.yml

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

prod环境:application-prod.yml

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

1.4 激活配置文件方式

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 配置文件加载顺序

springBoot启动会扫描读取以下位置中的配置文件,优先级由高到低依次是:
-file:./config/,即当前项目下的config文件夹(src同级目录)
-file:./,即当前项目下
-classpath:./config/,即当前项目中resources资源文件夹下的config文件夹
-classpath:./,即当前项目中resources资源文件夹下

2.SpringBoot多数据源配置

2.1 什么是数据源?

数据源(Data Source)顾名思义,数据的来源,是提供某种所需要数据的器件或原始媒体。在数据源中存储了所有建立数据库连接的信息。就像通过指定文件名称可以在文件系统中找到文件一样,通过提供正确的数据源名称,你可以找到相应的数据库连接。简单来说,数据源就是指数据库应用程序所使用的数据库或者数据库服务器。
多数据源可以理解为多数据库,甚至可以是多个不同类型的数据库,比如一个是MySql,一个是Oracle。随着项目的扩大,有时需要数据库的拆分或者引入另一个数据库,这时就需要配置多个数据源。

2.2 数据准备

SpringBoot中使用多数据源还是比较简单的,为了演示方便,我们在MySql中创建两个数据库:ds1、ds2,并在ds1数据库中创建student表,在ds2数据库中创建teacher表。数据库脚本如下:

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使用分包方式整合

2.3.1 项目结构图

项目结构图如下:
请添加图片描述

2.3.2 数据库连接配置

既然是多数据源,数据库连接的信息就有可能存在不同,所以需要在配置文件中配置多个数据源的连接信息,这里以druid数据库连接池为例。

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 重写SpringBoot的数据源配置

这里和单数据源不同的地方在于对 dataSource 、 sqlSessionFactory 、 sqlSessionTemplate、 transactionManager 都进行了单独的配置。另外,数据源1和数据源2主要存在两点不同:
1.@MapperScan 中的包扫描路径不一样,数据源1只扫描 com.demo.multipledatasource.dao.ds1路径下的 Mapper ,数据源2只扫描com.demo.multipledatasource.dao.ds2路径下的Mapper,因此,在前面创建的时候我们要把 StudentMapper 和 TeacherMapper 分开。此外,由于已经在配置类中配置了 @MapperScan ,所以在启动类中必须不能在存在 @MapperScan 注解
2.数据源1中多一个 @Primary 注解,这是告诉Spring我们使用的默认数据源,也是多数据源项目中必不可少的。

  • 数据源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);
    }
}

  • 数据源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 编写三层代码

编写相应的Controller和Service层代码,查询所有的Student和Teacher信息,完事直接打开浏览器进行测试。当然,也可以采用swagger、postman、apifox等进行测试。
相应的代码如下:
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分包方式整合测试

分别输入地址:
http://localhost:8083/student/selectAllStudent
http://localhost:8083/teacher/selectAllTeacher
进行访问,均访问成功,说明MyBatis自动帮我们切换到了对应的数据源上。。
在这里插入图片描述
在这里插入图片描述

2.6 自定义注解方式实现整合

上面我们提高到数据源自动切换主要依靠MyBatis,如果项目中没有使用MyBatis该如何做呢?

这里介绍一种基于自定义注解的方法实现多数据源的动态切换。SpringBoot中有一个 AbstractRoutingDataSource 抽象类,我们可以实现其抽象方法 determineCurrentLookupKey() 去指定数据源。并通过AOP编写自定义注解处理类,在sql语句执行前,切换到自定义注解中设置的数据源以实现数据源的自动切换。

2.6.1 配置两个数据库连接信息

配置信息同2.3,这里不再重复贴代码。

2.6.2 创建数据源存放类

DataSource 是和线程绑在一起的,因此,我们需要一个线程安全的类来存放 DataSource ,在determineCurrentLookupKey() 中通过该类获取数据源。
在AbstractRoutingDataSource 类中, DataSource 以键值对的形式保存,可以使用 ThreadLocal 来保存key,从而实现多数据源的自动切换

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 创建数据源枚举类

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 创建动态数据源类

DynamicDataSource类继承AbstractRoutingDataSource类,重写 determineCurrentLookupKey 方法指定数据源。

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 创建数据源配置类

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 创建自定义注解

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 创建AOP切面类

通过AOP在执行sql语句前拦截,并切换到自定义注解指定的数据源上。有一点需要注意,自定义数据源注解与 @Transaction 注解同一个方法时会先执行 @Transaction ,即获取数据源在切换数据源之前,所以会导致自定义注解失效,因此需要使用 @Order (@Order的value越小,就越先执行),保证该AOP在 @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 编写三层代码

dao层实现类借助了JdbcTemplate查询。强迫症患者又将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;
    }
}


其余代码同mybatis分包方式整合代码一样,不再重复贴。

2.6.9 移除DataSource自动配置类

在启动类的 @SpringBootApplication 注解中移除DataSource自动配置类,否则会默认自动配置,而不会使用我们自定义的DataSource,并且启动会有循环依赖的错误。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

2.7 自定义注解方式测试

分别输入地址:
http://localhost:8003/student/selectAllStudent
http://localhost:8003/teacher/selectAllTeacher
进行访问,均访问成功,说明自定义注解方式实现了多数据源的切换。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐