当前位置: 首页 > news >正文

【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理

 Sharding-JDBC系列

1、Sharding-JDBC分库分表的基本使用

2、Sharding-JDBC分库分表之SpringBoot分片策略

3、Sharding-JDBC分库分表之SpringBoot主从配置

4、SpringBoot集成Sharding-JDBC-5.3.0分库分表

5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表

6、【源码】Sharding-JDBC源码分析之JDBC

7、【源码】Sharding-JDBC源码分析之SPI机制

8、【源码】Sharding-JDBC源码分析之Yaml分片配置文件解析原理

9、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(一)

10、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(二)

11、【源码】Sharding-JDBC源码分析之Yaml分片配置转换原理

12、【源码】Sharding-JDBC源码分析之ShardingSphereDataSource的创建原理

13、【源码】Sharding-JDBC源码分析之ContextManager创建中mode分片配置信息的持久化存储的原理

14、【源码】Sharding-JDBC源码分析之ContextManager创建中ShardingSphereDatabase的创建原理

15、【源码】Sharding-JDBC源码分析之分片规则生成器DatabaseRuleBuilder实现规则配置到规则对象的生成原理

16、【源码】Sharding-JDBC源码分析之配置数据库定义的表的元数据解析原理

17、【源码】Sharding-JDBC源码分析之ShardingSphereConnection的创建原理

18、【源码】Sharding-JDBC源码分析之ShardingSpherePreparedStatement的创建原理

19、【源码】Sharding-JDBC源码分析之Sql解析的原理

20、【源码】Sharding-JDBC源码分析之SQL路由及SingleSQLRouter单表路由

21、【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理

前言

ShardingSphere透明的为Java应用程序提供了数据库分片功能,只需配置好分片规则,无需关心底层的数据库分片细节。ShardingSphere框架根据配置好的分片规则,自动路由到实际操作的数据库、表中。本文从源码的角度分析 SQL 路由中的分片路由器ShardingSQLRouter的实现原理。

ShardingSpherePreparedStatement回顾

在【源码】Sharding-JDBC源码分析之SQL路由及SingleSQLRouter单表路由-CSDN博客中分析在执行SQL语句前,会进行SQL路由,创建RouteContext对象,在RouteContext路由上下文对象中,包含了SQL真正执行的数据源、逻辑表及真实表的映射。如果是DML操作,且配置了分片键,则ShardingSQLRouter分片路由器会第一个执行。

ShardingSQLRouter

如果没有通过提示语指定路由的数据源,则从SPI获取路由器之后,优先执行ShardingSQLRouter分片路由器,即执行路由器的createRouteContext()方法。在该类中,对应的decorateRouteContext()方法为空方法。

ShardingSQLRouter的源码如下:

package org.apache.shardingsphere.sharding.route.engine;/*** 分片SQL路由器*/
public final class ShardingSQLRouter implements SQLRouter<ShardingRule> {/*** 创建路由上下文* @param queryContext 查询上下文* @param database 数据库信息* @param rule 分片规则* @param props 配置的属性* @param connectionContext 连接上下文* @return*/@SuppressWarnings({"rawtypes", "unchecked"})@Overridepublic RouteContext createRouteContext(final QueryContext queryContext, final ShardingSphereDatabase database, final ShardingRule rule,final ConfigurationProperties props, final ConnectionContext connectionContext) {// 获取对应数据库、对应查询类型的SQL语句对象SQLStatement sqlStatement = queryContext.getSqlStatementContext().getSqlStatement();// 解析,获取分片条件ShardingConditions shardingConditions = createShardingConditions(queryContext, database, rule);// 获取分片语句校验器,查询语句返回空Optional<ShardingStatementValidator> validator = ShardingStatementValidatorFactory.newInstance(sqlStatement, shardingConditions);// 校验validator.ifPresent(optional -> optional.preValidate(rule, queryContext.getSqlStatementContext(), queryContext.getParameters(), database, props));if (sqlStatement instanceof DMLStatement && shardingConditions.isNeedMerge()) {shardingConditions.merge();}// 创建路由引擎对象,执行路由,获取路由上下文RouteContext result = ShardingRouteEngineFactory.newInstance(rule, database, queryContext, shardingConditions, props, connectionContext).route(rule);// 校验validator.ifPresent(optional -> optional.postValidate(rule, queryContext.getSqlStatementContext(), queryContext.getHintValueContext(), queryContext.getParameters(), database, props, result));return result;}/*** 创建分片条件对象* @param queryContext 查询上下文* @param database 数据库信息* @param rule 分片规则* @return*/@SuppressWarnings({"rawtypes", "unchecked"})private ShardingConditions createShardingConditions(final QueryContext queryContext, final ShardingSphereDatabase database, final ShardingRule rule) {List<ShardingCondition> shardingConditions;// 如果SQL操作是DML || 游标if (queryContext.getSqlStatementContext().getSqlStatement() instanceof DMLStatement || queryContext.getSqlStatementContext() instanceof CursorAvailable) {// 创建分片条件引擎的新实例。通过SPI获取,默认返回DefaultShardingConditionEngineShardingConditionEngine shardingConditionEngine = ShardingConditionEngineFactory.createShardingConditionEngine(database, rule);// 通过引擎创建分片条件shardingConditions = shardingConditionEngine.createShardingConditions(queryContext.getSqlStatementContext(), queryContext.getParameters());} else {shardingConditions = Collections.emptyList();}// 创建分片条件集合对象return new ShardingConditions(shardingConditions, queryContext.getSqlStatementContext(), rule);}@Overridepublic void decorateRouteContext(final RouteContext routeContext, final QueryContext queryContext, final ShardingSphereDatabase database, final ShardingRule rule,final ConfigurationProperties props, final ConnectionContext connectionContext) {// TODO}@Overridepublic int getOrder() {return ShardingOrder.ORDER;}@Overridepublic Class<ShardingRule> getTypeClass() {return ShardingRule.class;}
}

3.1 createRouteContext()

在createRouteContext()方法中,主要执行如下:

1)获取SQL语句对象;

2)调用createShardingConditions()方法,解析,获取分片条件集合对象ShardingConditions;

2.1)ShardingConditions包含ShardingCondition集合、sql语句对象及分片规则对象;

2.2)每个分片键对应一个ShardingCondition;

2.3)ShardingCondition中存放分片键的信息,包含分片键的下标、列名、表名、分片的值;

2.4)分片的值主要为两种类型,一种是固定值,用于表示 =、in;另一种为区间值,用于表示 >、< 等区间条件;

3)安全性校验;

4)对于DML语句,如果需要条件合并,则进行条件合并;

5)调用ShardingRouteEngineFactory.newInstance(),创建分片路由引擎对象,执行route()路由方法,获取路由上下文;

5.1)通过ShardingRouteEngineFactory.newInstance(),获取分片路由引擎。对于DML语句的普通分片规则,返回ShardingStandardRoutingEngine对象;

5.2)在ShardingStandardRoutingEngine的route()方法中,根据配置的规则,创建对应的分片策略,执行对应doSharding()方法,传入参数值,获取真正执行的数据库、表信息DataNode;

5.3)根据DataNode,创建路由单元,保存到新创建的RouteContext对象中;

6)安全性校验;

7)返回创建的路由上下文;

3.2 createShardingConditions()

在createShardingConditions()方法中,执行如下:

1)如果SQL操作是DML语句 || 游标类型的语句,则获取分片条件引擎对象,默认为DefaultShardingConditionEngine,执行DefaultShardingConditionEngine的createShardingConditions()方法,创建ShardingCondition集合;

1.1)SQL语句中涉及分片键的有两种类型,一种为插入语句,另一种带where的语句。插入语句的分片键在插入的值中,其他类型的语句分片键在where条件中;

1.2)如果是插入语句,则通过InsertClauseShardingConditionEngine创建ShardingCondition集合;

1.3)其他类型语句,则通过WhereClauseShardingConditionEngine创建ShardingCondition集合;

2)否则不支持分片规则,ShardingCondition集合为空;

3)创建一个ShardingConditions对象,传入上面获取的ShardingCondition集合;

WhereClauseShardingConditionEngine

WhereClauseShardingConditionEngine为从 where 条件中解析出ShardingCondition分片条件的引擎。在该类中,通过解析SQL语句中的where条件部分,结合配置的分片规则,从中找出符合条件的分片键,以及分片键对应的值。

WhereClauseShardingConditionEngine的源码如下:

package org.apache.shardingsphere.sharding.route.engine.condition.engine.impl;/*** where分片条件引擎*/
@RequiredArgsConstructor
public final class WhereClauseShardingConditionEngine {// 分片规则private final ShardingRule shardingRule;// 数据库信息private final ShardingSphereDatabase database;/*** 创建分片条件,每个分片键对应一个ShardingCondition* @param sqlStatementContext* @param params sql语句中的参数值* @return*/public List<ShardingCondition> createShardingConditions(final SQLStatementContext<?> sqlStatementContext, final List<Object> params) {if (!(sqlStatementContext instanceof WhereAvailable)) {return Collections.emptyList();}// 获取where中用到的列,包括子查询Collection<ColumnSegment> columnSegments = ((WhereAvailable) sqlStatementContext).getColumnSegments();// 获取默认的schema的名称。默认为databaseName,即logic_dbString defaultSchemaName = DatabaseTypeEngine.getDefaultSchemaName(sqlStatementContext.getDatabaseType(), database.getName());// 获取schemaShardingSphereSchema schema = sqlStatementContext.getTablesContext().getSchemaName().map(database::getSchema).orElseGet(() -> database.getSchema(defaultSchemaName));// key:列名;value为表名Map<String, String> columnExpressionTableNames = sqlStatementContext.getTablesContext().findTableNamesByColumnSegment(columnSegments, schema);List<ShardingCondition> result = new ArrayList<>();for (WhereSegment each : ((WhereAvailable) sqlStatementContext).getWhereSegments()) {// each.getExpr()返回where条件中的二元运算等表达式,如BinaryOperationExpression// createShardingConditions():解析条件表达式中的分片键ShardingCondition对象result.addAll(createShardingConditions(each.getExpr(), params, columnExpressionTableNames));}return result;}/*** 从where表达式中创建分片条件对象。* 解析条件表达式中的分片键,* 1)如果是一个or条件,前后都有分片键,则创建两个ShardingCondition;* 2)如果是 and 条件,最多创建一个 ShardingCondition;* @param expression where条件中的某个表达式* @param params 参数值* @param columnExpressionTableNames* @return*/private Collection<ShardingCondition> createShardingConditions(final ExpressionSegment expression, final List<Object> params, final Map<String, String> columnExpressionTableNames) {// 获取And的谓词。// 小于等于1个and,创建一个AndPredicate。一个or创建两个AndPredicate。多个and创建一个AndPredicate,其中的predicates包含多个Collection<AndPredicate> andPredicates = ExpressionExtractUtil.getAndPredicates(expression);Collection<ShardingCondition> result = new LinkedList<>();for (AndPredicate each : andPredicates) {// 从谓词中创建分片条件值Map。key为对应的列,value为对应列的分片值对象ShardingConditionValueMap<Column, Collection<ShardingConditionValue>> shardingConditionValues = createShardingConditionValueMap(each.getPredicates(), params, columnExpressionTableNames);// 如果没有分片键,则返回空if (shardingConditionValues.isEmpty()) {return Collections.emptyList();}// 创建分片条件。一个And谓词一个ShardingCondition对象ShardingCondition shardingCondition = createShardingCondition(shardingConditionValues);// TODO remove startIndex when federation has perfect support for subqueryshardingCondition.setStartIndex(expression.getStartIndex());result.add(shardingCondition);}return result;}/*** 从谓词中创建分片条件值Map。key为对应的列,value为对应列的分片值对象ShardingConditionValue* @param predicates 条件中的谓词,如BinaryOperationExpression等* @param params 传入的参数* @param columnTableNames 列及对应表的Map* @return*/private Map<Column, Collection<ShardingConditionValue>> createShardingConditionValueMap(final Collection<ExpressionSegment> predicates,final List<Object> params, final Map<String, String> columnTableNames) {Map<Column, Collection<ShardingConditionValue>> result = new HashMap<>(predicates.size(), 1);// 遍历谓词的表达式for (ExpressionSegment each : predicates) {// 从谓词表达式部分中提取column部分for (ColumnSegment columnSegment : ColumnExtractor.extract(each)) {// 获取列对应的表Optional<String> tableName = Optional.ofNullable(columnTableNames.get(columnSegment.getExpression()));// 从列对应的表中查找配置的分片策略,如果配置的分片策略使用了对应columnName,则返回columnName;否则返回空Optional<String> shardingColumn = tableName.flatMap(optional -> shardingRule.findShardingColumn(columnSegment.getIdentifier().getValue(), optional));// 如果对应列没有配置分片策略,则跳过if (!tableName.isPresent() || !shardingColumn.isPresent()) {continue;}Column column = new Column(shardingColumn.get(), tableName.get());// 创建分片条件值,不同的表达式,场景不同的值。如 =、in,创建ListShardingConditionValue;>、< 等表达式,创建RangeShardingConditionValueOptional<ShardingConditionValue> shardingConditionValue = ConditionValueGeneratorFactory.generate(each, column, params);if (!shardingConditionValue.isPresent()) {continue;}result.computeIfAbsent(column, unused -> new LinkedList<>()).add(shardingConditionValue.get());}}return result;}/*** 创建分片条件对象* @param shardingConditionValues* @return*/private ShardingCondition createShardingCondition(final Map<Column, Collection<ShardingConditionValue>> shardingConditionValues) {ShardingCondition result = new ShardingCondition();for (Entry<Column, Collection<ShardingConditionValue>> entry : shardingConditionValues.entrySet()) {try {// 同一个列的多个分片条件值合并为一个ShardingConditionValue shardingConditionValue = mergeShardingConditionValues(entry.getKey(), entry.getValue());if (shardingConditionValue instanceof AlwaysFalseShardingConditionValue) {return new AlwaysFalseShardingCondition();}result.getValues().add(shardingConditionValue);} catch (final ClassCastException ex) {throw new ShardingValueDataTypeException(entry.getKey());}}return result;}/*** 同一个谓词中的同一个列的多个分片条件值合并为一个* @param column* @param shardingConditionValues* @return*/@SuppressWarnings({"unchecked", "rawtypes"})private ShardingConditionValue mergeShardingConditionValues(final Column column, final Collection<ShardingConditionValue> shardingConditionValues) {Collection<Comparable<?>> listValue = null;Range<Comparable<?>> rangeValue = null;Set<Integer> parameterMarkerIndexes = new HashSet<>();for (ShardingConditionValue each : shardingConditionValues) {// 添加下标parameterMarkerIndexes.addAll(each.getParameterMarkerIndexes());// =、in 的处理if (each instanceof ListShardingConditionValue) {// 获取两个Collection集合的交集// 对于确定的值,如age = 15 and age in (15, 20, 21),则最终只能是查询出age = 15的记录,// 所以此处要进行集合的交集运算[15]和[15, 20, 21],结果为15listValue = mergeListShardingValues(((ListShardingConditionValue) each).getValues(), listValue);// 如果没有交集,则返回falseif (listValue.isEmpty()) {return new AlwaysFalseShardingConditionValue();}} else if (each instanceof RangeShardingConditionValue) { // 区间值的处理try {// 值合并rangeValue = mergeRangeShardingValues(((RangeShardingConditionValue) each).getValueRange(), rangeValue);} catch (final IllegalArgumentException ex) {return new AlwaysFalseShardingConditionValue();}}}if (null == listValue) {return new RangeShardingConditionValue<>(column.getName(), column.getTableName(), rangeValue, new ArrayList<>(parameterMarkerIndexes));}if (null == rangeValue) {return new ListShardingConditionValue<>(column.getName(), column.getTableName(), listValue, new ArrayList<>(parameterMarkerIndexes));}listValue = mergeListAndRangeShardingValues(listValue, rangeValue);return listValue.isEmpty() ? new AlwaysFalseShardingConditionValue(): new ListShardingConditionValue<>(column.getName(), column.getTableName(), listValue, new ArrayList<>(parameterMarkerIndexes));}/*** 保留value1和value2交集的值* @param value1* @param value2* @return*/private Collection<Comparable<?>> mergeListShardingValues(final Collection<Comparable<?>> value1, final Collection<Comparable<?>> value2) {if (null == value2) {return value1;}// 保留value1和value2交集的值value1.retainAll(value2);return value1;}/*** 整合区间值* @param value1* @param value2* @return*/private Range<Comparable<?>> mergeRangeShardingValues(final Range<Comparable<?>> value1, final Range<Comparable<?>> value2) {return null == value2 ? value1 : SafeNumberOperationUtil.safeIntersection(value1, value2);}private Collection<Comparable<?>> mergeListAndRangeShardingValues(final Collection<Comparable<?>> listValue, final Range<Comparable<?>> rangeValue) {Collection<Comparable<?>> result = new LinkedList<>();for (Comparable<?> each : listValue) {if (SafeNumberOperationUtil.safeContains(rangeValue, each)) {result.add(each);}}return result;}
}

在createShardingConditions()方法中,主要执行如下:

1)获取where中用到的列,包括子查询部分;

2)获取默认的schema的名称。默认为databaseName,即logic_db;

3)获取列信息,为Map对象,key:列名;value为表名;

4)遍历查询语句的where部分,查询where部分中的分片键,创建ShardingCondition对象;

4.1)从where部分的表达式中获取AndPredicate谓词;

4.2)遍历AndPredicate谓词,每个谓词根据其中是否包含配置的分片键,以及值类型,创建ShardingCondition对象;

4.3)ShardingCondition对象中保存ShardingConditionValue集合。ShardingConditionValue包含了分片键、所属的表、值;

4.4)ShardingConditionValue主要包含两种类型:ListShardingConditionValue(确定的值,如 =、in 的条件)、RangeShardingConditionValue(区间值,如 > 、>= 等);

where部分解析的大体流程如下:

ShardingStandardRoutingEngine

在ShardingSQLRouter的createRouteContext()方法中,通过ShardingRouteEngineFactory.newInstance(),获取分片路由引擎。对于DML语句的普通分片规则,返回ShardingStandardRoutingEngine对象。

package org.apache.shardingsphere.sharding.route.engine.type.standard;/*** 标准分片路由引擎*/
public final class ShardingStandardRoutingEngine implements ShardingRouteEngine {// 逻辑表。有分片键的表或语句中的第一个表private final String logicTableName;// 分片键的分片条件信息对象private final ShardingConditions shardingConditions;// sql语句private final SQLStatementContext<?> sqlStatementContext;// 配置的propsprivate final ConfigurationProperties props;// SQl 操作的涉及的分片数据节点集合private final Collection<Collection<DataNode>> originalDataNodes = new LinkedList<>();// SQL 语句中的提示提取器private final SQLHintExtractor sqlHintExtractor;public ShardingStandardRoutingEngine(final String logicTableName, final ShardingConditions shardingConditions, final SQLStatementContext<?> sqlStatementContext,final HintValueContext hintValueContext, final ConfigurationProperties props) {this.logicTableName = logicTableName;this.shardingConditions = shardingConditions;this.sqlStatementContext = sqlStatementContext;this.props = props;this.sqlHintExtractor = new SQLHintExtractor(sqlStatementContext.getSqlStatement(), hintValueContext);}/*** 创建路由上下文* @param shardingRule sharding rule* @return*/@Overridepublic RouteContext route(final ShardingRule shardingRule) {RouteContext result = new RouteContext();// 获取当前sql分片条件对应的操作的数据节点。(操作的数据库及表)Collection<DataNode> dataNodes = getDataNodes(shardingRule, shardingRule.getTableRule(logicTableName));result.getOriginalDataNodes().addAll(originalDataNodes);for (DataNode each : dataNodes) {result.getRouteUnits().add(new RouteUnit(new RouteMapper(each.getDataSourceName(), each.getDataSourceName()), Collections.singleton(new RouteMapper(logicTableName, each.getTableName()))));}return result;}/*** 获取数据节点* @param shardingRule* @param tableRule* @return*/private Collection<DataNode> getDataNodes(final ShardingRule shardingRule, final TableRule tableRule) {// 创建数据源分片算法对象ShardingStrategy databaseShardingStrategy = createShardingStrategy(shardingRule.getDatabaseShardingStrategyConfiguration(tableRule),shardingRule.getShardingAlgorithms(), shardingRule.getDefaultShardingColumn());// 创建表分片算法对象ShardingStrategy tableShardingStrategy = createShardingStrategy(shardingRule.getTableShardingStrategyConfiguration(tableRule),shardingRule.getShardingAlgorithms(), shardingRule.getDefaultShardingColumn());// 是否数据源和表规则都是Hint策略if (isRoutingByHint(shardingRule, tableRule)) {return routeByHint(tableRule, databaseShardingStrategy, tableShardingStrategy);}// 是否数据源和表规则都不是Hint策略if (isRoutingByShardingConditions(shardingRule, tableRule)) {return routeByShardingConditions(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy);}// 混合策略return routeByMixedConditions(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy);}/*** 是否为数据源和表规则都是Hint策略* @param shardingRule* @param tableRule* @return*/private boolean isRoutingByHint(final ShardingRule shardingRule, final TableRule tableRule) {return shardingRule.getDatabaseShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration&& shardingRule.getTableShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration;}/*** 判断sql的hint中是否包含了sql语句中的表* @return*/private boolean isRoutingBySQLHint() {Collection<String> tableNames = sqlStatementContext.getTablesContext().getTableNames();for (String each : tableNames) {if (sqlHintExtractor.containsHintShardingValue(each)) {return true;}}return false;}/*** Hint的路由* @param tableRule* @param databaseShardingStrategy* @param tableShardingStrategy* @return*/private Collection<DataNode> routeByHint(final TableRule tableRule, final ShardingStrategy databaseShardingStrategy, final ShardingStrategy tableShardingStrategy) {return route0(tableRule, databaseShardingStrategy, getDatabaseShardingValuesFromHint(), tableShardingStrategy, getTableShardingValuesFromHint());}/*** 是否数据源和表规则都不是Hint策略* @param shardingRule* @param tableRule* @return*/private boolean isRoutingByShardingConditions(final ShardingRule shardingRule, final TableRule tableRule) {return !(shardingRule.getDatabaseShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration|| shardingRule.getTableShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration);}/*** 通过分片条件路由* @param shardingRule* @param tableRule* @param databaseShardingStrategy* @param tableShardingStrategy* @return*/private Collection<DataNode> routeByShardingConditions(final ShardingRule shardingRule, final TableRule tableRule,final ShardingStrategy databaseShardingStrategy, final ShardingStrategy tableShardingStrategy) {return shardingConditions.getConditions().isEmpty()? route0(tableRule, databaseShardingStrategy, Collections.emptyList(), tableShardingStrategy, Collections.emptyList()): routeByShardingConditionsWithCondition(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy);}/*** 通过分片条件作为条件路由* @param shardingRule* @param tableRule* @param databaseShardingStrategy* @param tableShardingStrategy* @return*/private Collection<DataNode> routeByShardingConditionsWithCondition(final ShardingRule shardingRule, final TableRule tableRule,final ShardingStrategy databaseShardingStrategy, final ShardingStrategy tableShardingStrategy) {Collection<DataNode> result = new LinkedList<>();for (ShardingCondition each : shardingConditions.getConditions()) {Collection<DataNode> dataNodes = route0(tableRule,databaseShardingStrategy, getShardingValuesFromShardingConditions(shardingRule, databaseShardingStrategy.getShardingColumns(), each),tableShardingStrategy, getShardingValuesFromShardingConditions(shardingRule, tableShardingStrategy.getShardingColumns(), each));result.addAll(dataNodes);originalDataNodes.add(dataNodes);}return result;}/*** 混合条件的路由,即包含Hint、又包含其他的规则* @param shardingRule* @param tableRule* @param databaseShardingStrategy* @param tableShardingStrategy* @return*/private Collection<DataNode> routeByMixedConditions(final ShardingRule shardingRule, final TableRule tableRule,final ShardingStrategy databaseShardingStrategy, final ShardingStrategy tableShardingStrategy) {return shardingConditions.getConditions().isEmpty()? routeByMixedConditionsWithHint(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy): routeByMixedConditionsWithCondition(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy);}/*** 通过解析的分片条件进行混合条件的路由* @param shardingRule* @param tableRule* @param databaseShardingStrategy* @param tableShardingStrategy* @return*/private Collection<DataNode> routeByMixedConditionsWithCondition(final ShardingRule shardingRule, final TableRule tableRule,final ShardingStrategy databaseShardingStrategy, final ShardingStrategy tableShardingStrategy) {Collection<DataNode> result = new LinkedList<>();for (ShardingCondition each : shardingConditions.getConditions()) {Collection<DataNode> dataNodes = route0(tableRule, databaseShardingStrategy,getDatabaseShardingValues(shardingRule, databaseShardingStrategy, each), tableShardingStrategy, getTableShardingValues(shardingRule, tableShardingStrategy, each));result.addAll(dataNodes);originalDataNodes.add(dataNodes);}return result;}/*** 有包含hint的复合条件* @param shardingRule* @param tableRule* @param databaseShardingStrategy* @param tableShardingStrategy* @return*/private Collection<DataNode> routeByMixedConditionsWithHint(final ShardingRule shardingRule, final TableRule tableRule,final ShardingStrategy databaseShardingStrategy, final ShardingStrategy tableShardingStrategy) {// 其中table规则是Hintif (shardingRule.getDatabaseShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration) {return route0(tableRule, databaseShardingStrategy, getDatabaseShardingValuesFromHint(), tableShardingStrategy, Collections.emptyList());}// database规则为Hintreturn route0(tableRule, databaseShardingStrategy, Collections.emptyList(), tableShardingStrategy, getTableShardingValuesFromHint());}/*** 获取数据库的分片条件值* @param shardingRule* @param databaseShardingStrategy* @param shardingCondition* @return*/private List<ShardingConditionValue> getDatabaseShardingValues(final ShardingRule shardingRule, final ShardingStrategy databaseShardingStrategy, final ShardingCondition shardingCondition) {return isGettingShardingValuesFromHint(databaseShardingStrategy)// 如果是Hint类型的策略,从Hint中获取? getDatabaseShardingValuesFromHint()// 如果不是Hint类型的策略,从分片条件对象中获取分片值: getShardingValuesFromShardingConditions(shardingRule, databaseShardingStrategy.getShardingColumns(), shardingCondition);}/*** 获取分片表的值* @param shardingRule* @param tableShardingStrategy* @param shardingCondition* @return*/private List<ShardingConditionValue> getTableShardingValues(final ShardingRule shardingRule, final ShardingStrategy tableShardingStrategy, final ShardingCondition shardingCondition) {return isGettingShardingValuesFromHint(tableShardingStrategy)? getTableShardingValuesFromHint(): getShardingValuesFromShardingConditions(shardingRule, tableShardingStrategy.getShardingColumns(), shardingCondition);}/*** 判断策略是否为Hint类型的策略* @param shardingStrategy* @return*/private boolean isGettingShardingValuesFromHint(final ShardingStrategy shardingStrategy) {return shardingStrategy instanceof HintShardingStrategy;}/*** 从Hint中获取数据库的分片条件值* @return*/private List<ShardingConditionValue> getDatabaseShardingValuesFromHint() {if (isRoutingBySQLHint()) {return getDatabaseShardingValuesFromSQLHint();}return getShardingConditions(HintManager.isDatabaseShardingOnly() ? HintManager.getDatabaseShardingValues() : HintManager.getDatabaseShardingValues(logicTableName));}/*** 从SQL语句中的Hint信息中获取数据库的分片条件值* @return*/private List<ShardingConditionValue> getDatabaseShardingValuesFromSQLHint() {Collection<Comparable<?>> shardingValues = new LinkedList<>();Collection<String> tableNames = sqlStatementContext.getTablesContext().getTableNames();for (String each : tableNames) {// 如果是sql语句中的第一个表if (each.equals(logicTableName) && sqlHintExtractor.containsHintShardingDatabaseValue(each)) {// 创建分片条件值shardingValues.addAll(sqlHintExtractor.getHintShardingDatabaseValue(each));}}return getShardingConditions(shardingValues);}/*** 从Hint中获取分片表的条件值。*/private List<ShardingConditionValue> getTableShardingValuesFromHint() {if (isRoutingBySQLHint()) {return getTableShardingValuesFromSQLHint();}return getShardingConditions(HintManager.getTableShardingValues(logicTableName));}/*** 从SQL的hint中获取分片表的值* @return*/private List<ShardingConditionValue> getTableShardingValuesFromSQLHint() {Collection<Comparable<?>> shardingValues = new LinkedList<>();Collection<String> tableNames = sqlStatementContext.getTablesContext().getTableNames();for (String each : tableNames) {if (each.equals(logicTableName) && sqlHintExtractor.containsHintShardingTableValue(each)) {shardingValues.addAll(sqlHintExtractor.getHintShardingTableValue(each));}}return getShardingConditions(shardingValues);}/*** 创建分片条件值集合。如果传入的集合不为空,则创建ListShardingConditionValue对象,否则为空集合* @param shardingValue* @return*/private List<ShardingConditionValue> getShardingConditions(final Collection<Comparable<?>> shardingValue) {return shardingValue.isEmpty() ? Collections.emptyList() : Collections.singletonList(new ListShardingConditionValue<>("", logicTableName, shardingValue));}/*** 从分片条件对象中获取分片值* @param shardingRule* @param shardingColumns* @param shardingCondition* @return*/private List<ShardingConditionValue> getShardingValuesFromShardingConditions(final ShardingRule shardingRule, final Collection<String> shardingColumns, final ShardingCondition shardingCondition) {List<ShardingConditionValue> result = new ArrayList<>(shardingColumns.size());// 遍历分片条件for (ShardingConditionValue each : shardingCondition.getValues()) {// 查找绑定关联表Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(each.getTableName());// (等于逻辑表 || 有关联表 && 关联表是逻辑表) && 属于分片键,则将该分片条件值加入到result中if ((logicTableName.equals(each.getTableName()) || bindingTableRule.isPresent() && bindingTableRule.get().hasLogicTable(logicTableName))&& shardingColumns.contains(each.getColumnName())) {result.add(each);}}return result;}/*** 路由* @param tableRule 表规则* @param databaseShardingStrategy 配置的数据源分片策略* @param databaseShardingValues 数据源分片值* @param tableShardingStrategy 配置的表分片策略* @param tableShardingValues 表的分片值* @return*/private Collection<DataNode> route0(final TableRule tableRule,final ShardingStrategy databaseShardingStrategy, final List<ShardingConditionValue> databaseShardingValues,final ShardingStrategy tableShardingStrategy, final List<ShardingConditionValue> tableShardingValues) {// 数据源路由Collection<String> routedDataSources = routeDataSources(tableRule, databaseShardingStrategy, databaseShardingValues);Collection<DataNode> result = new LinkedList<>();// 遍历数据源for (String each : routedDataSources) {// 表路由result.addAll(routeTables(tableRule, each, tableShardingStrategy, tableShardingValues));}return result;}/*** 数据源路由* @param tableRule* @param databaseShardingStrategy* @param databaseShardingValues* @return*/private Collection<String> routeDataSources(final TableRule tableRule, final ShardingStrategy databaseShardingStrategy, final List<ShardingConditionValue> databaseShardingValues) {// 如果数据没有对应的分片值,即查询的列没有设置数据源分片,则返回实际数据源名称if (databaseShardingValues.isEmpty()) {return tableRule.getActualDataSourceNames();}// 执行分片算法的doSharding(),获取分片的数据源名称集合Collection<String> result = databaseShardingStrategy.doSharding(tableRule.getActualDataSourceNames(), databaseShardingValues, tableRule.getDataSourceDataNode(), props);Preconditions.checkState(!result.isEmpty(), "No database route info");Preconditions.checkState(tableRule.getActualDataSourceNames().containsAll(result),"Some routed data sources do not belong to configured data sources. routed data sources: `%s`, configured data sources: `%s`", result, tableRule.getActualDataSourceNames());return result;}/*** 表路由* @param tableRule 表规则* @param routedDataSource 路由的数据源* @param tableShardingStrategy 表分片策略* @param tableShardingValues 表分片值* @return*/private Collection<DataNode> routeTables(final TableRule tableRule, final String routedDataSource,final ShardingStrategy tableShardingStrategy, final List<ShardingConditionValue> tableShardingValues) {// 获取目标数据源的实际表的集合Collection<String> availableTargetTables = tableRule.getActualTableNames(routedDataSource);Collection<String> routedTables = tableShardingValues.isEmpty()? availableTargetTables// 执行表分片算法的doSharding()方法,获取分片表名: tableShardingStrategy.doSharding(availableTargetTables, tableShardingValues, tableRule.getTableDataNode(), props);Collection<DataNode> result = new LinkedList<>();for (String each : routedTables) {// 创建分片的DataNode对象result.add(new DataNode(routedDataSource, each));}return result;}/*** 根据配置的信息,创建分片策略对象* @param shardingStrategyConfig 分片策略配置,如算法名称、分片键等* @param shardingAlgorithms 分片算法。key为算法的名称,value为对应算法类型的类(如:ClassBasedShardingAlgorithm等)* @param defaultShardingColumn 默认的分片键* @return*/private ShardingStrategy createShardingStrategy(final ShardingStrategyConfiguration shardingStrategyConfig, final Map<String, ShardingAlgorithm> shardingAlgorithms,final String defaultShardingColumn) {return null == shardingStrategyConfig ? new NoneShardingStrategy(): ShardingStrategyFactory.newInstance(shardingStrategyConfig, shardingAlgorithms.get(shardingStrategyConfig.getShardingAlgorithmName()), defaultShardingColumn);}
}

在route()方法中,执行如下:

1)执行getDataNodes(),获取当前SQL的分片数据节点,即真正要执行的数据库及表的映射对象;

1.1)根据配置的分片键的分片策略,创建数据源、表的分片策略对象。如对应分片键配置的策略为standard,则创建StandardShardingStrategy对象。其中的StandardShardingAlgorithm算法对象为算法配置时指定的type所对应的对象。如type配置为CLASS_BASED,则对应的StandardShardingAlgorithm算法对象为ClassBasedShardingAlgorithm。而在ClassBasedShardingAlgorithm中的StandardShardingAlgorithm算法对象才是真正自定义的算法对象;

1.2)根据配置的分片键的分片策略,判断数据源、表的策略条件,分为两种:Hint、非Hint。结合数据源、表,总共有三种场景:数据源、表都为Hint;都不为Hint;其中一种为Hint。针对上面的三种情况,分别判断处理,最后针对Hint和非Hint,获取分片的数据源和表;

1.2.1)如果是Hint的,则从提示中获取指定的数据源或表,创建新的ShardingCondition分片条件集合。逻辑为:

a)先从SQL语句中的注释信息中获取指定的数据源或表;

b)如果没有找到,则从HintManager中获取动态指定的数据源或表;

1.2.2)如果不是Hint,则使用WhereClauseShardingConditionEngine中解析where条件获取的ShardingCondition分片条件集合;

1.3)执行route0()方法,创建分片的数据节点;

1.3.1)执行routeDataSources(),执行数据源的分片算法(如ClassBasedShardingAlgorithm的doSharding()方法,在该方法中,执行自定义的分片算法的doSharding()方法),获取数据源字符串集合;

详见:【源码】Sharding-JDBC源码分析之分片规则生成器DatabaseRuleBuilder实现规则配置到规则对象的生成原理-中的ClassBasedShardingAlgorithm部分

1.3.2)遍历数据源字符串集合,执行routeTables(),执行表的分片算法,获取表字符串集合。同数据源字符串映射,生成DataNode对象。该对象记录分片的真实数据源、表的映射;

2)遍历DataNode集合对象,创建路由映射RouteMapper对象(逻辑表和真实表映射、数据源映射),创建路由单元RouteUnit对象。添加到RouteContext对象中;

3)返回RouteContext对象;

小结

限于篇幅,本篇先分享到这里。以下做一个小结:

1)在ShardingSpherePreparedStatement中真正执行SQL之前,要先创建路由上下文RouteContext对象;

该对象保存了SQL真正执行的数据库、逻辑表及真实表的映射信息;

2)对于非全路由的SQL操作,且没有在SQL的注释中指定数据源,则执行配置的路由器;

路由器包括ShardingSQLRouter(分片路由器)、SingleSQLRouter(单表路由器)、ReadwriteSplittingSQLRouter(读写分离路由器)、DatabaseDiscoverySQLRouter(数据库发现路由器)、ShadowSQLRouter(影子库路由器)。只需进行对应的配置,将自动创建对应的路由器。所有路由器以上面的顺序执行,第一个执行的会创建路由上下文对象,后执行的则合并路由上下文;

3)分片路由器ShardingSQLRouter如果有配置,会是第一个执行的路由器。在该路由器中,主要针对数据库的DML操作进行分片路由,创建路由上下文RouteContext对象;

3.1)DML操作主要分为两类,一种是Insert,另一种是带Where的。对于Insert语句,分片键的值在values部分,处理在InsertClauseShardingConditionEngine;后一种分片键的值在where部分,处理在WhereClauseShardingConditionEngine。通过分片条件引擎,解析分片键及值,创建ShardingCondition集合;

3.2)通过ShardingRouteEngineFactory.newInstance(),获取分片路由引擎。对于DML语句的普通分片规则,返回ShardingStandardRoutingEngine对象;

3.2.1)根据配置的分片键的分片策略,创建数据源、表的分片策略对象。如对应分片键配置的策略为standard,则创建StandardShardingStrategy对象。其中的StandardShardingAlgorithm算法对象为算法配置时指定的type所对应的对象。如type配置为CLASS_BASED,则对应的StandardShardingAlgorithm算法对象为ClassBasedShardingAlgorithm。而在ClassBasedShardingAlgorithm中的StandardShardingAlgorithm算法对象才是真正自定义的算法对象;

3.2.2)如果配置的是Hint策略,则从提示中获取指定的数据源或表(先从SQL的注释中获取,没有从HintManager中获取),创建新的ShardingCondition分片条件集合;

3.2.3)传入ShardingCondition分片条件,执行分片算法,获取实际执行的数据源及表,生成DataNode集合。先执行type对应的分片算法,在算法中执行自定义的分片算法(如果有配置);

3.2.4)遍历DataNode集合,创建路由映射(逻辑表和真实表映射、数据源映射)及路由单元,添加到新创建的路由上下文RouteContext中;

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

相关文章:

【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理

Sharding-JDBC系列 1、Sharding-JDBC分库分表的基本使用 2、Sharding-JDBC分库分表之SpringBoot分片策略 3、Sharding-JDBC分库分表之SpringBoot主从配置 4、SpringBoot集成Sharding-JDBC-5.3.0分库分表 5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表 6、【…...

初学 flutter 环境变量配置

一、jdk&#xff08;jdk11&#xff09; 1&#xff09;配置环境变量 新增&#xff1a;JAVA_HOMEC:\Program Files\Java\jdk-11 //你的jdk目录 在path新增&#xff1a;%JAVA_HOME%\bin2&#xff09;验证是否配置成功&#xff08;cmd运行命令&#xff09; java java -version …...

蓝牙 AVRCP 协议详解

前言 随着无线音频设备的普及&#xff0c;蓝牙已经成为智能设备间通信的主流方式之一。除了传输音频流的 A2DP 协议外&#xff0c;AVRCP&#xff08;Audio/Video Remote Control Profile&#xff0c;音频/视频远程控制协议&#xff09;为用户提供了对蓝牙音频设备的控制能力&am…...

在 Ubuntu 18.04 上安装 MySQL 5.7和MySQL 8

1.Ubuntu安装MySQL 5.72.Ubuntu安装MySQL 8 在 Ubuntu 18.04 上安装 MySQL 5.7&#xff0c;可以按照以下步骤操作&#xff1a; 1. 更新系统包列表 运行以下命令以确保系统包列表是最新的&#xff1a; sudo apt update2. 检查默认 MySQL 版本 Ubuntu 18.04 默认提供 MySQL 5.…...

第4章 Spring Boot自动配置

自动配置概述 SpringBoot的两大核心 Spring Boot 框架的两大核心特性可以概括为“启动器”&#xff08;Starter&#xff09;和“自动配置”&#xff08;Auto-configuration&#xff09;。 启动器&#xff08;Starter&#xff09;&#xff1a; Spring Boot 提供了一系列的 Star…...

显存:存储,GPU:计算;Pipeline Parallelism(管道并行)

目录 显存:存储,GPU:计算 流水线切分策略:(数据并并,多头并行,单头MLP切片) 存储(显存)和计算(GPU)负载不均衡的问题 1,2,3,4,5指的计算任务(数据切分) 大方块代表GPU计算 黄色代表显存 解决办法:重计算和流水线切分策略 重计算策略: 流水线切分策略:…...

费曼路径积分简单示例

费曼路径积分简单示例 费曼路径积分是量子力学中的一种计算方法&#xff0c;它通过对所有可能路径的贡献进行积分&#xff0c;来计算粒子从一个点到另一个点的概率幅。与经典力学不同&#xff0c;经典力学中粒子沿着使作用量最小的路径运动&#xff0c;而在量子力学中&#xf…...

40分钟学 Go 语言高并发:【实战】并发安全的配置管理器(功能扩展)

【实战】并发安全的配置管理器&#xff08;功能扩展&#xff09; 一、扩展思考 分布式配置中心 实现配置的集中管理支持多节点配置同步实现配置的版本一致性 配置加密 敏感配置的加密存储配置的安全传输访问权限控制 配置格式支持 支持YAML、TOML等多种格式配置格式自动…...

麒麟安全增强-kysec

DAC: 自主访问控制是linux下默认的接入控制机制,通过对资源读、写、执行操作,保证系统安全 MAC:安全接入控制机制,由操作系统约束的访问控制,默认情况下,MAC不允许任何访问,用户可以自定义策略规则制定允许什么 ,从而避免很多攻击。 MAC强制访问控制常见的实现方式:…...

shell编程(8)

目录 一、until循环 示例 until 和 while 的区别 二、case语句 基本语法 示例 1. 简单的 case 语句 2. 使用通配符 3. 处理多个匹配 case 和 if 的比较 case 语句&#xff1a; if 语句&#xff1a; 三、基本函数 基本函数定义和调用 1. 定义一个简单的函数 2. …...

高级java每日一道面试题-2024年11月24日-JVM篇-说说对象分配规则?

如果有遗漏,评论区告诉我进行补充 面试官: 说说对象分配规则? 我回答: 在Java高级面试中&#xff0c;对象分配规则是一个核心考点&#xff0c;它涉及到JVM的内存管理、对象的创建和初始化等多个方面。以下是对Java对象分配规则的详细解释&#xff1a; 一、内存分配区域 J…...

进程间通信5:信号

引入 我们之前学习了信号量&#xff0c;信号量和信号可不是一个东西&#xff0c;不能混淆。 信号是什么以及一些基础概念 信号是一种让进程给其他进程发送异步消息的方式 信号是随时产生的&#xff0c;无法预测信号可以临时保存下来&#xff0c;之后再处理信号是异步发送的…...

性能测试及调优

一、性能测试介绍 1、什么叫做性能测试&#xff1f; &#xff08;1&#xff09;通过某些工具或手段来检测软件的某些指标是否达到了要求&#xff0c;这就是性能测试 &#xff08;2&#xff09;指通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指…...

实战基于LangChain和ChatGLM私有化部署聊天机器人

本文主要阐述了如何使用第二代6B模型进行对话训练&#xff0c;以及如何通过微调来提高大模型的性能。文中提到了在8501端口上启动第二代6B模型&#xff0c;并使用极简模板进行请求。与第一代模型相比&#xff0c;第二代6B模型具有更强的对话能力&#xff0c;并且可以通过微调来…...

利用adb工具安装卸载安卓平板(手机)软件

参考链接&#xff1a; 1、ADB 操作命令详解及用法大全 2、全面掌握Android调试工具箱&#xff1a;ADB与实用程序实战 平时使用小米手机没有感觉&#xff0c;miui系统做的确实好。最近买了个水货学习系统平板&#xff08;主要看重硬件配置&#xff0c;性价比很高&#xff0c;但…...

基于docker进行任意项目灵活发布

引言 不管是java还是python程序等&#xff0c;使用docker发布的优势有以下几点&#xff1a; 易于维护。直接docker命令进行管理&#xff0c;如docker stop、docker start等&#xff0c;快速方便无需各种进程查询关闭。环境隔离。项目代码任何依赖或设置都可以基本独立&#x…...

Datatables:监听行内文本框,进行行内数据修改;计算行总和

一、监听行内文本框&#xff0c;进行行内数据修改 效果 修改数量、单价会自动计算金额&#xff08;金额数量*单价&#xff09; 实现 1、增加行的class 2、数据监听、修改数值 "initComplete": function() {// 监听数量和单价输入框的变化$(document).on(input, .…...

对于某些原型或UI软件的个人看法(2024/11)

由于我这几天&#xff0c;一边敲代码&#xff0c;一边进行页面布局设计与编码&#xff0c;发现可能就一个卡片&#xff0c;我都得调很久样式&#xff0c;觉得这样改很累也没效率&#xff0c;页面也不是很美观。所以我想到了ui设计&#xff0c;我可以先进行ui设计&#xff0c;然…...

嵌入式硬件实战提升篇(二)PCB高速板设计 FPGA核心板带DDR3 PCB设计DDR全面解析

引言&#xff1a;设计一款高速板&#xff0c;供读者学习&#xff0c;FPGA核心板&#xff0c;带一颗DDR3内存&#xff0c;FPGA型号&#xff1a;XC6SLX16-2FTG256C。 随着嵌入式硬件技术的快速发展&#xff0c;高速板设计逐渐成为嵌入式系统设计中的核心技术之一。高速板的设计要…...

亚信安全携手飞书“走近先进” 与保隆科技探索制造业数字化转型

亚信安全携手飞书组织举办“走近先进”活动。近日活动“走近”了中国汽车供应链百强、上海市制造业五十强企业——上海保隆汽车科技股份有限公司&#xff08;以下简称“保隆科技”&#xff09;。活动围绕“突破桎梏 加速升级”的主题&#xff0c;聚焦企业数字化转型的核心议题&…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

使用分级同态加密防御梯度泄漏

抽象 联邦学习 &#xff08;FL&#xff09; 支持跨分布式客户端进行协作模型训练&#xff0c;而无需共享原始数据&#xff0c;这使其成为在互联和自动驾驶汽车 &#xff08;CAV&#xff09; 等领域保护隐私的机器学习的一种很有前途的方法。然而&#xff0c;最近的研究表明&…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...

jdbc查询mysql数据库时,出现id顺序错误的情况

我在repository中的查询语句如下所示&#xff0c;即传入一个List<intager>的数据&#xff0c;返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致&#xff0c;会导致返回的id是从小到大排列的&#xff0c;但我不希望这样。 Query("SELECT NEW com…...

前端开发者常用网站

Can I use网站&#xff1a;一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use&#xff1a;Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站&#xff1a;MDN JavaScript权威网站&#xff1a;JavaScript | MDN...

React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构

React 实战项目&#xff1a;微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇&#xff01;在前 29 篇文章中&#xff0c;我们从 React 的基础概念逐步深入到高级技巧&#xff0c;涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

密码学基础——SM4算法

博客主页&#xff1a;christine-rr-CSDN博客 ​​​​专栏主页&#xff1a;密码学 &#x1f4cc; 【今日更新】&#x1f4cc; 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 ​编辑…...