在上一篇中,我们全面了解了 ShardingSphere 作为 Apache 顶级开源软件的发展历程、设计理念和核心功能。其中特别强调了一点:ShardingSphere 是一种典型的客户端分片解决方案,而客户端分片的核心实现方式之一就是重写 JDBC 规范。ShardingSphere 对外暴露的分片操作接口与 JDBC 规范中所提供的接口完全一致,这究竟是如何实现的?本课时将深入剖析 JDBC 规范与 ShardingSphere 的这层关系,从底层设计上解开其中的奥秘。
一、JDBC 规范简介
JDBC(Java Database Connectivity)是 Java 领域访问数据库的标准规范。它定义了一套统一的接口,让应用程序可以通过一致的方式操作不同数据库。数据库厂商则提供这些接口的实现(即数据库驱动),从而将具体数据库的差异屏蔽在驱动层之下。
1.1 JDBC 架构
JDBC 的核心架构如下图所示,它由 DriverManager、Driver、Connection 等核心组件构成:

-
DriverManager:负责加载和管理不同数据库的驱动程序,并根据连接 URL 返回对应的 Connection 对象。
-
Driver:数据库驱动程序,由数据库厂商实现,负责与数据库建立底层通信。
-
Connection:代表与数据库的会话连接,是执行 SQL 的上下文。
-
Statement / PreparedStatement:用于执行静态 SQL 或预编译 SQL。
-
ResultSet:封装 SQL 查询的结果集。
1.2 JDBC 核心接口及典型使用流程
在 JDBC 规范中,DataSource、Connection、Statement、ResultSet 是最核心的接口。下面通过一段典型代码展示它们的使用方式:
// 创建池化的数据源
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 获取连接
Connection connection = dataSource.getConnection();
// 创建语句并执行查询
PreparedStatement statement = connection.prepareStatement("SELECT * FROM user");
ResultSet resultSet = statement.executeQuery();
// 遍历结果集
while (resultSet.next()) {
// 处理每一行数据
}
// 关闭资源
resultSet.close();
statement.close();
connection.close();
上述流程可以用下图概括:

1.3 各核心接口详解
1.3.1 DataSource
DataSource 是 JDBC 2.0 引入的接口,它作为连接工厂,提供了比 DriverManager 更强大的功能,例如连接池、分布式事务等。其定义如下:
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password) throws SQLException;
}
CommonDataSource 是 DataSource、ConnectionPoolDataSource 和 XADataSource 的父接口,后两者分别用于连接池和分布式事务场景。它们的关系如下:

注意 DataSource 还继承了 Wrapper 接口。Wrapper 是 JDBC 4.0 引入的,用于让应用程序访问非 JDBC 标准的扩展功能。通过 unwrap 方法,可以将 JDBC 包装对象还原为原生对象;通过 isWrapperFor 可以判断是否可包装为指定类型。这个接口为 ShardingSphere 重写 JDBC 规范提供了基础。
1.3.2 Connection
Connection 代表与数据库的会话连接,它负责创建 Statement 或 PreparedStatement,并管理事务(提交、回滚、设置隔离级别等)。在分片场景下,Connection 需要能够路由到多个物理数据库,因此 ShardingSphere 实现了 ShardingConnection。
1.3.3 Statement 与 PreparedStatement
Statement 用于执行静态 SQL,而 PreparedStatement 继承自 Statement,支持预编译 SQL,可防止 SQL 注入并提升执行效率。ShardingSphere 分别提供了 ShardingStatement 和 ShardingPreparedStatement,它们在内部将逻辑 SQL 改写为真实 SQL,并路由到对应分片执行。
1.3.4 ResultSet
ResultSet 封装查询结果,提供遍历和获取数据的方法。分片查询可能涉及多个数据源返回的多个结果集,ShardingSphere 的 ShardingResultSet 负责对这些结果集进行归并,对外呈现为单个逻辑结果集。
二、基于适配器模式的 JDBC 重写实现方案
ShardingSphere 要实现与 JDBC 规范完全兼容的 API,必须对 DataSource、Connection、Statement、ResultSet 等核心接口进行重写,使其在内部嵌入分片逻辑,但对外依然呈现为标准接口。这一重写机制的设计核心是适配器模式(Adapter Pattern)。
2.1 适配器模式概述
适配器模式用于将一个类的接口转换成客户希望的另一个接口,使得原本因接口不兼容而无法一起工作的类可以协同工作。在 ShardingSphere 中,它扮演的角色是:
-
目标接口(Target):JDBC 规范中的标准接口(如 DataSource、Connection)。
-
被适配者(Adaptee):ShardingSphere 内部实现分片逻辑的类(如 ShardingDataSource)。
-
适配器(Adapter):连接目标接口和被适配者的中间层,使得最终对象既符合 JDBC 规范,又具备分片能力。
2.2 ShardingSphere 适配器体系总体结构
ShardingSphere 为每个核心接口设计了一套适配器类,其总体结构如下:

图中各层次的含义:
-
Wrapper:JDBC 标准接口,所有核心接口都继承它。
-
WrapperAdapter:ShardingSphere 提供的包装器适配基类,实现了 recordMethodInvocation 和 replayMethodsInvocation 方法,用于记录和重放对底层真实连接的方法调用(后面详述)。
-
AbstractUnsupportedOperationJdbcObject:抽象类,将所有 JDBC 中不打算支持的方法直接抛出异常,明确职责边界。例如 prepareCall(存储过程调用)在分片场景中通常不支持,就在这里抛出 SQLFeatureNotSupportedException。
-
AbstractJdbcObjectAdapter:抽象类,继承自 AbstractUnsupportedOperationJdbcObject,提供部分方法的默认实现,这些方法是 ShardingSphere 需要增强的(如 prepareStatement)。真正的分片逻辑尚未加入,留给子类实现。
-
ShardingJdbcObject:具体类,如 ShardingDataSource、ShardingConnection、ShardingStatement,它们实现分片逻辑,并对外呈现为标准 JDBC 接口。
这种分层设计将“不支持的方法”、“需增强的方法”和“具体分片逻辑”清晰地分离,既保证了与 JDBC 规范的兼容,又使代码易于维护和扩展。
2.3 包结构组织
在 ShardingSphere 源码的 sharding-jdbc-core 模块中,适配器相关的类位于 org.apache.shardingsphere.shardingjdbc.jdbc.adapter 包下,结构如下:
org.apache.shardingsphere.shardingjdbc.jdbc.adapter
├── AbstractDataSourceAdapter
├── AbstractConnectionAdapter
├── AbstractStatementAdapter
├── AbstractPreparedStatementAdapter
├── AbstractResultSetAdapter
├── WrapperAdapter
└── … (各子类的实现)
这种按职责划分的包结构使得开发者可以快速定位需要扩展的类。
三、ShardingSphere 重写 JDBC 规范示例:ShardingConnection
下面以 ShardingConnection 为例,深入分析其如何通过适配器模式实现对 JDBC Connection 接口的重写。
3.1 ShardingConnection 的类层结构
ShardingConnection 的继承体系完全遵循前面介绍的适配器模式:

-
AbstractUnsupportedOperationConnection:继承 WrapperAdapter,实现了 Connection 接口中所有不支持的方法(如 prepareCall),直接抛出异常。这确保 ShardingConnection 只关注支持的方法。
-
AbstractConnectionAdapter:继承 AbstractUnsupportedOperationConnection,提供了连接缓存、获取物理连接等通用能力,但 createConnection 方法留为抽象,由子类实现。
-
ShardingConnection:继承 AbstractConnectionAdapter,实现 createConnection 方法,根据分片规则返回实际的数据库连接,并重写 prepareStatement 等方法嵌入分片逻辑。
3.2 连接缓存与获取:AbstractConnectionAdapter
AbstractConnectionAdapter 中维护了一个 cachedConnections 属性,它是一个 Map,键为数据源名称,值为该数据源对应的物理连接集合。这样,在同一个逻辑连接生命周期内,对同一数据源的多次操作可以复用已创建的连接,避免重复创建。
核心方法 getConnections 负责从缓存中获取连接,若不足则创建新的连接。其简化流程如下:
public final List<Connection> getConnections(ConnectionMode connectionMode, String dataSourceName, int connectionSize) throws SQLException {
// 1. 从缓存中获取已有连接
Collection<Connection> connections = cachedConnections.get(dataSourceName);
// 2. 如果缓存中连接数足够,直接返回子列表
if (connections.size() >= connectionSize) {
return new ArrayList<>(connections).subList(0, connectionSize);
}
// 3. 否则,创建不足数量的新连接
List<Connection> newConnections = createConnections(dataSourceName, connectionMode, dataSource, connectionSize – connections.size());
// 4. 将新连接加入缓存并返回
synchronized (cachedConnections) {
cachedConnections.putAll(dataSourceName, newConnections);
}
// … 合并已有连接和新连接返回
}
创建连接最终调用抽象方法 createConnection:
protected abstract Connection createConnection(String dataSourceName, DataSource dataSource) throws SQLException;
ShardingConnection 会实现该方法,从数据源中获取真正的物理连接。
3.3 方法记录与重放:WrapperAdapter
在 AbstractConnectionAdapter 的 getConnections 方法中,创建新连接后会调用 replayMethodsInvocation(connection)。这个方法是定义在 WrapperAdapter 中的,其作用是将之前对逻辑连接执行的一些配置方法(如 setAutoCommit、setReadOnly)“重放”到新创建的物理连接上,确保物理连接的状态与逻辑连接一致。
WrapperAdapter 中维护了一个 jdbcMethodInvocations 列表,用于记录需要重放的方法调用:
public final void recordMethodInvocation(Class<?> targetClass, String methodName, Class<?>[] argumentTypes, Object[] arguments) {
jdbcMethodInvocations.add(new JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments));
}
public final void replayMethodsInvocation(Object target) {
for (JdbcMethodInvocation each : jdbcMethodInvocations) {
each.invoke(target);
}
}
JdbcMethodInvocation 是一个简单的包装类,内部通过反射执行方法:
public class JdbcMethodInvocation {
private final Method method;
private final Object[] arguments;
public void invoke(Object target) {
method.invoke(target, arguments);
}
}
那么,何时记录方法调用?在 AbstractConnectionAdapter 中,对 setAutoCommit、setReadOnly、setTransactionIsolation 这三个方法进行了记录。以 setReadOnly 为例:
@Override
public final void setReadOnly(boolean readOnly) throws SQLException {
this.readOnly = readOnly;
// 记录方法调用
recordMethodInvocation(Connection.class, "setReadOnly", new Class[]{boolean.class}, new Object[]{readOnly});
// 对现有所有物理连接立即执行该操作
forceExecuteTemplate.execute(cachedConnections.values(), new ForceExecuteCallback<Connection>() {
@Override
public void execute(Connection connection) throws SQLException {
connection.setReadOnly(readOnly);
}
});
}
这里既记录了调用,也立即对已有连接执行了操作。当后续新连接加入时,通过 replayMethodsInvocation 将这些调用在新连接上重放,保证所有物理连接的状态与逻辑连接一致。
3.4 不支持的方法处理:AbstractUnsupportedOperationConnection
在 AbstractUnsupportedOperationConnection 中,所有不打算支持的方法都直接抛出 SQLFeatureNotSupportedException,例如:
@Override
public final CallableStatement prepareCall(String sql) throws SQLException {
throw new SQLFeatureNotSupportedException("prepareCall");
}
这样,任何试图调用这些方法的代码都会得到明确的异常提示,符合 JDBC 规范中对不支持操作的要求。
四、其他核心类的适配实现
除了 ShardingConnection,ShardingSphere 对 DataSource、Statement、PreparedStatement、ResultSet 也采用了完全相同的适配模式:
-
ShardingDataSource:继承 AbstractDataSourceAdapter,实现 getConnection 返回 ShardingConnection。
-
ShardingStatement:继承 AbstractStatementAdapter,重写 executeQuery、executeUpdate 等方法,将逻辑 SQL 路由到真实数据库执行。
-
ShardingPreparedStatement:继承 AbstractPreparedStatementAdapter,处理参数化 SQL,并在执行时替换参数值。
-
ShardingResultSet:继承 AbstractResultSetAdapter,归并多个物理结果集,对外呈现为单个结果集。
它们的继承体系与 ShardingConnection 类似,都遵循 WrapperAdapter → AbstractUnsupportedOperationXxx → AbstractXxxAdapter → ShardingXxx 的层次结构。
五、小结
本篇深入剖析了 ShardingSphere 如何通过适配器模式实现对 JDBC 规范的重写,使其对外提供完全兼容的 API,内部却嵌入了强大的分片功能。我们首先回顾了 JDBC 规范的核心接口及其典型使用流程,然后详细介绍了 ShardingSphere 基于适配器模式的整体设计,最后以 ShardingConnection 为例,分析了连接缓存、方法记录与重放、不支持方法处理等关键实现细节。
这种设计带来的好处显而易见:
-
对开发者友好:只需掌握标准 JDBC 编程,即可使用分片功能。
-
与现有框架无缝集成:可轻松与 MyBatis、Hibernate、Spring 等生态结合。
-
高可扩展性:适配器模式将不支持的接口统一抛出异常,支持的接口通过抽象类预留扩展点,新增功能只需添加新的子类。
理解这一底层机制,是正确使用和扩展 ShardingSphere 的基础。
思考题:在 WrapperAdapter 中,recordMethodInvocation 记录了哪些方法?为什么只记录这三个(setAutoCommit、setReadOnly、setTransactionIsolation)?请结合 JDBC 规范和你对 ShardingSphere 的理解,谈谈你的看法。欢迎在评论区留言讨论。



