二、拓扑图 三、代码实现 1.maven依赖
- 配置多个数据源。
- 将多个数据源放入map,map放入动态数据源,设定动态数据源的默认数据源。
- sqlSessionFactory中设定动态数据源。
- 动态数据源从threadLocal中获取目标数据源的名称,进而切换数据源。
- 通过aop切入方法,并约定好方法命名(注意this会使aop失效),来区分读和写。
- 前置通知中,读操作,threadLocal中放入读数据源的key;写操作,threadLocal中放入写数据源的key。
- 后置通知中,清空threadLocal,防止内存泄漏。
2.配置文件org.springframework.boot spring-boot-starter-weborg.springframework.boot spring-bootorg.projectlombok lombokmysql mysql-connector-javaruntime org.mybatis.spring.boot mybatis-spring-boot-starter1.3.0 com.alibaba druid-spring-boot-starter1.1.9 org.springframework.boot spring-boot-starter-aop
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
name: masterDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://10.200.242.87:8066/yxh_user?zeroDateTimeBehavior=convertToNull
username: root
password: root
slaver:
name: slaverDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/yxh_user?zeroDateTimeBehavior=convertToNull
username: root
password: root
3.枚举标识数据源名称
public enum DataSourceKey {
masterDataSource,
slaverDataSource
;
}
4.Threadlocal存放数据源名称
public class DynamicDataSourceContextHolder {
private static final ThreadLocal DB_CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceKey(String key){
DB_CONTEXT_HOLDER.set(key);
}
public static String getDataSourceKey(){
return DB_CONTEXT_HOLDER.get();
}
public static void clearDataSourceKey(){
DB_CONTEXT_HOLDER.remove();
}
}
5.动态数据源类
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
6.配置类
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfigurer {
@Bean("masterDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("slaverDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.slaver")
public DataSource slaverDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("dynamicDataSource")
@Autowired
public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaverDataSource")DataSource slaverDataSource) {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map
7.AOP
@Component
@Aspect
public class DynamicDataSourceAspect {
private final List READ_PREFIX = Arrays.asList("query", "select", "get", "find");
private final List WRITE_PREFIX = Arrays.asList("add", "insert","update","modify", "delete", "remove");
@Pointcut("execution(* com.yxh.service..*.*(..))")
public void daoAspect() {}
@Before("daoAspect()")
public void switchDataSource(JoinPoint point) {
String methodName = point.getSignature().getName();
if (isReadMethod(methodName)) {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.slaverDataSource.name());
} else if(isWriteMethod(methodName)){
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.masterDataSource.name());
}else {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.masterDataSource.name());
}
}
@After("daoAspect()")
public void clearDataSource(JoinPoint point) {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
public boolean isReadMethod(String methodName){
if(StringUtils.isBlank(methodName)){
return false;
}
for(String prefix : READ_PREFIX){
if(StringUtils.isNotBlank(prefix) && methodName.startsWith(prefix)){
return true;
}
}
return false;
}
public boolean isWriteMethod(String methodName){
if(StringUtils.isBlank(methodName)){
return false;
}
for(String prefix : WRITE_PREFIX){
if(StringUtils.isNotBlank(prefix) && methodName.startsWith(prefix)){
return true;
}
}
return false;
}
}
四、简单分析AbstractRoutingDataSource源码
自定义的数据源继承了AbstractRoutingDataSource,并重写了determineCurrentLookupKey()来获取目标数据源的名称。
先贴一波AbstractRoutingDataSource的源码供大家参考。
package org.springframework.jdbc.datasource.lookup;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable
private Map
AbstractRoutingDataSource中的determineTargetDataSource()中,使用模板方法设计模式,将determineCurrentLookupKey()抽象,供调用者灵活配置。我们一起看下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;
}
resolvedDataSources其实是复制并加工了targetDataSources的内容,参考afterPropertiesSet()方法。而targetDataSources的赋值基于public void setTargetDataSources(Map
也就是最开始我们在配置类DataSourceConfigurer中创建自定义的动态数据源时调用了setTargetDataSources,放入的数据源map。
欢迎大家和帝都的雁积极互动,头脑交流会比个人埋头苦学更有效!共勉!
公众号:帝都的雁



