上一篇文章说了shardingsphereSQL执行的大体源码流程,可以快速帮助新了解的人员。实际上最终依旧由JDBC的JAR进行请求数据库。
一、分析
1.准备
代理的依旧是mysql数据库,假设在t1表中将 vc_source已经进行加密了,此时按照之前的文章,我们在访问原库的时候已经变成密文,那么连接到代理库的时候查询的是明文。那么再查询出结果的时候到底在代理中干了些什么?
2.源码分析
从上篇开始,真正将这个sql发送给数据库,依旧由各自的驱动jar进行执行的,部分调用链路
JDBCExecutor.execute => JDBCExecutorCallback.execute => ProxyJDBCExecutorCallback.executeSQL =>
ProxyStatementExecutorCallback.execute => 各自的驱动执行
private List<ExecuteResult> doExecute(final ExecutionContext executionContext) throws SQLException {
String databaseName = backendConnection.getConnectionSession().getDatabaseName();
// 加密规则 包含表的对应关系 字段对应关系 那个表的那个字段需要加解密,用的那个加密器; 包含加密器,map《加密器名称,加密器实例》;
Collection<ShardingSphereRule> rules = ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData().getDatabase(databaseName).getRuleMetaData().getRules();
int maxConnectionsSizePerQuery = ProxyContext.getInstance()
.getContextManager().getMetaDataContexts().getMetaData().getProps().<Integer>getValue(ConfigurationPropertyKey.MAX_CONNECTIONS_SIZE_PER_QUERY);
boolean isReturnGeneratedKeys = executionContext.getSqlStatementContext().getSqlStatement() instanceof MySQLInsertStatement;
return hasRawExecutionRule(rules) ? rawExecute(executionContext, rules, maxConnectionsSizePerQuery) // 一般走 useDriverToExecute,驱动jar执行
: useDriverToExecute(executionContext, rules, maxConnectionsSizePerQuery, isReturnGeneratedKeys, SQLExecutorExceptionHandler.isExceptionThrown());
}
ProxyJDBCExecutorCallback.executeSQL
ResultSet resultSet = statement.getResultSet(); 我们发现了一个地方,完全类似于jdbc连接数据库时 将结果封装在ResultSet 中
private ExecuteResult executeSQL(final String sql, final Statement statement, final ConnectionMode connectionMode, final boolean withMetaData, final DatabaseType storageType) throws SQLException {
databaseCommunicationEngine.add(statement);
if (execute(sql, statement, isReturnGeneratedKeys)) {
ResultSet resultSet = statement.getResultSet();
databaseCommunicationEngine.add(resultSet);
return createQueryResult(resultSet, connectionMode, storageType);
}
return new UpdateResult(statement.getUpdateCount(), isReturnGeneratedKeys ? getGeneratedKey(statement) : 0L);
}
最后依旧到统一流转的地方CommandExecutorTask.executeCommand() ,咱们之前sql真正准备执行的入口再这Collection<DatabasePacket<?>> responsePackets = commandExecutor.execute();
private boolean executeCommand(final ChannelHandlerContext context, final PacketPayload payload) throws SQLException {
CommandExecuteEngine commandExecuteEngine = databaseProtocolFrontendEngine.getCommandExecuteEngine();
CommandPacketType type = commandExecuteEngine.getCommandPacketType(payload);
CommandPacket commandPacket = commandExecuteEngine.getCommandPacket(payload, type, connectionSession);
CommandExecutor commandExecutor = commandExecuteEngine.getCommandExecutor(type, commandPacket, connectionSession);
try {
Collection<DatabasePacket<?>> responsePackets = commandExecutor.execute();
if (responsePackets.isEmpty()) {
return false;
}
responsePackets.forEach(context::write);
if (commandExecutor instanceof QueryCommandExecutor) {
commandExecuteEngine.writeQueryData(context, connectionSession.getBackendConnection(), (QueryCommandExecutor) commandExecutor, responsePackets.size());
}
return true;
} catch (final SQLException | ShardingSphereSQLException | SQLDialectException ex) {
databaseProtocolFrontendEngine.handleException(connectionSession, ex);
throw ex;
} finally {
commandExecutor.close();
}
}
commandExecuteEngine.writeQueryData()方法中表名了将一行行的数据重新组装成报文进行返回,再获取一行数据的时候,会进行一个判断,判断是否需要进行解密,如果需要解密,先进行解密,具体步骤:
MySQLCommandExecuteEngine.writeQueryData =>
MySQLComQueryPacketExecutor.getQueryRowPacket =>
DatabaseCommunicationEngine.getRowData =>
EncryptMergedResult.getValue => 解密算法decrypt()
public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
Optional<EncryptContext> encryptContext = metaData.findEncryptContext(columnIndex);
if (!encryptContext.isPresent() || !metaData.isQueryWithCipherColumn(encryptContext.get().getTableName(), encryptContext.get().getColumnName())) {
return mergedResult.getValue(columnIndex, type); // 是非加密字段,返回
}
Optional<StandardEncryptAlgorithm> encryptAlgorithm = metaData.findEncryptor(encryptContext.get().getTableName(), encryptContext.get().getColumnName());
if (!encryptAlgorithm.isPresent()) {
return mergedResult.getValue(columnIndex, type);
}
Object cipherValue = mergedResult.getValue(columnIndex, Object.class); // 获取加密字段的值 ,会到 JDBCStreamQueryResult 先判断类型,decrypt解密
return null == cipherValue ? null : encryptAlgorithm.get().decrypt(cipherValue, encryptContext.get());
}
二、总结
本篇主要介绍了,sql执行返回的结果依旧遵守JDBC,以及到底在什么地方判断进行解密操作,对于整个shardingsphere代码来说比较复杂,复杂的点主要在里面封装了很多层上下文调用链之长,实现类之多 ,非常不好排查,代码量也非常大,再制作镜像时有500M,但是实际上如果理清楚了整个流程那么还是比较简单得,本栏目总共使用了9篇来帮助你理清它们之间的关键的流程。