栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot项目多数据库源动态切换

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot项目多数据库源动态切换

文章目录
    • 实现
      • 1、导入依赖
      • 2、配置文件 + 数据源储存
      • 3、实现 DynamicDataSource
      • 4、实现 AOP 和 动态数据源配置类
      • 5、实体类 和 Mapper
      • 6、单元测试
    • 问题
      • 配置文件中找不到相对应的mapper

实现 1、导入依赖

    org.springframework.boot
    spring-boot-starter-jdbc


    mysql
    mysql-connector-java
    runtime




    org.springframework.boot
    spring-boot-starter-test
    test




    org.springframework
    spring-aop
    5.1.5.RELEASE


    org.aspectj
    aspectjweaver
    1.9.2

2、配置文件 + 数据源储存

配置文件需要配置多个数据源,用名称指定,这里使用primary 和 secondary

  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

写一个 ThreadLocal 储存正在使用的数据源,也就意味着在方法使用前后需要重置清空,做到线程隔离的作用

package com.example.demo.test.config;

public class DataSourceType {
    // 内部枚举类,用于选择特定的数据类型
    public enum DataBaseType{
        Primary, Secondary
    }

    // 使用 ThreadLocal 保证线程安全(隔离)
    private static final ThreadLocal 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、实现 DynamicDataSource

继承 AbstractRoutingDataSource ,重写方法,提供数据源

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、实现 AOP 和 动态数据源配置类

首先创建动态数据源配置类

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 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();
    }
}

配置好了之后,创建自定义注解,以便于随时注入,动态切换数据库。

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

对标记了注解进行 AOP 切面,获取value,将其储存到 ThreadLocal,限定本线程的数据源

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、实体类 和 Mapper

创建一个实体类

package com.example.demo.test.pojo;
import lombok.Data;

@Data
public class Person {
    private int id;
    private String name;
}

创建相应的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 query();
}

创建PersonMapper.xml(/resouce/mapper/PersonMapper.xml)







    
        select * from Person
    

6、单元测试
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);
    }
}

结果比对:

// 项目输出
Person(id=1, name=小黄)
Person(id=2, name=小花)

// 数据库
id   name
1	 小黄
2	 小花
问题 配置文件中找不到相对应的mapper

解决方案1:在pom.xml中添加


        
            
                src/main/java
                
                    
                    ***.*
                
            
            
                src/main/webapp
                META-INF/resources
                
                    ****Mapper.xml		#错误的写法
  mapper-locations: classpath:mapper**Mapper.xml	#正确的写法

解决方案3:动态配置的时候写错

// 错误写法(和方案 2 一样,也是因为少了 s )
....getResouce("classpath:mapper**Mapper.xml")
// 正确写法
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper**Mapper.xml"));
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/926726.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号