数据库方言机制
Smart MyBatis 3.0.2 将不同数据库的 SQL 差异集中到 SqlDialects 接口。实体解析、条件树和 Mapper API 保持统一,标识符引用、分页、字段类型、DDL、批量插入和主键回填等差异由方言实现负责。
启动时如何选择方言
启动链路如下:
SmartMybatisInitializer绑定spring.mybatis.smart.*。- 优先读取
spring.mybatis.smart.dialect-driver-class-name。 - 未显式配置时,读取
spring.datasource.driver-class-name。 DialectResolver扫描带@SmartDialect的方言实现,按 JDBC driver class 精确匹配。- 方言实例保存到
SmartConfigHolder,SQL Provider 和表结构同步统一从这里读取。
应用启动日志会输出最终选择,例如:
SmartMybatis initialized: ..., dialect: H2Dialect
注意
当前版本无法识别驱动时会回退到 MysqlDialect。接入非内置数据库时,应显式配置或实现方言,并通过启动日志确认结果。
内置方言
| 数据库 | JDBC driver class | 主要适配 |
|---|---|---|
| MySQL | com.mysql.cj.jdbc.Driver、com.mysql.jdbc.Driver | 反引号、LIMIT offset,size、AUTO_INCREMENT、InnoDB、字段注释 |
| H2 | org.h2.Driver | H2 类型、IDENTITY/AUTO_INCREMENT、批量插入、ALTER COLUMN |
| Oracle | oracle.jdbc.OracleDriver、oracle.jdbc.driver.OracleDriver | FETCH FIRST/OFFSET、NUMBER/VARCHAR2/CLOB、IDENTITY、MODIFY、无 AS 表别名 |
| DB2 | com.ibm.db2.jcc.DB2Driver、COM.ibm.db2.jdbc.app.DB2Driver | FETCH 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 同时提供两层扩展点:
- 原子能力:
quote、buildLimit、javaTypeToSql、buildColumnDef、buildAutoPkDef、表别名等。 - 完整语句:INSERT、SELECT、UPDATE、DELETE、CREATE TABLE、ALTER TABLE 和 WHERE 构建。
大多数方言只需覆盖有差异的原子能力;批量插入、ALTER TABLE 或主键回填差异较大时,再覆盖完整语句或行为钩子。
主键回填与批量插入
DefaultSmartMapperInitializer 会在 Mapper 初始化后调整 MyBatis 的 MappedStatement,为 insert 和批量插入配置 generated keys、keyProperty 和 keyColumn。
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。
