Article directory
accomplish
1. Import dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 切面编程 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
2. Configuration file + data source storage
The configuration file needs to configure multiple data sources, specified by name, here use primary
andsecondary
datasource:
primary:
jdbc-url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowMultiQueries=true
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://localhost:3306/test2?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowMultiQueries=true
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
Write a ThreadLocal to store the data source in use, which means that it needs to be reset and cleared before and after the method is used to achieve thread isolation
package com.example.demo.test.config;
public class DataSourceType {
// 内部枚举类,用于选择特定的数据类型
public enum DataBaseType{
Primary, Secondary
}
// 使用 ThreadLocal 保证线程安全(隔离)
private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<>();
// 往当前线程里设置数据源类型
public static void setDataBaseType(DataBaseType dataBaseType){
if(dataBaseType == null){
throw new NullPointerException();
}
TYPE.set(dataBaseType);
}
// 获取数据类型
public static DataBaseType getDataBaseType(){
DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get();
return dataBaseType;
}
// 清空数据类型
public static void clearDataBaseType(){
TYPE.remove();
}
}
3. Implement DynamicDataSource
Inherit AbstractRoutingDataSource, rewrite the method, and provide the data source
package com.example.demo.test.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
return dataBaseType;
}
}
4. Implement AOP and dynamic data source configuration classes
First create a dynamic data source configuration class
package com.example.demo.test.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@MapperScan(basePackages = "com.example.demo.**.mapper", sqlSessionFactoryRef = "sqlSessionFactory") // basePackage 接口地址
public class DynamicDataSourceConfig {
// 将这个对象放入Spring容器
@Bean(name = "PrimaryDataSource")
// 表示这个是默认数据源
@Primary
// 读取配置文件的参数,映射为一个对象
// prefix 表示参数的前缀
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource getDataSource(){
return DataSourceBuilder.create().build();
}
@Bean(name = "SecondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource getDataSource1(){
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource,
@Qualifier("SecondaryDataSource") DataSource secondaryDataSource){
// targetDataSource 集合是我们数据库和名字之间的映射
Map<Object, Object> targetDataSource = new HashMap<>();
// 设置两个数据库
targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource);
targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
// 动态切换源
dataSource.setTargetDataSources(targetDataSource);
// 默认是第一个数据源
dataSource.setDefaultTargetDataSource(primaryDataSource);
return dataSource;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/**Mapper.xml"));
return bean.getObject();
}
}
After configuration, create custom annotations to facilitate injection at any time and dynamically switch databases.
package com.example.demo.test.config;
import java.lang.annotation.*;
// 可以在方法,参数,类或接口声明
@Target({
ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
// 注释
@Documented
public @interface DataSource {
// 默认使用的数据库
String value() default "primary";
}
Perform an AOP aspect on the marked annotation, get the value, store it in ThreadLocal, and limit the data source of this thread
package com.example.demo.test.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
// 拦截注释
@Before("@annotation(dataSource)")
public void changeDataSource(JoinPoint point, DataSource dataSource){
// 通过注解 获取值(也就是数据源)
String value = dataSource.value();
if(value.equals("primary")){
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
}
else if(value.equals("secondary")){
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
}
// 如果没有值,默认使用主数据库
else{
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
}
}
@After("@annotation(dataSource)")
public void restoreDataSoucer(JoinPoint point, DataSource dataSource){
// 清空存在的数据源
DataSourceType.clearDataBaseType();
}
}
5. Entity class and Mapper
Create an entity class
package com.example.demo.test.pojo;
import lombok.Data;
@Data
public class Person {
private int id;
private String name;
}
Create the corresponding Mapper
package com.example.demo.test.mapper;
import com.example.demo.test.config.DataSource;
import com.example.demo.test.pojo.Person;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface PersonMapper {
// 使用数据源二
@DataSource("secondary")
List<Person> query();
}
Create PersonMapper.xml (/resouce/mapper/PersonMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.test.mapper.PersonMapper">
<select id="query" resultType="com.example.demo.test.pojo.Person">
select * from Person
</select>
</mapper>
6. Unit testing
package com.example.demo;
import com.example.demo.test.mapper.PersonMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class TestPersonMapper {
@Autowired
PersonMapper personMapper;
@Test
public void test(){
personMapper.query().forEach(System.out::println);
}
}
Result comparison:
// 项目输出
Person(id=1, name=小黄)
Person(id=2, name=小花)
// 数据库
id name
1 小黄
2 小花
question
The corresponding mapper cannot be found in the configuration file
Solution 1: Add in pom.xml
<builder>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<!-- 把 java目录下的 xml文件编译-->
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</builder>
Solution 2: There may be typos in the configuration file (requires s)
mapper-location: classpath:mapper/**/**Mapper.xml #错误的写法
mapper-locations: classpath:mapper/**/**Mapper.xml #正确的写法
Solution 3: Wrong writing during dynamic configuration
// 错误写法(和方案 2 一样,也是因为少了 s )
....getResouce("classpath:mapper/**/**Mapper.xml")
// 正确写法
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/**Mapper.xml"));