小工具知识网

当前位置: 首页 > 行业知识 > springBoot如何动态切换数据源

行业知识

springBoot如何动态切换数据源

2026-11-09 08:21:26 来源:互联网转载

项目背景:最近公司中需要搭建mysql的主从,想着在spring中集成多数据源。mybatisplus提供的有插件用@DS注解就能够实现,但是这种在mysql服务宕机的情况下不能够进行自动切换,于是就想着用aop+自定义注解的方式来实现

项目实现效果:如果公司服务器搭建的是一主多从多个mysql数据源,主服务器用来读。从服务器用来写。此时你在代码层面用注解指定了一个增删改方法到从数据源,但是碰巧此时从数据源失效了,那么就会自动的切换到其它服务器。代码实现如下:

注意:为了节省篇幅,向controller、service层就不展示出来了,只展示相关核心代码。

1、pom文件

           org.springframework.boot           spring-boot-starter-web                                 org.springframework.boot           spring-boot-starter-aop                          org.springframework.boot           spring-boot-starter-jdbc                           org.projectlombok           lombok           true                         org.springframework.boot           spring-boot-starter-test           test                                       com.alibaba           druid-spring-boot-starter           1.2.8                                com.baomidou           dynamic-datasource-spring-boot-starter           2.4.2                         mysql           mysql-connector-java           8.0.21                         com.baomidou           mybatis-plus-boot-starter           3.2.0                                com.baomidou           mybatis-plus-generator           3.3.2                         commons-lang           commons-lang           2.6                                 org.apache.velocity           velocity-engine-core           2.0                          log4j           log4j           1.2.14       

2、配置文件:application.yml

server:  port: 8088 spring:  datasource:    druid:      type: com.alibaba.druid.pool.DruidDataSource      master:        url: jdbc:mysql://192.168.26.4:3306/test01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai        username: root        password: root        driver-class-name: com.mysql.jdbc.Driver      slave:        url: jdbc:mysql://192.168.26.8:3306/test01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai        username: root        password: root        driver-class-name: com.mysql.jdbc.Driver

3、数据源名称枚举类CommonConstant:

public class CommonConstant {    /**     * 默认数据源标识     */    public static final String MASTER = "master";    /**     * 从数据源标识     */    public static final String SLAVE = "slave";}

4 数据源解析类DruidConfig:

@Data@Configurationpublic class DruidConfig {      @Bean(name = CommonConstant.MASTER)    @ConfigurationProperties("spring.datasource.druid.master")    public DataSource masterDataSource()    {        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();        return dataSource;    }      @Bean(name = CommonConstant.SLAVE)    @ConfigurationProperties("spring.datasource.druid.slave")    public DataSource slaveDataSource()    {        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();        return dataSource;    }      @Bean    @Primary    public DynamicDataSource dynamicDataSource()    {        Map dataSourceMap = new HashMap(2);        dataSourceMap.put(CommonConstant.MASTER,masterDataSource());        dataSourceMap.put(CommonConstant.SLAVE,slaveDataSource());        //设置动态数据源        DynamicDataSource dynamicDataSource = new DynamicDataSource();        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());        dynamicDataSource.setTargetDataSources(dataSourceMap);        //将数据源信息备份在defineTargetDataSources中        dynamicDataSource.setDefineTargetDataSources(dataSourceMap);        return dynamicDataSource;    }}

5、DynamicDataSource类

编写DynamicDataSource类继承AbstractRoutingDataSource类并重写抽象方法determineCurrentLookupKey以此来决定当前线程使用哪个数据源

/** * 动态数据源 * 调用AddDefineDataSource组件的addDefineDynamicDataSource()方法,获取原来targetdatasources的map,并将新的数据源信息添加到map中,并替换targetdatasources中的map * 切换数据源时可以使用@DataSource(value = "数据源名称"),或者DynamicDataSourceContextHolder.setContextKey("数据源名称") * @author zhangyu */@Data@AllArgsConstructor@NoArgsConstructorpublic class DynamicDataSource extends AbstractRoutingDataSource {     //备份所有数据源信息,    private Map defineTargetDataSources;      /**     * 决定当前线程使用哪个数据源     */    @Override    protected Object determineCurrentLookupKey() {        return DynamicDataSourceHolder.getDynamicDataSourceKey();    } }

6、DynamicDataSourceHolder

DynamicDataSourceHolder类主要是设置当前线程的数据源名称,移除数据源名称,以及获取当前数据源的名称,便于动态切换

/** * 数据源切换处理 * * @author zhangyu */@Slf4jpublic class DynamicDataSourceHolder {    /**     * 保存动态数据源名称     */    private static final ThreadLocal DYNAMIC_DATASOURCE_KEY = new ThreadLocal();      /**     * 设置/切换数据源,决定当前线程使用哪个数据源     */    public static void setDynamicDataSourceKey(String key){        log.info("数据源切换为:{}",key);        DYNAMIC_DATASOURCE_KEY.set(key);    }      /**     * 获取动态数据源名称,默认使用mater数据源     */    public static String getDynamicDataSourceKey(){        String key = DYNAMIC_DATASOURCE_KEY.get();        return key == null ? CommonConstant.MASTER : key;    }      /**     * 移除当前数据源     */    public static void removeDynamicDataSourceKey(){        log.info("移除数据源:{}",DYNAMIC_DATASOURCE_KEY.get());        DYNAMIC_DATASOURCE_KEY.remove();    }}

7、自定义注解

@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface DataSource{    /**     * 切换数据源名称     */    public String value() default CommonConstant.MASTER;}

8 aop切面

import com.alibaba.druid.pool.DruidDataSource;import com.liubujun.config.*;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.stereotype.Component; import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.*; @Aspect@Component@Slf4jpublic class DataSourceAspect {      // 设置DataSource注解的切点表达式//    @Pointcut("@annotation(com.liubujun.config.aespect.DataSource)")    @Pointcut("execution(public * com.liubujun.service..*.*(..))")    public void dynamicDataSourcePointCut(){      }      //环绕通知    @Around("dynamicDataSourcePointCut()")    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {        DataSource defineAnnotation = getDefineAnnotation(joinPoint);        String key = "";        //判断方法上是否有注解,没有注解则默认是走的是主服务器        if (defineAnnotation == null ) {            key = CommonConstant.MASTER;        }else {            key = defineAnnotation.value();        }        //判断数据库是否断开连接        key = getConnection(key);        DynamicDataSourceHolder.setDynamicDataSourceKey(key);        Object proceed = null;        try {            proceed =  joinPoint.proceed();        } finally {            DynamicDataSourceHolder.removeDynamicDataSourceKey();        }        return proceed;    }     /**     * 先判断方法的注解,后判断类的注解,以方法的注解为准     * @param joinPoint     * @return     */    private DataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();        DataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(DataSource.class);        if (Objects.nonNull(methodSignature)) {            return dataSourceAnnotation;        } else {            Class dsClass = joinPoint.getTarget().getClass();            return dsClass.getAnnotation(DataSource.class);        }    }     /**     * 判断数据库是否连接成功     * @return     */    private String getConnection(String target) throws SQLException {        //将数据源名称添加到list集合,方便后续操作        List dataSources = new ArrayList();        dataSources.add(CommonConstant.SLAVE);        dataSources.add(CommonConstant.MASTER);        //获取装配好的bean对象        DruidConfig druidConfig = (DruidConfig)SpringUtil.getBean("druidConfig");        DruidDataSource druidDataSource = new DruidDataSource();        if (target.equals(CommonConstant.SLAVE)) {             druidDataSource = (DruidDataSource) druidConfig.slaveDataSource();        }        if (target.equals(CommonConstant.MASTER)) {            druidDataSource = (DruidDataSource) druidConfig.masterDataSource();        }        try {            Connection connection = DriverManager.getConnection(druidDataSource.getUrl(), druidDataSource.getUsername(), druidDataSource.getPassword());        } catch (SQLException e) {            dataSources.remove(target);            // shuffle 打乱顺序            Collections.shuffle(dataSources);            String changeTarget = dataSources.get(0);            getConnection(changeTarget);            log.info("========================数据源:{}连接异常,切换数据源为:{}===========================",target,changeTarget);            return changeTarget;        }        return target;    }  }

9 获取bean对象工具类

@Componentpublic class SpringUtil implements ApplicationContextAware {      private static ApplicationContext applicationContext;      @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        if(SpringUtil.applicationContext == null) {            SpringUtil.applicationContext = applicationContext;        }    }      public static ApplicationContext getApplicationContext() {        return applicationContext;    }     //根据类名获取指定对象    public static Object getBean(String name){        return getApplicationContext().getBean(name);    }     //根据类型获取指定对象    public static  T getBean(Class clazz){        return getApplicationContext().getBean(clazz);    }     //根据类名和类型获取指定对象    public static  T getBean(String name,Class clazz){        return getApplicationContext().getBean(name, clazz);    }}

可以直接在业务service的实现层直接在方法上添加注解指定数据源,如:

我在这个方法上指定的是从数据库,如果此时从数据库发生宕机,那么就会自动切换到主数据库进行操作 

上一篇:PHP源码高效打包秘籍,轻松提升性能与效率!

下一篇:kook服务器助力怎么续?