Dynamic switching of multiple database sources in SpringBoot project

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 primaryandsecondary

  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"));

Guess you like

Origin blog.csdn.net/DespairC/article/details/124923027