动态数据源切换

1.创建一个springboot项目

2.添加maven依赖

org.springframework.bootspring-boot-starter-aopmysqlmysql-connector-javaruntimecom.alibabadruid-spring-boot-starter1.1.18org.springframework.bootspring-boot-starter-testorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-freemarkerorg.projectlomboklombokcom.baomidoumybatis-plus-boot-starter3.1.0

3.配置application.yml

spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverdruid:master:jdbc-url: jdbc:mysql://localhost:3306/act_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&nullCatalogMeansCurrent=trueusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driversalve:jdbc-url: jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&nullCatalogMeansCurrent=trueusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver

 4.在配置类上启用AOP注解

@EnableAspectJAutoProxy

5、定义一个注解类

/*** @author wts* @date 2022/4/11* @description  自定义多数据源切换注解* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface DataSource {/*** 切换的数据源类型*/String value() default DataSourceName.DB_MerchantCenter;
}

 6.定义一个枚举类

/*** @author wts* @date 2022/4/11* @description 数据源*/
public enum DataSourceType {/*** 主库*/MASTER,/*** 从库*/SALVE
}

 7.定义一个动态数据源处理器

/*** @author wts* @date 2022/4/11* @description 数据源切换处理*/
public class DynamicDataSourceContextHolder {/*** 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。*/private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();/*** 设置数据源的变量*/public static void setDataSourceType(String dsType) {CONTEXT_HOLDER.set(dsType);}/*** 获得数据源的变量*/public static String getDataSourceType() {return CONTEXT_HOLDER.get();}/*** 清空数据源变量*/public static void clearDataSourceType() {CONTEXT_HOLDER.remove();}
}

 8.定义一个动态数据源

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.util.Map;/*** @author wts* @date 2022/4/11* @description 动态数据源*/
public class DynamicDataSource extends AbstractRoutingDataSource {public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}

 9.定义一个数据源配置类

import com.zcy.common.datasource.DataSourceType;
import com.zcy.common.datasource.DynamicDataSource;
import com.zcy.utils.SpringUtils;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author wts* @date 2021/4/11* @description 数据源配置类*/
@Configuration
public class DataSourceConfig {@Bean(name = "masterDataSource")@ConfigurationProperties(prefix = "spring.datasource.druid.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean(name = "salveDataSource")@ConfigurationProperties(prefix = "spring.datasource.druid.salve")public DataSource salveDataSource() {return DataSourceBuilder.create().build();}@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dataSource() {Map targetDataSources = new HashMap<>();targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource());setDataSource(targetDataSources, DataSourceType.SALVE.name(), "salveDataSource");return new DynamicDataSource(masterDataSource(), targetDataSources);}/*** 设置数据源** @param targetDataSources 备选数据源集合* @param sourceName        数据源名称* @param beanName          bean名称*/public void setDataSource(Map targetDataSources, String sourceName, String beanName) {try {DataSource dataSource = SpringUtils.getBean(beanName);targetDataSources.put(sourceName, dataSource);} catch (Exception e) {e.printStackTrace();}}}

10.定义一个AOP切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.Objects;/*** @author wts* @date 2021/4/11* @description 多数据源处理*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {protected Logger logger = LoggerFactory.getLogger(getClass());@Pointcut("@annotation(com.zcy.common.datasource.DataSource)" + "|| @within(com.zcy.common.datasource.DataSource)")public void dsPointCut() {}@Around("dsPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {DataSource dataSource = getDataSource(point);if (null != dataSource) {DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());}try {return point.proceed();} finally {// 销毁数据源 在执行方法之后DynamicDataSourceContextHolder.clearDataSourceType();}}/*** 获取需要切换的数据源*/public DataSource getDataSource(ProceedingJoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature();DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);if (Objects.nonNull(dataSource)) {return dataSource;}return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);}
}

 【补充】:AbstractRoutingDataSource分析

抽象类AbstractRoutingDataSource,通过扩展这个类实现根据不同的请求切换数据源。

AbstractRoutingDataSource继承AbstractDataSource,如果声明一个类DynamicDataSource继承AbstractRoutingDataSource后,DynamicDataSource本身就相当于一种数据源。所以AbstractRoutingDataSource必然有getConnection()方法获取数据库连接。如下:

@Override
public Connection getConnection() throws SQLException {return determineTargetDataSource().getConnection();
}@Override
public Connection getConnection(String username, String password) throws SQLException {return determineTargetDataSource().getConnection(username, password);
}

AbstractRoutingDataSourcegetConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。接着看determineTargetDataSource()方法:

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

大致流程为,通过determineCurrentLookupKey()方法获取一个key,通过key从resolvedDataSources中获取数据源DataSource对象。determineCurrentLookupKey()是个抽象方法,需要继承AbstractRoutingDataSource的类实现;而resolvedDataSources是一个Map,里面应该保存当前所有可切换的数据源。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部