ShardingJDBC源码阅读(二)创建ShardingDataSource

6,469 阅读8分钟

前言

众所周知,对于数据库的操作离不开几个类,Datasource、Connection、Statement、ResultSet。

本章关注ShardingDataSource的创建。

  • 多个数据源和ShardingRuleConfiguration的配置,如何转换为运行时的DataSource?
  • 分片数据源与普通数据源的区别是什么?

一、ShardingDataSourceFactory

org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory用于创建ShardingDataSource。sharding-jdbc中所有包名是api的,都是最终暴露给用户使用的,很规范。

  • dataSourceMap:数据源名称与数据源的映射关系。
  • shardingRuleConfig:分片规则配置。
  • props:配置,如sql.show=true
public final class ShardingDataSourceFactory {
    public static DataSource createDataSource(
            final Map<String, DataSource> dataSourceMap, 
            final ShardingRuleConfiguration shardingRuleConfig, 
            final Properties props) throws SQLException {
        return new ShardingDataSource(dataSourceMap, new ShardingRule(shardingRuleConfig, dataSourceMap.keySet()), props);
    }
}

首先,调用ShardingRule的构造方法,将ShardingRuleConfiguration配置转换为ShardingRule。这一步直接忽略了,就是解析配置(参照第一章)转换为运行时的对象,只需要知道我们配置的东西最终都在ShardingRule中可以找到。

public class ShardingRule implements BaseRule {
    private final ShardingRuleConfiguration ruleConfiguration;
    private final ShardingDataSourceNames shardingDataSourceNames;
    private final Collection<TableRule> tableRules;
    private final Collection<BindingTableRule> bindingTableRules;
    private final Collection<String> broadcastTables;
    private final ShardingStrategy defaultDatabaseShardingStrategy;
    private final ShardingStrategy defaultTableShardingStrategy;
    private final ShardingKeyGenerator defaultShardingKeyGenerator;
    private final Collection<MasterSlaveRule> masterSlaveRules;
    private final EncryptRule encryptRule;
    
    public ShardingRule(final ShardingRuleConfiguration shardingRuleConfig, final Collection<String> dataSourceNames) {
        Preconditions.checkArgument(null != shardingRuleConfig, "ShardingRuleConfig cannot be null.");
        Preconditions.checkArgument(null != dataSourceNames && !dataSourceNames.isEmpty(), "Data sources cannot be empty.");
        this.ruleConfiguration = shardingRuleConfig;
        shardingDataSourceNames = new ShardingDataSourceNames(shardingRuleConfig, dataSourceNames);
        tableRules = createTableRules(shardingRuleConfig);
        broadcastTables = shardingRuleConfig.getBroadcastTables();
        bindingTableRules = createBindingTableRules(shardingRuleConfig.getBindingTableGroups());
        defaultDatabaseShardingStrategy = createDefaultShardingStrategy(shardingRuleConfig.getDefaultDatabaseShardingStrategyConfig());
        defaultTableShardingStrategy = createDefaultShardingStrategy(shardingRuleConfig.getDefaultTableShardingStrategyConfig());
        defaultShardingKeyGenerator = createDefaultKeyGenerator(shardingRuleConfig.getDefaultKeyGeneratorConfig());
        masterSlaveRules = createMasterSlaveRules(shardingRuleConfig.getMasterSlaveRuleConfigs());
        encryptRule = createEncryptRule(shardingRuleConfig.getEncryptRuleConfig());
    }
}

最后,调用ShardingDataSource的构造方法,创建数据源。

二、ShardingDataSource

跳过java.sql包里的三个类,之前学HikariCP的时候有介绍过。

1、WrapperAdapter

在sharding-jdbc中,基本所有数据库驱动相关的类都继承了这个WrapperAdapter

首先WrapperAdapter实现了java.sql.Wrapper接口,提供了isWrapperForunwrap方法的实现。

public abstract class WrapperAdapter implements Wrapper {
    @Override
    public final <T> T unwrap(final Class<T> iface) throws SQLException {
    	// 判断当前对象是否是iface的实例
        if (isWrapperFor(iface)) {
        	// 如果是的话强转
            return (T) this;
        }
        // 否则抛出异常
        throw new SQLException(String.format("[%s] cannot be unwrapped as [%s]", getClass().getName(), iface.getName()));
    }
    @Override
    public final boolean isWrapperFor(final Class<?> iface) {
    	// 判断当前对象是否是iface的实例
        return iface.isInstance(this);
    }
}

其次,WrapperAdapter扩展了两个公共方法。

  • recordMethodInvocation:记录一次方法调用到jdbcMethodInvocations集合中。
  • replayMethodsInvocation:对于某个对象,重放所有jdbcMethodInvocations集合中的方法调用。
public abstract class WrapperAdapter implements Wrapper {
    private final Collection<JdbcMethodInvocation> jdbcMethodInvocations = new ArrayList<>();
    @SneakyThrows
    public final void recordMethodInvocation(final Class<?> targetClass, final String methodName, final Class<?>[] argumentTypes, final Object[] arguments) {
        jdbcMethodInvocations.add(new JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments));
    }
    public final void replayMethodsInvocation(final Object target) {
        for (JdbcMethodInvocation each : jdbcMethodInvocations) {
            each.invoke(target);
        }
    }
}

为什么sharding-jdbc大部分对于java.sql驱动相关的实现类都继承了WrapperAdapter

对于ShardingDataSource可以理解,因为实现java.sql.DataSource也必须要实现java.sql.WrapperWrapperAdapter提供了默认实现。但是对于java.sql.Connection并不需要实现java.sql.Wrapper

此外,recordMethodInvocationreplayMethodsInvocation到底有什么用?

对于传统jdbc开启事务(set autocommit = 0)的操作流程是这样的: 而对于sharding-jdbc流程是这样的:

sharding-jdbc暴露给用户的Connection实现是ShardingConnection。 在没有得到sql的情况下,用户调用connection.setAutoCommit(false)开启事务,sharding-jdbc不知道实际数据源是哪几个,这时候只能记录下来。 等到用户执行了connection.prepareStatement("xxx")后,sharding-jdbc才能通过解析sql知道实际数据源是哪几个,获取实际的Connection,重放setAutoCommit方法执行。

2、AbstractUnsupportedOperationDataSource

AbstractUnsupportedOperationDataSource提供DataSource接口的两个方法默认实现。

public abstract class AbstractUnsupportedOperationDataSource extends WrapperAdapter implements DataSource {
    @Override
    public final int getLoginTimeout() throws SQLException {
        throw new SQLFeatureNotSupportedException("unsupported getLoginTimeout()");
    }
    @Override
    public final void setLoginTimeout(final int seconds) throws SQLException {
        throw new SQLFeatureNotSupportedException("unsupported setLoginTimeout(int seconds)");
    }
}

org.apache.shardingsphere.shardingjdbc.jdbc.unsupported包下的所有类,都和AbstractUnsupportedOperationDataSource类似,由于sharding-jdbc对于java.sql接口的有些方法没有实现,就会提供一个抽象UnsupportedOperationXXX类。 目的是不要让每个实现类都实现一遍这些不支持的方法,仅仅是抛出一个SQLFeatureNotSupportedException异常。

3、AbstractDataSourceAdapter

AbstractDataSourceAdapterDataSource的适配层。

  • 提供了getLogWriter/setLogWriter的实现
  • 提供了dataSourceMap(多数据源)和databaseType的get方法
  • 对于getConnection(username,password)方法提供默认实现(直接调用无参getConnection)
@Getter
public abstract class AbstractDataSourceAdapter extends AbstractUnsupportedOperationDataSource implements AutoCloseable {
    
    private final Map<String, DataSource> dataSourceMap;
    
    private final DatabaseType databaseType;
    
    @Setter
    private PrintWriter logWriter = new PrintWriter(System.out);
    
    public AbstractDataSourceAdapter(final Map<String, DataSource> dataSourceMap) throws SQLException {
        this.dataSourceMap = dataSourceMap;
        databaseType = createDatabaseType();
    }

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

另外实现了AutoCloseable接口,close方法用于关闭资源。

public abstract class AbstractDataSourceAdapter extends AbstractUnsupportedOperationDataSource implements AutoCloseable {
    @Override
    public final void close() throws Exception {
        close(dataSourceMap.keySet());
    }
    public void close(final Collection<String> dataSourceNames) throws Exception {
        for (String each : dataSourceNames) {
            close(dataSourceMap.get(each));
        }
        getRuntimeContext().close();
    }
    
    private void close(final DataSource dataSource) {
        try {
            Method method = dataSource.getClass().getDeclaredMethod("close");
            method.setAccessible(true);
            method.invoke(dataSource);
        } catch (final ReflectiveOperationException ignored) {
        }
    }
    
    protected abstract RuntimeContext getRuntimeContext();
}

此外,子类需要实现getRuntimeContext方法,获取RuntimeContext

4、ShardingDataSource

@Getter
public class ShardingDataSource extends AbstractDataSourceAdapter {
    
    private final ShardingRuntimeContext runtimeContext;
    
    static {
        NewInstanceServiceLoader.register(RouteDecorator.class);
        NewInstanceServiceLoader.register(SQLRewriteContextDecorator.class);
        NewInstanceServiceLoader.register(ResultProcessEngine.class);
    }
    
    public ShardingDataSource(final Map<String, DataSource> dataSourceMap, final ShardingRule shardingRule, final Properties props) throws SQLException {
        super(dataSourceMap);
        checkDataSourceType(dataSourceMap);
        runtimeContext = new ShardingRuntimeContext(dataSourceMap, shardingRule, props, getDatabaseType());
    }
    
    private void checkDataSourceType(final Map<String, DataSource> dataSourceMap) {
        for (DataSource each : dataSourceMap.values()) {
            Preconditions.checkArgument(!(each instanceof MasterSlaveDataSource), "Initialized data sources can not be master-slave data sources.");
        }
    }
    
    @Override
    public final ShardingConnection getConnection() {
        return new ShardingConnection(getDataSourceMap(), runtimeContext, TransactionTypeHolder.get());
    }
}

利用JDKSPI

static代码块中,利用JDK的SPI,将RouteDecorator(路由)、SQLRewriteContextDecorator(SQL重写)、ResultProcessEngine(结果处理)三个接口的实现Class,注册到NewInstanceServiceLoader#SERVICE_MAP中。

public final class NewInstanceServiceLoader {
	// 抽象接口 - 实现类Class集合
    private static final Map<Class, Collection<Class<?>>> SERVICE_MAP = new HashMap<>();
   
    public static <T> void register(final Class<T> service) {
        for (T each : ServiceLoader.load(service)) {
            registerServiceClass(service, each);
        }
    }
    
    private static <T> void registerServiceClass(final Class<T> service, final T instance) {
        Collection<Class<?>> serviceClasses = SERVICE_MAP.get(service);
        if (null == serviceClasses) {
            serviceClasses = new LinkedHashSet<>();
        }
        serviceClasses.add(instance.getClass());
        SERVICE_MAP.put(service, serviceClasses);
    }
}

注意到SERVICE_MAP并不是保存实现类的全局单例对象集合,而是保存实现类的Class对象集合。sharding-jdbc中这些通过SPI机制引入的实现类,都是非单例的,每次调用NewInstanceServiceLoadernewServiceInstances方法就会创建所有实现类的实例。正如NewInstanceServiceLoader的javadoc注释说的一样:

SPI service loader for new instance for every call.

public static <T> Collection<T> newServiceInstances(final Class<T> service) {
    Collection<T> result = new LinkedList<>();
    if (null == SERVICE_MAP.get(service)) {
        return result;
    }
    for (Class<?> each : SERVICE_MAP.get(service)) {
        result.add((T) each.newInstance());
    }
    return result;
}

构造方法

private final ShardingRuntimeContext runtimeContext;

public ShardingDataSource(final Map<String, DataSource> dataSourceMap, final ShardingRule shardingRule, final Properties props) throws SQLException {
    super(dataSourceMap);
    checkDataSourceType(dataSourceMap);
    runtimeContext = new ShardingRuntimeContext(dataSourceMap, shardingRule, props, getDatabaseType());
}

private void checkDataSourceType(final Map<String, DataSource> dataSourceMap) {
    for (DataSource each : dataSourceMap.values()) {
        Preconditions.checkArgument(!(each instanceof MasterSlaveDataSource), "Initialized data sources can not be master-slave data sources.");
    }
}
  • 构造方法将dataSourceMap传给父类构造。
  • checkDataSourceType校验传入的DataSource不包含MasterSlaveDataSource
  • 构造ShardingRuntimeContext,提供getRuntimeContext方法的实现。

核心方法getConnection实现

getConnection方法就是new一个ShardingConnection返回给用户。

@Override
public final ShardingConnection getConnection() {
    return new ShardingConnection(getDataSourceMap(), runtimeContext, TransactionTypeHolder.get());
}

三、ShardingRuntimeContext

ShardingRuntimeContext是sharding-jdbc运行时的上下文对象,包含了所有运行时需要的信息。

1、RuntimeContext

RuntimeContext是sharding-jdbc运行时上下文抽象接口。

public interface RuntimeContext<T extends BaseRule> extends AutoCloseable {

    T getRule();

    ConfigurationProperties getProperties();
    
    DatabaseType getDatabaseType();
    
    ExecutorEngine getExecutorEngine();
    
    SQLParserEngine getSqlParserEngine();
}
  • getRule:获取BaseRule,最常用的就是ShardingRule,整个RuntimeContext就是针对某个BaseRule的。
  • getProperties:获取配置,比如获取sql.show=true等配置。
  • getDatabaseType:获取DatabaseTypeDataSourceType包含数据源类型(MySQL、Oracle),host、port、catalog、schema等信息。
  • getExecutorEngine:获取执行引擎,执行引擎的实现只有一个ExecutorEngine
  • getSqlParserEngine:获取sql解析引擎,解析引擎的实现只有一个SQLParserEngine

2、AbstractRuntimeContext

@Getter
public abstract class AbstractRuntimeContext<T extends BaseRule> implements RuntimeContext<T> {
    
    private final T rule;
    
    private final ConfigurationProperties properties;
    
    private final DatabaseType databaseType;
    
    private final ExecutorEngine executorEngine;
    
    private final SQLParserEngine sqlParserEngine;
    
    protected AbstractRuntimeContext(final T rule, final Properties props, final DatabaseType databaseType) {
        this.rule = rule;
        properties = new ConfigurationProperties(null == props ? new Properties() : props);
        this.databaseType = databaseType;
        executorEngine = new ExecutorEngine(properties.<Integer>getValue(ConfigurationPropertyKey.EXECUTOR_SIZE));
        sqlParserEngine = SQLParserEngineFactory.getSQLParserEngine(DatabaseTypes.getTrunkDatabaseTypeName(databaseType));
    }
    
    protected abstract ShardingSphereMetaData getMetaData();
    
    @Override
    public void close() throws Exception {
        executorEngine.close();
    }
}

AbstractRuntimeContext实现了所有RuntimeContext的抽象方法。

AbstractRuntimeContext的构造方法需要子类提供BaseRulePropertiesDatabaseType,并创建了ExecutorEngineSQLParserEngine

AbstractRuntimeContext需要子类实现ShardingSphereMetaData的获取方法。

3、MultipleDataSourcesRuntimeContext

ShardingSphereMetaData

@RequiredArgsConstructor
@Getter
public final class ShardingSphereMetaData {
    
    private final DataSourceMetas dataSources;
    
    private final SchemaMetaData schema;
}

ShardingSphereMetaData封装了两个成员变量:

  • DataSourceMetas:管理所有DataSourceMetaDataSourceMeta保存了host、port、catalog、schema。
public final class DataSourceMetas {
    private final Map<String, DataSourceMetaData> dataSourceMetaDataMap;
}
public interface DataSourceMetaData {
    String getHostName();
    int getPort();
    // 对于MySQL为库名
    String getCatalog();
    // 对于MySQL为Null
    String getSchema();
}
  • SchemaMetaData:管理所有表元数据。
public final class SchemaMetaData {
    // 逻辑表名 - 表元数据
    private final Map<String, TableMetaData> tables;
}
public final class TableMetaData {
    private final Map<String, ColumnMetaData> columns;
    private final Map<String, IndexMetaData> indexes;
    private final List<String> columnNames = new ArrayList<>();
    private final List<String> primaryKeyColumns = new ArrayList<>();
}

MultipleDataSourcesRuntimeContext

MultipleDataSourcesRuntimeContextAbstractRuntimeContext的基础上实现了ShardingSphereMetaData的getter方法,即MultipleDataSourcesRuntimeContext有获取DataSource和Table元数据信息的能力。

@Getter
public abstract class MultipleDataSourcesRuntimeContext<T extends BaseRule> extends AbstractRuntimeContext<T> {
    
    private final ShardingSphereMetaData metaData;
    
    protected MultipleDataSourcesRuntimeContext(final Map<String, DataSource> dataSourceMap, final T rule, final Properties props, final DatabaseType databaseType) throws SQLException {
        super(rule, props, databaseType);
        metaData = createMetaData(dataSourceMap, databaseType);
    }
    
    private ShardingSphereMetaData createMetaData(final Map<String, DataSource> dataSourceMap, final DatabaseType databaseType) throws SQLException {
        // 1. 创建DataSourceMetas
        DataSourceMetas dataSourceMetas = new DataSourceMetas(databaseType, getDatabaseAccessConfigurationMap(dataSourceMap));
        // 2. 子类实现创建SchemaMetaData
        SchemaMetaData schemaMetaData = loadSchemaMetaData(dataSourceMap);
        ShardingSphereMetaData result = new ShardingSphereMetaData(dataSourceMetas, schemaMetaData);
        return result;
    }
    
    private Map<String, DatabaseAccessConfiguration> getDatabaseAccessConfigurationMap(final Map<String, DataSource> dataSourceMap) throws SQLException {
        Map<String, DatabaseAccessConfiguration> result = new LinkedHashMap<>(dataSourceMap.size(), 1);
        for (Entry<String, DataSource> entry : dataSourceMap.entrySet()) {
            DataSource dataSource = entry.getValue();
            try (Connection connection = dataSource.getConnection()) {
                DatabaseMetaData metaData = connection.getMetaData();
                result.put(entry.getKey(), new DatabaseAccessConfiguration(metaData.getURL(), metaData.getUserName(), null));
            }
        }
        return result;
    }
    
    protected abstract SchemaMetaData loadSchemaMetaData(Map<String, DataSource> dataSourceMap) throws SQLException;
}

首先MultipleDataSourcesRuntimeContext在构造方法中也需要子类提供BaseRulePropertiesDatabaseType

接下来createMetaData创建ShardingSphereMetaData,其中DataSourceMetas的创建由MultipleDataSourcesRuntimeContext实现,子类需要实现loadSchemaMetaData方法创建SchemaMetaData

4、ShardingRuntimeContext

@Getter
public final class ShardingRuntimeContext extends MultipleDataSourcesRuntimeContext<ShardingRule> {
    
    private final CachedDatabaseMetaData cachedDatabaseMetaData;
    
    private final ShardingTransactionManagerEngine shardingTransactionManagerEngine;
    
    public ShardingRuntimeContext(final Map<String, DataSource> dataSourceMap, final ShardingRule shardingRule, final Properties props, final DatabaseType databaseType) throws SQLException {
        super(dataSourceMap, shardingRule, props, databaseType);
        cachedDatabaseMetaData = createCachedDatabaseMetaData(dataSourceMap);
        shardingTransactionManagerEngine = new ShardingTransactionManagerEngine();
        shardingTransactionManagerEngine.init(databaseType, dataSourceMap);
    }
}

CachedDatabaseMetaData

ShardingRuntimeContext缓存了java.sql.DatabaseMetaData

CachedDatabaseMetaData就是由DatabaseMetaData创建的。

public final class ShardingRuntimeContext extends MultipleDataSourcesRuntimeContext<ShardingRule> {
    
    private final CachedDatabaseMetaData cachedDatabaseMetaData;
    
    public ShardingRuntimeContext(final Map<String, DataSource> dataSourceMap, final ShardingRule shardingRule, final Properties props, final DatabaseType databaseType) throws SQLException {
        super(dataSourceMap, shardingRule, props, databaseType);
        cachedDatabaseMetaData = createCachedDatabaseMetaData(dataSourceMap);
        // ...
    }
    private CachedDatabaseMetaData createCachedDatabaseMetaData(final Map<String, DataSource> dataSourceMap) throws SQLException {
        try (Connection connection = dataSourceMap.values().iterator().next().getConnection()) {
            return new CachedDatabaseMetaData(connection.getMetaData());
        }
    }
 }

CachedDatabaseMetaData只提供一些getter方法,所有的属性都来源于java.sql.DatabaseMetaData

/**
 * Cached database meta data.
 */
@Getter
public final class CachedDatabaseMetaData {
    private final String url;
    private final String userName;
    private final String databaseProductName;
    private final String databaseProductVersion;
    private final String driverName;
    private final String driverVersion;
    // ... 省略其他从DatabaseMetaData获取的属性
}

ShardingTransactionManagerEngine

由于ShardingDatasource可能操作多个数据源,会导致分布式事务存在,所以在这里引入了分片事务处理引擎ShardingTransactionManagerEngine

public final class ShardingTransactionManagerEngine {
    
    private final Map<TransactionType, ShardingTransactionManager> transactionManagerMap = new EnumMap<>(TransactionType.class);
    
    public ShardingTransactionManagerEngine() {
        loadShardingTransactionManager();
    }
    
    private void loadShardingTransactionManager() {
        for (ShardingTransactionManager each : ServiceLoader.load(ShardingTransactionManager.class)) {
            if (transactionManagerMap.containsKey(each.getTransactionType())) {
                continue;
            }
            transactionManagerMap.put(each.getTransactionType(), each);
        }
    }
}

public enum TransactionType {
    LOCAL, 
    XA, 
    BASE
}

对于不同的TransactionType会通过SPI对应一个ShardingTransactionManager实例。XA对应XAShardingTransactionManagerBASE对应SeataATShardingTransactionManager

实现loadSchemaMetaData

ShardingDataSource实现了MultipleDataSourcesRuntimeContext父类定义的loadSchemaMetaData方法。

@Override
protected SchemaMetaData loadSchemaMetaData(final Map<String, DataSource> dataSourceMap) throws SQLException {
    // 一次查询请求在每个数据库实例中所能使用的最大连接数,默认1
    int maxConnectionsSizePerQuery = getProperties().<Integer>getValue(ConfigurationPropertyKey.MAX_CONNECTIONS_SIZE_PER_QUERY);
    // 是否在启动时检查分表元数据一致性,默认false
    boolean isCheckingMetaData = getProperties().<Boolean>getValue(ConfigurationPropertyKey.CHECK_TABLE_METADATA_ENABLED);
    // 创建ShardingMetaDataLoader加载SchemaMetaData
    SchemaMetaData result = new ShardingMetaDataLoader(dataSourceMap, getRule(), maxConnectionsSizePerQuery, isCheckingMetaData).load(getDatabaseType());
    // 使用ShardingTableMetaDataDecorator对SchemaMetadata做二次装饰
    // 如果有主键生成策略,设置SchemaMetadata.TableMetaData.ColumnMetaData.generated置为true
    // 代表这一列需要生成主键ID
    result = SchemaMetaDataDecorator.decorate(result, getRule(), new ShardingTableMetaDataDecorator());
    // 如果有加密规则,使用EncryptTableMetaDataDecorator对SchemaMetadata做二次装饰
    // 将脱敏字段SchemaMetadata.TableMetaData.ColumnMetaData封装为EncryptColumnMetaData
    // 扩展三个字段 密文列、原文列、辅助查询列
    if (!getRule().getEncryptRule().getEncryptTableNames().isEmpty()) {
        result = SchemaMetaDataDecorator.decorate(result, getRule().getEncryptRule(), new EncryptTableMetaDataDecorator());
    }
    return result;
}

总结

  • ShardingRuleConfiguration运行时转换为ShardingRule包含了所有分片配置信息。
  • 多数据源作为ShardingDataSource的成员变量而存在,具体是由AbstractDataSourceAdapter管理并提供getter方法。
  • ShardingRuntimeContext是运行上下文,持有ShardingRule分片规则、各种引擎(sql解析引擎、sql执行引擎、事务引擎)、元数据信息(数据源、表)。
  • sharding-jdbc通过WrapperAdapter记录对象方法执行,当sql解析完成后,在合适的时机重放这些方法。让用户使用传统java.sql相关接口不用关心底层分片执行逻辑,解决了用户调用时序与实际执行时序不一致的问题。