前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文弄懂spring官方多数据源

一文弄懂spring官方多数据源

作者头像
阿珍
发布2023-06-06 17:07:42
2280
发布2023-06-06 17:07:42
举报

路由键#determineCurrentLookupKey

先看一下AbstractRoutingDataSource的类图

我们可以看到,它间接实现了DataSource。是个抽象类,只有一个抽象方法#determineCurrentLookupKey()

代码语言:javascript
复制
java复制代码public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

	/**
	 * Determine the current lookup key. This will typically be
	 * implemented to check a thread-bound transaction context.
	 * <p>Allows for arbitrary keys. The returned key needs
	 * to match the stored lookup key type, as resolved by the
	 * {@link #resolveSpecifiedLookupKey} method.
	 */
	@Nullable
	protected abstract Object determineCurrentLookupKey();

	......
}

通过注释与方法名我们可以知道,这个方法是来确定数据源的路由key的,那他究竟有什么用呢

核心方法#determineTargetDataSource

看代码可知,抽象方法#determineCurrentLookupKey()只有一个地方用到,即#determineTargetDataSource()

代码语言:javascript
复制
java复制代码public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

	/**
	 * Retrieve the current target DataSource. Determines the
	 * {@link #determineCurrentLookupKey() current lookup key}, performs
	 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
	 * falls back to the specified
	 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
	 * @see #determineCurrentLookupKey()
	 */
	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
        //通过当前lookupKey获取数据源
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        //如果数据源为空,开启了宽松模式或者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;
	}

	......
}

而#determineTargetDataSource()为什么是核心方法,因为AbstractRoutingDataSource其自身就是一个DataSource,获取jdbc连接时会通过该方法获取数据源

代码语言:javascript
复制
java复制代码public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

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

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

	......
}

到这里,AbstractRoutingDataSource怎么工作的我们大概已经清楚了。但他是怎么初始化的呢?determineTargetDataSource方法中的resolvedDataSources是怎么来的呢?

初始化

代码语言:javascript
复制
java复制代码public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

	@Nullable
	private Map<Object, Object> targetDataSources;

	@Nullable
	private Object defaultTargetDataSource;

	private boolean lenientFallback = true;

	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

	@Nullable
	private Map<Object, DataSource> resolvedDataSources;

	@Nullable
	private DataSource resolvedDefaultDataSource;

    getter/setter忽略......

    @Override
	public void afterPropertiesSet() {
    	//targetDataSources是必须的,不然bean初始化时候就会抛错
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
        //已解析数据源map初始化
		this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
		this.targetDataSources.forEach((key, value) -> {
        	//这一步resolveSpecifiedLookupKey其实就是返回key自身
			Object lookupKey = resolveSpecifiedLookupKey(key);
            //如果value是DataSource类型直接返回,是String类型则会认为是jndi名称,通过JndiDataSourceLookup类查找
			DataSource dataSource = resolveSpecifiedDataSource(value);
            //放入已解析数据源
			this.resolvedDataSources.put(lookupKey, dataSource);
		});
        //设置默认数据源
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}

	......
}

resolveSpecifiedDataSource部分没有详细介绍,感兴趣的小伙伴们可以自行追踪源码与DataSourceLookup类的源码去查看

简单使用

上面源码看完相信大家都对AbstractRoutingDataSource很了解了,使用方面总结就三步

  • 实现AbstractRoutingDataSource
  • 重写#determineCurrentLookupKey()
  • 设置targetDataSources与defaultTargetDataSource

新建实现类

代码语言:javascript
复制
java复制代码public class RoutingDataSource extends AbstractRoutingDataSource {

    /**
     * 获取路由key,通过key可获取已设置数据源中对应的数据源
     * <p>如果lookupKey为空则获取默认数据源
     * <p>详见{@link AbstractRoutingDataSource#determineTargetDataSource}
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return RoutingDataSourceContext.getRoutingKey();
    }
}

新建上下文切换类

代码语言:javascript
复制
typescript复制代码public class RoutingDataSourceContext {

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

    public static void setRoutingKey(String routingKey) {
        LOOKUP_KEY_HOLDER.set(routingKey);
    }

    public static String getRoutingKey() {
        String name = LOOKUP_KEY_HOLDER.get();
        // 如果routingKey不存在则返回默认数据源
        return StringUtils.hasText(name) ? name : null;
    }

    public static void reset() {
        LOOKUP_KEY_HOLDER.remove();
    }
}

注册bean

代码语言:javascript
复制
java复制代码@Configuration
public class DataSourceConfiguration {

    /**
     * routingDataSource也是dataSource
     * <p>这里指定该bean为主dataSource,防止多个datasource时导致mybatis的自动装配失效
     */
    @Bean
    public RoutingDataSource routingDataSource() {
        //数据源路由器
        RoutingDataSource routingDataSource = new RoutingDataSource();
        DataSource first = DataSourceBuilder.create()
                .url("")
                .username("")
                .password("")
                .driverClassName("")
                .build();
        DataSource second = DataSourceBuilder.create()
                .url("")
                .username("")
                .password("")
                .driverClassName("")
                .build();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("first", first);
        targetDataSources.put("second", second);
        //设置默认数据源
        routingDataSource.setDefaultTargetDataSource(first);
        //设置总共支持哪些数据源切换
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }

}

使用

代码语言:javascript
复制
java复制代码@Service
public class CarService {

    private final CarMapper carMapper;

    public CarService(CarMapper carMapper) {
        this.carMapper = carMapper;
    }

    public Car firstGet() {
        RoutingDataSourceContext.setRoutingKey("first");
        return carMapper.getOne(1L);
    }
}

封装成组件

很容易想到,如果能封装成组件,通过配置来动态添加数据源,并新建自定义注解通过AOP来读取就更方便了。还好我已经替你们实现啦Scindapsus-DS,而且还通过本地事务解决了AOP与Spring声明式事务冲突只能单数据源事务的问题,感兴趣的小伙伴们可以自行查看源码

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 路由键#determineCurrentLookupKey
  • 核心方法#determineTargetDataSource
  • 初始化
  • 简单使用
    • 新建实现类
      • 新建上下文切换类
        • 注册bean
          • 使用
          • 封装成组件
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档