ShardingSphere之sharding jdbc

881 阅读5分钟

说明

1、infra模块的功能是核心引擎,包含:解析、路由、改写、执行、归并等。但是在日常工作中我们却无法直接使用infra模块的功能,而是通过sharding jdbc模块。sharding jdbc通过对jdbc的封装来提供分布式数据库场景的支持,使得我们可以不关心底层实现,直接用操作jdbc的方式来处理分片、读写分离等问题。

JDBC(Java Database Connectivity)的设计初衷是提供一套用于各种数据库的统一标准,而不同的数据库厂家共同遵守这套标准,并提供各自的实现方案供应用程序调用。作为统一标准,JDBC 规范具有完整的架构体系,如下图所示:

image.png

JDBC 架构中的 Driver Manager 负责加载各种不同的驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。而应用程序通过调用 JDBC API 来实现对数据库的操作。对于开发人员而言,JDBC API 是我们访问数据库的主要途径,也是 ShardingSphere 重写 JDBC 规范并添加分片功能的入口。如果我们使用 JDBC 开发一个访问数据库的处理流程,常见的代码风格如下所示:

final String HOST = "127.0.0.1";
final int PORT = 3306;
final String USER_NAME = "root";
final String PASSWORD = "123456";
HikariDataSource result = new HikariDataSource();
result.setDriverClassName("com.mysql.jdbc.Driver");
result.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", HOST, PORT, dataSourceName));
result.setUsername(USER_NAME);
result.setPassword(PASSWORD);
try (Connection connection = result.getConnection();
     PreparedStatement preparedStatement = connection.prepareStatement("select * from user");
     ResultSet set = preparedStatement.executeQuery();) {
    while (set.next()) {
        String name = set.getString(1);
    }
} catch (SQLException throwables) {
    throwables.printStackTrace();
}

这段代码中包含了 JDBC API 中的核心接口,使用这些核心接口是我们基于 JDBC 进行数据访问的基本方式,你会发现在 ShardingSphere 中,完成日常开发所使用的也就是这些接口。

DataSource

DataSource 在 JDBC 规范中代表的是数据源,核心作用是获取数据库连接对象 Connection。在 JDBC 规范中,实际可以直接通过 DriverManager 获取 Connection。我们知道获取 Connection 的过程需要建立与数据库之间的连接,而这个过程会产生较大的系统开销。

为了提高性能,通常会建立一个中间层,该中间层将 DriverManager 生成的 Connection 存放到连接池中,然后从池中获取 Connection,可以认为,DataSource 就是这样一个中间层。在日常开发过程中,我们通常都会基于 DataSource 来获取 Connection。而在 ShardingSphere 中,暴露给业务开发人员的同样是一个经过增强的 DataSource 对象。DataSource 接口的定义是这样的:

public interface DataSource  extends CommonDataSource, Wrapper {

  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password)
    throws SQLException;
}

请注意,DataSource 接口同时还继承了一个 Wrapper 接口。从接口的命名上看,可以判断该接口应该起到一种包装器的作用,事实上,由于很多数据库供应商提供了超越标准 JDBC API 的扩展功能,所以,Wrapper 接口可以把一个由第三方供应商提供的、非 JDBC 标准的接口包装成标准接口。以 DataSource 接口为例,如果我们想要实现自己的数据源 MyDataSource,就可以提供一个实现了 Wrapper 接口的 MyDataSourceWrapper 类来完成包装和适配。

在 JDBC 规范中,除了 DataSource 之外,Connection、Statement、ResultSet 等核心对象也都继承了这个接口。显然,ShardingSphere 提供的就是非 JDBC 标准的接口,所以也应该会用到这个 Wrapper 接口,并提供了类似的实现方案。

下图为jdbc规范进行数据库访问的开发流程图

image.png

ShardingSphere 提供了与 JDBC 规范完全兼容的 API。也就是说,开发人员可以基于这个开发流程和 JDBC 中的核心接口完成分片引擎、数据脱敏等操作,我们来看一下。

基于适配器模式的 JDBC 重写实现方案

在 ShardingSphere 中,实现与 JDBC 规范兼容性的基本策略就是采用了设计模式中的适配器模式(Adapter Pattern)。适配器模式通常被用作连接两个不兼容接口之间的桥梁,涉及为某一个接口加入独立的或不兼容的功能。

作为一套适配 JDBC 规范的实现方案,ShardingSphere 需要对 JDBC API 中的 DataSource、Connection、Statement 及 ResultSet 等核心对象都完成重写。虽然这些对象承载着不同功能,但重写机制应该是共通的,否则就需要对不同对象都实现定制化开发,显然,这不符合我们的设计原则。为此,ShardingSphere 抽象并开发了一套基于适配器模式的实现方案,整体结构是这样的,我以Connection为例,如下图所示:

image.png

首先,我们看到这里有一个Connection接口。前面提到,这些接口都继承自包装器 Wrapper 接口。ShardingSphere 为这个 Wrapper 接口提供了一个实现类 WrapperAdapter,这点在图中得到了展示。在 ShardingSphere 代码工程 sharding-jdbc-core 的 org.apache.shardingsphere.shardingjdbc.jdbc.adapter 包中包含了所有与 Adapter 相关的实现类:

image.png

在 ShardingSphere 基于适配器模式的实现方案图的底部,有一个 ShardingSphereConnection实现类。

最后发现 ShardingSphereConnection 继承自一个 AbstractConnectionAdapter,而 AbstractConnectionAdapter 又继承自 AbstractUnsupportedOperationConnection,这两个类都是抽象类,而且也都泛指一组类。两者的区别在于,AbstractConnectionAdapter 只提供了针对 Connection 接口的一部分实现方法,这些方法是我们完成分片操作所需要的。而对于那些我们不需要的方法实现,则全部交由 AbstractUnsupportedOperationConnection 进行实现,这两个类的所有方法的合集,就是原有 Connection 接口的所有方法定义。

这样,我们大致了解了 ShardingSphere 对 JDBC 规范中核心接口的重写机制。这个重写机制非常重要,在 ShardingSphere 中应用也很广泛,我们可以通过示例对这一机制做进一步理解。

ShardingSphereConnection 类层结构 ShardingSphereConnection 是对 JDBC 中 Connection 的适配和包装,所以它需要提供 Connection 接口中定义的方法,包括 createConnection、getMetaData、各种重载的 prepareStatement 和 createStatement 以及针对事务的 setAutoCommit、commit 和 rollback 方法等。ShardingSphereConnection 对这些方法都进行了重写。

总结

JDBC 规范是理解和应用 ShardingSphere 的基础,ShardingSphere 对 JDBC 规范进行了重写,并提供了完全兼容的一套接口。