Spring Boot 中的多环境配置、多数据源配置方案
Spring Boot 中的多环境配置、多数据源配置方案
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
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
进行访问,均访问成功,说明自定义注解方式实现了多数据源的切换。
更多推荐
所有评论(0)