方言系统架构演进:从分离到统一
· 阅读需 6 分钟
dbVisitor 是一个旨在提供统一数据库访问体验的 Java 工具库。随着对 MySQL、PostgreSQL 等关系型数据库以及 MongoDB、ElasticSearch 等 NoSQL 数据源支持的不断深入,底层的方言系统(Dialect System)面临着越来越复杂的挑战。
近期,我们对 dbVisitor 的方言系统进行了一次深度的架构重构。本次重构不涉及功能变更,旨在解决旧架构中存在的抽象割裂问题,将“方言元数据”与“命令构建能力”高度内聚。
本文将深入探讨这次架构演进背后的思考、实施方案以及带来的显著优势。
背景:旧架构的痛点
在重构之前,dbVisitor 的方言层设计采用了职责分离的原则,主要由两个平行的接口体系构成:
SqlDialect:负责定义数据库的静态特征和元数据。例如:左右转义符、关键字集合、分页语句的拼接模式、表名/列名的格式化规则等。 它通常是无状态的单例。SqlCommandBuilder(及其子类MongoCommandBuilder等):负责动态构建查询命令。它持有查询的上下文(SELECT 哪些列、WHERE 条件是什么),最终生成BoundSql。它是有状态的对象。
存在的问题
这种分离虽然遵循了单一职责原则,但在实际扩展和维护中暴露出了明显的问题:
- 抽象割裂:当我们要适配一种新数据库(例如 TiDB)时,实现一个
TiDBDialect很容易,但如果它的 SQL 语法比较特殊,我们可能需要修改通用的SqlCommandBuilder甚至继承一个新的 Builder。对于 MongoDB 这种非 SQL 数据源,情况更糟:我们需要创建特定的MongoCommandBuilder,并且必须在上层代码(如LambdaTemplate)中硬编码判断逻辑来决定实例化哪个 Builder。 - API 使用繁琐:用户或上层框架在构建查询时,必须显式地进行“配对”。
- MySQL 场景:
new SqlCommandBuilder(new MySqlDialect()) - Mongo 场景:
new MongoCommandBuilder(new MongoDialect())
- MySQL 场景:
- 中间类冗余:为了适配 NoSQL,我们引入了
MongoBuilderDialect这样的胶水代码,仅仅是为了把 Dialect 和 Builder 粘合在一起,这增加了代码库的复杂度。