动态多数据源配置(ORACLE)

1.配置多数据源

根据实际需求配置多个数据源,给副数据源进行别名区分,并统一设置命名方便后面进行获取
# 主数据源(默认数据源)
spring.datasource.url = jdbc:oracle:thin:@10.51.12.88:1521:test
spring.datasource.username=test
spring.datasource.password=123456
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver

# 副数据源
slave.datasource.one.url = jdbc:oracle:thin:@10.60.13.99:1521:test
slave.datasource.one.username=test
slave.datasource.one.password=123456
slave.datasource.one.driver-class-name=oracle.jdbc.driver.OracleDriver

# 数据源区别名
slave.datasource.names =one(例如:one,two,three)

2.动态数据源上下文管理

创建一个动态数据源上下文管理类,用于线程存储数据源信息
import java.util.ArrayList;
import java.util.List;

/**
 * <h3>mes-product</h3>
 * <p>动态数据源上下文管理</p>
 *
 * @author : kyc
 * @date : 2021-01-21 14:27
 **/
public class DynamicDataSourceContextHolder {

    /**
     * 存放当前线程使用的数据源类型信息
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>();

    /**
     * 存放数据源id
     */
    public static List<String> dataSourceIds = new ArrayList<String>();

    /**
     * 设置数据源ID
     */
    public static void setDataSourceType(String dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }

    /**
     * 获取数据源
     */
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清除数据源
     */
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }

    /**
     * 判断当前数据源是否存在
     */
    public static boolean isContainsDataSource(String dataSourceId) {
        return dataSourceIds.contains(dataSourceId);
    }

}

3.动态数据源获取类

没执行一次数据库都进行动态获取数据源信息
/**
 * @author kyc
 * @ClassName: DynamicDataSource
 * @Description: 动态数据源:通知spring用key当前的数据源
 * @date 2021-01-21
 * AbstractRoutingDataSource(每执行一次数据库,动态获取DataSource)
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 用于指定到底需要使用哪一个数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

4.多数据源源注册器

初始化对配置文件进行解析多数据源信息

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @description 多数据源注册器
 * @author : kyc
 * @date : 2021-01-21 14:29
 **/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    /**
     * 指定默认数据源类型(springboot2.0默认数据源是hikari如何想使用其他数据源可以自己配置),我这里用的是druid
     */
    private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";

    /**
     * 默认数据源
     */
    private DataSource defaultDataSource;

    /**
     * 用户自定义数据源
     */
    private final Map<String, DataSource> slaveDataSources = new HashMap<>();

    @Override
    public void setEnvironment(Environment environment) {
        //初始化默认数据源
        initDefaultDataSource(environment);
        //初始化副数据源
        initSlaveDataSources(environment);

    }

    private void initDefaultDataSource(Environment env) {
        // 拼接 主数据源信息
        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("driver-class-name", env.getProperty("spring.datasource.driver-class-name"));
        dsMap.put("url", env.getProperty("spring.datasource.url"));
        dsMap.put("username", env.getProperty("spring.datasource.username"));
        dsMap.put("password", env.getProperty("spring.datasource.password"));
        // 根据数据源map信息构建数据源
        defaultDataSource = buildDataSource(dsMap);
        System.err.println("配置默认数据源成功");
    }


    private void initSlaveDataSources(Environment env) {
        // 读取配置文件获取更多数据源
        String dsPrefixs = env.getProperty("slave.datasource.names");
        for (String dsPrefix : dsPrefixs.split(",")) {
            // 多个数据源
            Map<String, Object> dsMap = new HashMap<>();
            dsMap.put("driver-class-name", env.getProperty("slave.datasource." + dsPrefix + ".driver-class-name"));
            dsMap.put("url", env.getProperty("slave.datasource." + dsPrefix + ".url"));
            dsMap.put("username", env.getProperty("slave.datasource." + dsPrefix + ".username"));
            dsMap.put("password", env.getProperty("slave.datasource." + dsPrefix + ".password"));
            DataSource ds = buildDataSource(dsMap);
            System.err.println("配置"+dsPrefix+"数据源成功");
            slaveDataSources.put(dsPrefix, ds);
        }
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        //添加默认数据源
        targetDataSources.put("dataSource", this.defaultDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
        //添加其他数据源
        targetDataSources.putAll(slaveDataSources);
        DynamicDataSourceContextHolder.dataSourceIds.addAll(slaveDataSources.keySet());

        //创建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        //注册 - BeanDefinitionRegistry
        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);

    }

    public DataSource buildDataSource(Map<String, Object> dataSourceMap) {
        try {
            Object type = dataSourceMap.get("type");
            if (type == null) {
                type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
            }
            Class<? extends DataSource> dataSourceType;
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
            String driverClassName = dataSourceMap.get("driver-class-name").toString();
            String url = dataSourceMap.get("url").toString();
            String username = dataSourceMap.get("username").toString();
            String password = dataSourceMap.get("password").toString();
            // 自定义DataSource配置
            DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                    .username(username).password(password).type(dataSourceType);
            return factory.build();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}
配置完后必须在启动类上加上注解@Import(DynamicDataSourceRegister.class),才能在启动应用后自动进行数据源注册
import com.cowin.mes.config.DynamicDataSourceRegister;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@EnableTransactionManagement
@SpringBootApplication
@MapperScan("com.xxx.xxx.xxx")
@Import(DynamicDataSourceRegister.class)
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(CowinApplication.class, args);
	}

}

5.自定义注解

首先定义一个注解可以用在方法或者接口上面用以标明使用哪一个数据源进行操作。
import java.lang.annotation.*;

/**
 * 动态数据源注解
 * @author : kyc
 * @date : 2021-01-21 14:26
 **/
@Target({ElementType.TYPE, ElementType.METHOD})// TYPE用于类,接口上,METHOD可以用于方法
@Retention(RetentionPolicy.RUNTIME)//一般都是用RUNTIME,因为这是在程序运行时可以对注解进行读取
@Documented//被 javadoc工具记录. 默认情况下,javadoc是不包括注解的
public @interface TargetDataSource {

    //数据源名称(用于根据名称切换数据源)
    String name();

}

6.定义切面AOP处理注解

对注解进行解析,判断使用哪一个数据源,并进行切换。
import com.cowin.mes.annotation.TargetDataSource;
import com.cowin.mes.config.DynamicDataSourceContextHolder;
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.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 *
 * @ClassName: DynamicDataSourceAspect
 * @Description: 动态改变数据库AOP通知
 * @kyc
 * @date 2021-01-21 14:32
 */
@Aspect
@Order(-1)//保证在@Transactional之前执行
@Component("dynamicDataSourceAspect")
public class DynamicDataSourceAspect {

    /**
     * 定义切入点,切入点为com.example.controller下的所有函数
     */
    @Pointcut("@annotation(com.cowin.mes.annotation.TargetDataSource)")
    public void webLog(){}


    /**
     * 改变数据源
     * @param joinPoint
     */
    @Before("webLog()")
    public void changeDataSource(JoinPoint joinPoint) {
        TargetDataSource targetDataSource = getAnnotationDataSource(joinPoint);
        String dbid = targetDataSource.name();

        if (!DynamicDataSourceContextHolder.isContainsDataSource(dbid)) {
//            System.err.println("数据源 " + dbid + " 不存在使用默认的数据源!" );
        } else {
//            System.out.println("使用数据源:" + dbid);
            DynamicDataSourceContextHolder.setDataSourceType(dbid);
        }
    }

    @After("webLog()")
    public void clearDataSource() {
//    	System.err.println("清除数据源");
        DynamicDataSourceContextHolder.clearDataSourceType();
    }

    /**
     * 获取数据源注解信息
     * @param joinPoint
     * @return
     */
    private TargetDataSource getAnnotationDataSource(JoinPoint joinPoint){
        TargetDataSource targetDataSource = null;
        try {
            MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
            if(null == targetDataSource) {
                Method method = methodSignature.getMethod();
                if(method != null) {
                    targetDataSource = method.getAnnotation(TargetDataSource.class);
                    if(null == targetDataSource) {
                        targetDataSource = method.getDeclaringClass().getAnnotation(TargetDataSource.class);
                    }
                }
            }
            return targetDataSource;
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

7.使用

在接口或者方法上添加注解@@TargetDataSource(name = "one"),name为配置文件中自己配的数据库别名,添加完后,调用接口则会使用该数据库进行操作.
	@TargetDataSource(name = "one")
    @GetMapping(value = "/listSupplier")
    public String listSupplier(Supplier query) {
        List<Supplier> list = supplierManager.listSupplier(query);
        return Result.ok(list);
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值