栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

threadlocal实战动态数据源

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

threadlocal实战动态数据源

一、思路
  1. 配置多个数据源。
  2. 将多个数据源放入map,map放入动态数据源,设定动态数据源的默认数据源。
  3. sqlSessionFactory中设定动态数据源。
  4. 动态数据源从threadLocal中获取目标数据源的名称,进而切换数据源。
  5. 通过aop切入方法,并约定好方法命名(注意this会使aop失效),来区分读和写。
  6. 前置通知中,读操作,threadLocal中放入读数据源的key;写操作,threadLocal中放入写数据源的key。
  7. 后置通知中,清空threadLocal,防止内存泄漏。
二、拓扑图

三、代码实现 1.maven依赖

    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot



    org.projectlombok
    lombok


    mysql
    mysql-connector-java
    runtime


    org.mybatis.spring.boot
    mybatis-spring-boot-starter
    1.3.0



    com.alibaba
    druid-spring-boot-starter
    1.1.9


    org.springframework.boot
    spring-boot-starter-aop
2.配置文件
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 dataSourceMap = new HashMap<>();
      dataSourceMap.put(DataSourceKey.masterDataSource.name(), masterDataSource);
      dataSourceMap.put(DataSourceKey.slaverDataSource.name(), slaverDataSource);

      dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
      dynamicRoutingDataSource.setDefaultTargetDataSource(masterDataSource);

      return dynamicRoutingDataSource;
   }

   @Bean("sqlSessionFactory")
   @Autowired
   public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
      DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();

      SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      sqlSessionFactoryBean.setDataSource(dynamicDataSource);
      sqlSessionFactoryBean.setConfigLocation(defaultResourceLoader.getResource("classpath:mybatis-config.xml"));

      return sqlSessionFactoryBean.getObject();
   }

   @Autowired
   @Bean
   public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
      return new SqlSessionTemplate(sqlSessionFactory);
   }

   @Autowired
   @Bean
   public DataSourceTransactionManager txManager(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) {
      DataSourceTransactionManager txManager = new DataSourceTransactionManager();
      txManager.setDataSource(dynamicDataSource);
      return txManager;
   }
}
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 targetDataSources;

	@Nullable
	private Object defaultTargetDataSource;

	private boolean lenientFallback = true;

	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

	@Nullable
	private Map resolvedDataSources;

	@Nullable
	private DataSource resolvedDefaultDataSource;

	public void setTargetDataSources(Map targetDataSources) {
		this.targetDataSources = targetDataSources;
	}

	public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
		this.defaultTargetDataSource = defaultTargetDataSource;
	}

	public void setLenientFallback(boolean lenientFallback) {
		this.lenientFallback = lenientFallback;
	}

	public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {
		this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
	}


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

	protected Object resolveSpecifiedLookupKey(Object lookupKey) {
		return lookupKey;
	}

	
	protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
		if (dataSource instanceof DataSource) {
			return (DataSource) dataSource;
		}
		else if (dataSource instanceof String) {
			return this.dataSourceLookup.getDataSource((String) dataSource);
		}
		else {
			throw new IllegalArgumentException(
					"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
		}
	}


	@Override
	public Connection getConnection() throws SQLException {
		return determineTargetDataSource().getConnection();
	}

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

	@Override
	@SuppressWarnings("unchecked")
	public  T unwrap(Class iface) throws SQLException {
		if (iface.isInstance(this)) {
			return (T) this;
		}
		return determineTargetDataSource().unwrap(iface);
	}

	@Override
	public boolean isWrapperFor(Class iface) throws SQLException {
		return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
	}

	
	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;
	}
	
	@Nullable
	protected abstract Object determineCurrentLookupKey();

}

        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 targetDataSources)

        也就是最开始我们在配置类DataSourceConfigurer中创建自定义的动态数据源时调用了setTargetDataSources,放入的数据源map。

欢迎大家和帝都的雁积极互动,头脑交流会比个人埋头苦学更有效!共勉!

公众号:帝都的雁

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/305711.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号