详细聊聊SpringBoot中动态切换数据源的方法

 更新时间:2021年09月29日 12:58:09   作者:Nicksxs  
在大型分布式项目中,经常会出现多数据源的情况,下面这篇文章主要给大家介绍了关于SpringBoot中动态切换数据源的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下

其实这个表示有点不太对,应该是 Druid 动态切换数据源的方法,只是应用在了 springboot 框架中,准备代码准备了半天,之前在一次数据库迁移中使用了,发现 Druid 还是很强大的,用来做动态数据源切换很方便。

首先这里的场景跟我原来用的有点点区别,在项目中使用的是通过配置中心控制数据源切换,统一切换,而这里的例子多加了个可以根据接口注解配置

第一部分是最核心的,如何基于 Spring JDBC 和 Druid 来实现数据源切换,是继承了org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 这个类,他的determineCurrentLookupKey方法会被调用来获得用来决定选择那个数据源的对象,也就是 lookupKey,也可以通过这个类看到就是通过这个 lookupKey 来路由找到数据源。

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        if (DatabaseContextHolder.getDatabaseType() != null) {
            return DatabaseContextHolder.getDatabaseType().getName();
        }
        return DatabaseType.MASTER1.getName();
    }
}

而如何使用这个 lookupKey 呢,就涉及到我们的 DataSource 配置了,原来就是我们可以直接通过spring 的 jdbc 配置数据源,像这样

现在我们要使用 Druid 作为数据源了,然后配置 DynamicDataSource的参数,通过 key 来选择对应的 DataSource,也就是下面配的 master1 和 master2

<bean id="master1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close"
          p:driverClassName="com.mysql.cj.jdbc.Driver"
          p:url="${master1.demo.datasource.url}"
          p:username="${master1.demo.datasource.username}"
          p:password="${master1.demo.datasource.password}"
          p:initialSize="5"
          p:minIdle="1"
          p:maxActive="10"
          p:maxWait="60000"
          p:timeBetweenEvictionRunsMillis="60000"
          p:minEvictableIdleTimeMillis="300000"
          p:validationQuery="SELECT 'x'"
          p:testWhileIdle="true"
          p:testOnBorrow="false"
          p:testOnReturn="false"
          p:poolPreparedStatements="false"
          p:maxPoolPreparedStatementPerConnectionSize="20"
          p:connectionProperties="config.decrypt=true"
          p:filters="stat,config"/>

    <bean id="master2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close"
          p:driverClassName="com.mysql.cj.jdbc.Driver"
          p:url="${master2.demo.datasource.url}"
          p:username="${master2.demo.datasource.username}"
          p:password="${master2.demo.datasource.password}"
          p:initialSize="5"
          p:minIdle="1"
          p:maxActive="10"
          p:maxWait="60000"
          p:timeBetweenEvictionRunsMillis="60000"
          p:minEvictableIdleTimeMillis="300000"
          p:validationQuery="SELECT 'x'"
          p:testWhileIdle="true"
          p:testOnBorrow="false"
          p:testOnReturn="false"
          p:poolPreparedStatements="false"
          p:maxPoolPreparedStatementPerConnectionSize="20"
          p:connectionProperties="config.decrypt=true"
          p:filters="stat,config"/>

    <bean id="dataSource" class="com.nicksxs.springdemo.config.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- master -->
                <entry key="master1" value-ref="master1"/>
                <!-- slave -->
                <entry key="master2" value-ref="master2"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="master1"/>
    </bean>

现在就要回到头上,介绍下这个DatabaseContextHolder,这里使用了 ThreadLocal 存放这个 DatabaseType,为啥要用这个是因为前面说的我们想要让接口层面去配置不同的数据源,要把持相互隔离不受影响,就使用了 ThreadLocal,关于它也可以看我前面写的一篇文章聊聊传说中的 ThreadLocal,而 DatabaseType 就是个简单的枚举

public class DatabaseContextHolder {
    public static final ThreadLocal<DatabaseType> databaseTypeThreadLocal = new ThreadLocal<>();

    public static DatabaseType getDatabaseType() {
        return databaseTypeThreadLocal.get();
    }

    public static void putDatabaseType(DatabaseType databaseType) {
        databaseTypeThreadLocal.set(databaseType);
    }

    public static void clearDatabaseType() {
        databaseTypeThreadLocal.remove();
    }
}
public enum DatabaseType {
    MASTER1("master1", "1"),
    MASTER2("master2", "2");

    private final String name;
    private final String value;

    DatabaseType(String name, String value) {
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public String getValue() {
        return value;
    }

    public static DatabaseType getDatabaseType(String name) {
        if (MASTER2.name.equals(name)) {
            return MASTER2;
        }
        return MASTER1;
    }
}

这边可以看到就是通过动态地通过putDatabaseType设置lookupKey来进行数据源切换,要通过接口注解配置来进行设置的话,我们就需要一个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    String value();
}

这个注解可以配置在我的接口方法上,比如这样

public interface StudentService {

    @DataSource("master1")
    public Student queryOne();

    @DataSource("master2")
    public Student queryAnother();

}

通过切面来进行数据源的设置

@Aspect
@Component
@Order(-1)
public class DataSourceAspect {

    @Pointcut("execution(* com.nicksxs.springdemo.service..*.*(..))")
    public void pointCut() {

    }


    @Before("pointCut()")
    public void before(JoinPoint point)
    {
        Object target = point.getTarget();
        System.out.println(target.toString());
        String method = point.getSignature().getName();
        System.out.println(method);
        Class<?>[] classz = target.getClass().getInterfaces();
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
                .getMethod().getParameterTypes();
        try {
            Method m = classz[0].getMethod(method, parameterTypes);
            System.out.println("method"+ m.getName());
            if (m.isAnnotationPresent(DataSource.class)) {
                DataSource data = m.getAnnotation(DataSource.class);
                System.out.println("dataSource:"+data.value());
                DatabaseContextHolder.putDatabaseType(DatabaseType.getDatabaseType(data.value()));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @After("pointCut()")
    public void after() {
    DatabaseContextHolder.clearDatabaseType();
    }
}

通过接口判断是否带有注解跟是注解的值,DatabaseType 的配置不太好,不过先忽略了,然后在切点后进行清理

这是我 master1 的数据,

master2 的数据

然后跑一下简单的 demo,

@Override
public void run(String...args) {
 LOGGER.info("run here");
 System.out.println(studentService.queryOne());
 System.out.println(studentService.queryAnother());

}

看一下运行结果

其实这个方法应用场景不止可以用来迁移数据库,还能实现精细化的读写数据源分离之类的,算是做个简单记录和分享。

总结

到此这篇关于SpringBoot中动态切换数据源的文章就介绍到这了,更多相关SpringBoot动态切换数据源内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot实现Word转PDF和TXT的实践分享

    SpringBoot实现Word转PDF和TXT的实践分享

    研发工作中难免会遇到一些奇奇怪怪的需求,就比如最近,客户提了个新需求:上传一个WORD文档,要求通过系统把该文档转换成PDF和TXT,所以本文给大家分享了SpringBoot实现Word转PDF和TXT的实践,感兴趣的朋友可以参考下
    2024-08-08
  • 详解Java如何优雅的处理异常

    详解Java如何优雅的处理异常

    在编写 Java 程序的过程中,有一种异常几乎每个开发者都会遇到——空指针异常(NullPointerException),那么我们应该如何有效且优雅的处理空指针异常呢,下面小编就来详细介绍这个处理方案吧
    2023-08-08
  • SpringBoot如何通过Map实现策略模式

    SpringBoot如何通过Map实现策略模式

    策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这篇文章主要介绍了SpringBoot如何通过Map实现策略模式,需要的朋友可以参考下
    2025-03-03
  • Java必须学会的类的继承与多态

    Java必须学会的类的继承与多态

    这篇文章主要介绍了Java类的继承与多态的相关资料,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-08-08
  • 深入了解java中的逃逸分析

    深入了解java中的逃逸分析

    这篇文章主要介绍了深入了解java中的逃逸分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Jeecg-Boot异常处理'jeecg-boot.QRTZ_LOCKS' doesn't exist问题

    Jeecg-Boot异常处理'jeecg-boot.QRTZ_LOCKS' doesn'

    这篇文章主要介绍了Jeecg-Boot异常处理'jeecg-boot.QRTZ_LOCKS' doesn't exist问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 百度翻译API使用详细教程(前端vue+后端springboot)

    百度翻译API使用详细教程(前端vue+后端springboot)

    这篇文章主要给大家介绍了关于百度翻译API使用的相关资料,百度翻译API是百度面向开发者推出的免费翻译服务开放接口,任何第三方应用或网站都可以通过使用百度翻译API为用户提供实时优质的多语言翻译服务,需要的朋友可以参考下
    2024-02-02
  • Java远程debug调试的操作步骤

    Java远程debug调试的操作步骤

    日常项目中,通常我们的代码都是部署到远程的服务器,有时线上出现了问题,经常需要我们增加一些日志来排查问题,但是如果是测试环境的场景下或者我们能进行远程调试的话,我们就可以使用远程的Debug方式进行远程调试,下面给大家介绍一下Java远程debug调试的操作步骤
    2025-04-04
  • Java类加载器层次结构原理解析

    Java类加载器层次结构原理解析

    这篇文章主要介绍了Java类加载器层次结构原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • SpringMVC深入讲解文件的上传下载实现

    SpringMVC深入讲解文件的上传下载实现

    这篇文章主要为大家详细介绍了springMVC实现文件上传和下载的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06

最新评论