数据库方言机制

郭胜凯2026/08/17

Smart MyBatis 3.0.2 将不同数据库的 SQL 差异集中到 SqlDialects 接口。实体解析、条件树和 Mapper API 保持统一,标识符引用、分页、字段类型、DDL、批量插入和主键回填等差异由方言实现负责。

启动时如何选择方言

启动链路如下:

  1. SmartMybatisInitializer 绑定 spring.mybatis.smart.*
  2. 优先读取 spring.mybatis.smart.dialect-driver-class-name
  3. 未显式配置时,读取 spring.datasource.driver-class-name
  4. DialectResolver 扫描带 @SmartDialect 的方言实现,按 JDBC driver class 精确匹配。
  5. 方言实例保存到 SmartConfigHolder,SQL Provider 和表结构同步统一从这里读取。

应用启动日志会输出最终选择,例如:

SmartMybatis initialized: ..., dialect: H2Dialect

注意

当前版本无法识别驱动时会回退到 MysqlDialect。接入非内置数据库时,应显式配置或实现方言,并通过启动日志确认结果。

内置方言

数据库JDBC driver class主要适配
MySQLcom.mysql.cj.jdbc.Drivercom.mysql.jdbc.Driver反引号、LIMIT offset,size、AUTO_INCREMENT、InnoDB、字段注释
H2org.h2.DriverH2 类型、IDENTITY/AUTO_INCREMENT、批量插入、ALTER COLUMN
Oracleoracle.jdbc.OracleDriveroracle.jdbc.driver.OracleDriverFETCH FIRST/OFFSET、NUMBER/VARCHAR2/CLOB、IDENTITY、MODIFY、无 AS 表别名
DB2com.ibm.db2.jcc.DB2DriverCOM.ibm.db2.jdbc.app.DB2DriverFETCH FIRST/OFFSET、DB2 类型、IDENTITY、SET DATA TYPE

仅仅能连接数据库不代表方言已经完整适配。至少应验证:

  • 标识符引用和大小写
  • 分页、排序和关联查询
  • Java 类型、枚举和 JSON 字段
  • 建表、补列和修改列
  • AUTO/INPUT/UUID/SNOWFLAKE 主键
  • 单条和批量插入后的主键回填
  • 自定义 SQL 与初始化脚本

SQL 如何经过方言

BaseSqlProvider 先通过 MapperUtil 获得 MapperDeclaration,再把 SQL 构建交给当前方言:

实体 + 注解
  -> MapperDeclaration / ColumnDeclaration
  -> BaseSqlProvider
  -> SmartConfigHolder.getDialect()
  -> SqlDialects
  -> MyBatis 执行

SqlDialects 同时提供两层扩展点:

  • 原子能力:quotebuildLimitjavaTypeToSqlbuildColumnDefbuildAutoPkDef、表别名等。
  • 完整语句:INSERT、SELECT、UPDATE、DELETE、CREATE TABLE、ALTER TABLE 和 WHERE 构建。

大多数方言只需覆盖有差异的原子能力;批量插入、ALTER TABLE 或主键回填差异较大时,再覆盖完整语句或行为钩子。

主键回填与批量插入

DefaultSmartMapperInitializer 会在 Mapper 初始化后调整 MyBatis 的 MappedStatement,为 insert 和批量插入配置 generated keys、keyPropertykeyColumn

MySQL、H2、DB2 默认走批量 SQL。Oracle 的 identity 批量回填存在额外限制,3.0.2 通过 usesBaseInsertForBatch() 让框架内部逐条复用基础 insert

int rows = classifyMapper.insertBatch(classifies);

业务代码仍然调用 insertBatch,每个对象的 AUTO 主键都会回填,同时返回累计影响行数。这个差异由框架和方言处理,不需要 Service 针对 Oracle 分支。

H2 大小写注意事项

当命名策略为 underline_upper,框架会生成带引号的大写列,例如 "AGE"。如果业务还执行未加引号的原始 SQL:

UPDATE SM_STUDENT SET age = age + 1 WHERE age < ?

建议 H2 URL 使用:

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:smart_mybatis;MODE=MySQL;DATABASE_TO_UPPER=true;DB_CLOSE_DELAY=-1

这样未加引号的 age 会解析为 AGE,与框架建表字段一致。

自定义方言

实现 SqlDialects,并用 @SmartDialect 声明驱动类名:

package ink.icoding.smartmybatis.mapper.provider.dialects.impl;

@SmartDialect("org.example.Driver")
public class ExampleDialect implements SqlDialects {
    @Override
    public String quote(String identifier) {
        return "\"" + identifier + "\"";
    }

    @Override
    public String buildLimit(int offset, int size) {
        return " OFFSET " + offset + " ROWS FETCH FIRST " + size + " ROWS ONLY";
    }
}

扫描范围

3.0.2 的 DialectResolver 只扫描 ink.icoding.smartmybatis.mapper.provider.dialects.impl 包。希望自动发现的自定义实现必须位于该包中,并确保类在运行时 classpath 上。

然后配置实际驱动或显式覆盖匹配驱动:

spring:
  mybatis:
    smart:
      dialect-driver-class-name: org.example.Driver

自定义方言至少应添加针对分页、类型、DDL、批量插入和 generated keys 的测试,不能只验证一条 SELECT。