一文弄懂spring官方多数据源

简介: 路由键#determineCurrentLookupKey先看一下AbstractRoutingDataSource的类图我们可以看到,它间接实现了DataSource。

路由键#determineCurrentLookupKey
先看一下AbstractRoutingDataSource的类图
我们可以看到,它间接实现了DataSource。是个抽象类,只有一个抽象方法#determineCurrentLookupKey()
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()
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连接时会通过该方法获取数据源
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是怎么来的呢?
初始化
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

新建实现类
java复制代码public class RoutingDataSource extends AbstractRoutingDataSource {

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

}

新建上下文切换类
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
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;
}

}

使用
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声明式事务冲突只能单数据源事务的问题,感兴趣的小伙伴们可以自行查看源码

相关文章
|
5月前
|
Java API Spring
Spring的设计哲学--来自官方
Spring框架设计哲学强调在每个层级提供选择,允许延迟设计决策,如通过配置切换持久性提供商。它拥抱灵活性,适应不同观点,同时保持强向后兼容性,确保版本间少有破坏性更改。Spring注重API设计,追求高质量代码,拥有清晰无循环依赖的结构。这些原则使Spring成为Java开发中最受欢迎的框架之一。
|
11月前
|
前端开发 Java 数据库连接
基于Spring boot轻松实现一个多数据源框架
基于Spring boot轻松实现一个多数据源框架
249 0
|
5月前
|
前端开发 Java 数据库连接
Spring Boot 3 整合 Mybatis-Plus 动态数据源实现多数据源切换
Spring Boot 3 整合 Mybatis-Plus 动态数据源实现多数据源切换
|
4月前
|
druid Java 关系型数据库
Spring Boot2 系列教程(二十五)Spring Boot 整合 Jpa 多数据源
Spring Boot2 系列教程(二十五)Spring Boot 整合 Jpa 多数据源
|
5月前
|
Java 关系型数据库 数据库
Spring Boot多数据源及事务管理:概念与实战
【4月更文挑战第29天】在复杂的企业级应用中,经常需要访问和管理多个数据源。Spring Boot通过灵活的配置和强大的框架支持,可以轻松实现多数据源的整合及事务管理。本篇博客将探讨如何在Spring Boot中配置多数据源,并详细介绍事务管理的策略和实践。
394 3
|
3月前
|
开发框架 Java 数据库
Spring Boot集成多数据源的最佳实践
Spring Boot集成多数据源的最佳实践
|
4月前
|
druid Java 关系型数据库
在Spring Boot中集成Druid实现多数据源有两种常用的方式:使用Spring Boot的自动配置和手动配置。
在Spring Boot中集成Druid实现多数据源有两种常用的方式:使用Spring Boot的自动配置和手动配置。
551 5
|
3月前
|
存储 Java 关系型数据库
Spring Data与多数据源配置
Spring Data与多数据源配置
|
5月前
|
Java 数据库连接 Spring
Spring多数据源配置
Spring多数据源配置
|
5月前
|
Java 数据库连接 数据库
Spring Boot整合MyBatis Plus集成多数据源轻松实现数据读写分离
Spring Boot整合MyBatis Plus集成多数据源轻松实现数据读写分离
97 2