《ShardingSphere解读》03 JDBC 规范与 ShardingSphere 是什么关系?
在上一篇中我们全面了解了 ShardingSphere 作为 Apache 顶级开源软件的发展历程、设计理念和核心功能。其中特别强调了一点ShardingSphere 是一种典型的客户端分片解决方案而客户端分片的核心实现方式之一就是重写 JDBC 规范。ShardingSphere 对外暴露的分片操作接口与 JDBC 规范中所提供的接口完全一致这究竟是如何实现的本课时将深入剖析 JDBC 规范与 ShardingSphere 的这层关系从底层设计上解开其中的奥秘。一、JDBC 规范简介JDBCJava 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 DataSourceDataSource是 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 ConnectionConnection代表与数据库的会话连接它负责创建Statement或PreparedStatement并管理事务提交、回滚、设置隔离级别等。在分片场景下Connection需要能够路由到多个物理数据库因此 ShardingSphere 实现了ShardingConnection。1.3.3 Statement 与 PreparedStatementStatement用于执行静态 SQL而PreparedStatement继承自Statement支持预编译 SQL可防止 SQL 注入并提升执行效率。ShardingSphere 分别提供了ShardingStatement和ShardingPreparedStatement它们在内部将逻辑 SQL 改写为真实 SQL并路由到对应分片执行。1.3.4 ResultSetResultSet封装查询结果提供遍历和获取数据的方法。分片查询可能涉及多个数据源返回的多个结果集ShardingSphere 的ShardingResultSet负责对这些结果集进行归并对外呈现为单个逻辑结果集。二、基于适配器模式的 JDBC 重写实现方案ShardingSphere 要实现与 JDBC 规范完全兼容的 API必须对DataSource、Connection、Statement、ResultSet等核心接口进行重写使其在内部嵌入分片逻辑但对外依然呈现为标准接口。这一重写机制的设计核心是适配器模式Adapter Pattern。2.1 适配器模式概述适配器模式用于将一个类的接口转换成客户希望的另一个接口使得原本因接口不兼容而无法一起工作的类可以协同工作。在 ShardingSphere 中它扮演的角色是目标接口TargetJDBC 规范中的标准接口如DataSource、Connection。被适配者AdapteeShardingSphere 内部实现分片逻辑的类如ShardingDataSource。适配器Adapter连接目标接口和被适配者的中间层使得最终对象既符合 JDBC 规范又具备分片能力。2.2 ShardingSphere 适配器体系总体结构ShardingSphere 为每个核心接口设计了一套适配器类其总体结构如下图中各层次的含义WrapperJDBC 标准接口所有核心接口都继承它。WrapperAdapterShardingSphere 提供的包装器适配基类实现了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为例深入分析其如何通过适配器模式实现对 JDBCConnection接口的重写。3.1 ShardingConnection 的类层结构ShardingConnection的继承体系完全遵循前面介绍的适配器模式AbstractUnsupportedOperationConnection继承WrapperAdapter实现了Connection接口中所有不支持的方法如prepareCall直接抛出异常。这确保ShardingConnection只关注支持的方法。AbstractConnectionAdapter继承AbstractUnsupportedOperationConnection提供了连接缓存、获取物理连接等通用能力但createConnection方法留为抽象由子类实现。ShardingConnection继承AbstractConnectionAdapter实现createConnection方法根据分片规则返回实际的数据库连接并重写prepareStatement等方法嵌入分片逻辑。3.2 连接缓存与获取AbstractConnectionAdapterAbstractConnectionAdapter中维护了一个cachedConnections属性它是一个Map键为数据源名称值为该数据源对应的物理连接集合。这样在同一个逻辑连接生命周期内对同一数据源的多次操作可以复用已创建的连接避免重复创建。核心方法getConnections负责从缓存中获取连接若不足则创建新的连接。其简化流程如下public final ListConnection getConnections(ConnectionMode connectionMode, String dataSourceName, int connectionSize) throws SQLException { // 1. 从缓存中获取已有连接 CollectionConnection connections cachedConnections.get(dataSourceName); // 2. 如果缓存中连接数足够直接返回子列表 if (connections.size() connectionSize) { return new ArrayList(connections).subList(0, connectionSize); } // 3. 否则创建不足数量的新连接 ListConnection newConnections createConnections(dataSourceName, connectionMode, dataSource, connectionSize - connections.size()); // 4. 将新连接加入缓存并返回 synchronized (cachedConnections) { cachedConnections.putAll(dataSourceName, newConnections); } // ... 合并已有连接和新连接返回 }创建连接最终调用抽象方法createConnectionprotected 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 ForceExecuteCallbackConnection() { 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 规范中对不支持操作的要求。四、其他核心类的适配实现除了ShardingConnectionShardingSphere 对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 的理解谈谈你的看法。欢迎在评论区留言讨论。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2414091.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!