Springboot动态数据源整合详解

本文详细介绍了如何在SpringBoot中实现动态数据源的配置,包括自定义配置文件、配置属性类的使用、配置多个数据源、动态数据库配置、原理分析以及注解的调用方式,以实现通过注解轻松切换数据源。

 

 

使用动态数据源可以实现读写分离,然后通过注解的形式只需在方法上加注解就可以实现动态切换数据源

1.通过自定义配置文件配置连接数据库的信息。配置数据如下

spring.dataSource.db1.type=com.alibaba.druid.pool.DruidDataSource
spring.dataSource.db1.driverClassName=com.mysql.jdbc.Driver
spring.dataSource.db1.driver-class-name=com.mysql.jdbc.Driver
spring.dataSource.db1.platform=mysql
spring.dataSource.db1.url=jdbc:mysql://localhost:3306/sqlpractice?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.dataSource.db1.username=root
spring.dataSource.db1.password=admin123
spring.dataSource.db1.initialSize=5
spring.dataSource.db1.minIdle=5
spring.dataSource.db1.maxActive=20
spring.dataSource.db1.maxWait=60000
spring.dataSource.db1.timeBetweenEvictionRunsMillis=60000
spring.dataSource.db1.minEvictableIdleTimeMillis=300000
spring.dataSource.db1.validationQuery=SELECT 1
spring.dataSource.db1.testWhileIdle=true
spring.dataSource.db1.testOnBorrow=false
spring.dataSource.db1.testOnReturn=false
spring.dataSource.db1.filters=stat,wall,log4j
spring.dataSource.db1.logSlowSql=true


spring.dataSource.db2.type=com.alibaba.druid.pool.DruidDataSource
spring.dataSource.db2.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.dataSource.db2.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.dataSource.db2.platform=sqlserver
spring.dataSource.db2.url=jdbc:sqlserver://127.0.0.1:1433;databaseName=TEST
spring.dataSource.db2.username=sa
spring.dataSource.db2.password=lyw123456
spring.dataSource.db2.initialSize=5
spring.dataSource.db2.minIdle=5
spring.dataSource.db2.maxActive=20
spring.dataSource.db2.maxWait=60000
spring.dataSource.db2.timeBetweenEvictionRunsMillis=60000
spring.dataSource.db2.minEvictableIdleTimeMillis=300000
spring.dataSource.db2.validationQuery=SELECT 1
spring.dataSource.db2.testWhileIdle=true
spring.dataSource.db2.testOnBorrow=false
spring.dataSource.db2.testOnReturn=false
spring.dataSource.db2.filters=stat,wall,log4j
spring.dataSource.db2.logSlowSql=true

 

2.配置类如果要加载自定义配置文件中的属性值就需要定义一个配置属性类,

     通过配置属性类去获得配置文件中的信息。如下所示,@Configuration表明该类是一个配置类。@PropertySource注解是加载指定的配置文件,其中value值是配置文件的路径。encoding指定读取属性文件所使用的编码。

    然而在加载该类的时候打印的值为null。这是因为你虽然指定了配置文件的位置但是却没有获取到配置文件中的值。需要在属性上加注解@Value("${spring.datasource,db1.url}")给属性赋值,但是当属性太多时这就显得极其麻烦。为此,springboot提供了一个注解@ConfigurationProperties(prefix = "spring.datasource")。通过prefix指定前缀属性就会自动赋值。至此,该类就具有了配置文件中的值。代码如下:

package com.yszhspringboot.springbootdemo.bean;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Data
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
@PropertySource(value={"classpath:config/dataSource.properties"},encoding = "UTF-8")
public class DataSourceConfigBean {
    private String type;
    private String driverClassName;
    private String platform;
    private String url;
    private String username;
    private String password;
    private Integer initialSize;
    private Integer minIdle;
    private Integer maxActive;
    private Integer maxWait;
    private Integer timeBetweenEvictionRunsMillis;
    private Integer minEvictableIdleTimeMillis;
    private String validationQuery;
    private boolean testWhileIdle;
    private boolean testOnBorrow;
    private boolean testOnReturn;
    private String filters;
    private boolean logSlowSql;
}
3.配置数据源配置类

      1.首先加载配置属性类的信息。使用注解@EnableConfigurationProperties({DataSourceConfigBean.class})。配置多个数据源bean,并通过@ConfigurationProperties(prefix = "spring.datasource.db1")注解为数据源指定连接信息。

/**
     * db1数据库配置
     */
    @Bean("db1")
    @ConfigurationProperties(prefix = "spring.datasource.db1")
    public DataSource db1Source() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * db2数据库配置
     */
    @Bean("db2")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource db2Source() {
        return DruidDataSourceBuilder.create().build();
    }

 2.动态数据库的配置。

 2.1-总的配置类如下,新建一个

DynamicDataSource类继承AbstractRoutingDataSource并实现determineCurrentLookupKey方法
package com.yszhspringboot.springbootdemo.dataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


    public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDB();
    }
}

 2.2-定义一个可以设置当前线程的变量的工具类,用于设置对应的数据源名称:

package com.yszhspringboot.springbootdemo.dataSource;

import lombok.extern.slf4j.Slf4j;

public class DataSourceContextHolder {
    // 默认数据源
    public static final String DEFAULT_DS = "db2";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDB(String dbType) {
        contextHolder.set(dbType);
    }

    public static String getDB() {
        return (contextHolder.get());
    }

    public static void clearDB() {
        contextHolder.remove();
    }
}

2.3-建立一个Map设置对应的键来对应不同的数据源,并为 AbstractRoutingDataSource中targetDataSource赋值

 /**
     * 动态数据库配置
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(db1Source());
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap(5);
        dsMap.put("db1", db1Source());
        dsMap.put("db2", db2Source());
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }

 4.原理分析:

继承AbstractRoutingDataSource。并实现他的一个抽象方法determineCurrentLookupKey();这个抽象类中实现了InitializingBean接口
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

就会在初始化这个bean的时候调用afterPropertiesSet()方法,在该方法中会将配置动态数据源的targetDataSources这个Map赋值给resolvedDataSources这个Map当中。

@Override
	public void afterPropertiesSet() {
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
		this.targetDataSources.forEach((key, value) -> {
			Object lookupKey = resolveSpecifiedLookupKey(key);
			DataSource dataSource = resolveSpecifiedDataSource(value);
			this.resolvedDataSources.put(lookupKey, dataSource);
		});
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}

这个抽象类的核心就是

determineTargetDataSource() 这个抽象方法。
determineCurrentLookupKey()由用户去继承实现该方法,由于动态数据库配置了一个Map,就可以以此来通过该方法得到动态数据库中配置map的key来实现数据源的切换
protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}

    5.注解调用

    5.1-给注解一个默认连接数据库的名字

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DataBase {
    String value() default "db1";
}

  5.2-定义一个切面,为每一个标注了该注解的方法去执行aop逻辑

package com.yszhspringboot.springbootdemo.aop;

import com.yszhspringboot.springbootdemo.anotation.DataBase;
import com.yszhspringboot.springbootdemo.dataSource.DataSourceContextHolder;
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.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DataBaseAspect {

    @Pointcut("@annotation(com.yszhspringboot.springbootdemo.anotation.DataBase)")
    public void dbPointCut() {

    }

    @Before("dbPointCut()")
    public void beforeSwitchDS(JoinPoint point){
        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();
        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();

        String dataSource = DataSourceContextHolder.DEFAULT_DS;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);

            // 判断是否存在@DateBase注解
            if (method.isAnnotationPresent(DataBase.class)) {
                DataBase annotation = method.getAnnotation(DataBase.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 切换数据源
        DataSourceContextHolder.setDB(dataSource);
    }

    @After("dbPointCut()")
    public void afterSwitchDS(JoinPoint point){
        DataSourceContextHolder.clearDB();
    }
}

6.使用

在调用方法上通过更改名字就可以实现动态切换数据源了

@Override
    @DataBase("db2")
    public List<UserInfo> findAll() {
        return userInfoMapper.findAll();
    }

至此动态数据源配置完成

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值