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:
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:
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. .
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
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.