在 Spring Boot 同时支持 PostgreSQL 和达梦数据库的系统中,页面查询可能突然出现如下异常:
PreparedStatementCallback; SQL [...]; 数据类型不匹配这类问题常见于逻辑删除字段、启停标记等布尔语义列。它的难点不在于某一条 SQL,而在于同一套业务代码需要同时适配 PostgreSQL 的 boolean 和达梦常见的 bit 存储方式。
一、典型触发场景
假设逻辑删除字段在 PostgreSQL 中为 boolean,在达梦中为 bit。业务 SQL 按 PostgreSQL 习惯书写:
select app.*
from sys_portal_application app
left join sys_portal_application_integration integration
on integration.application_id = app.id
and integration.deleted_flag = false
where app.deleted_flag = ?调用方再传入 Java Boolean.FALSE 作为最后一个参数。达梦无法将 SQL 中的 false 字面量或 JDBC Boolean 参数自动等价为 bit 值时,就会抛出“数据类型不匹配”。
二、先区分两个问题
排查时不要只盯着 where deleted_flag = ?,应同时检查以下两类输入。
- SQL 布尔字面量:
deleted_flag = false、set deleted_flag = true。 - PreparedStatement 参数:
deleted_flag = ?,实际传入Boolean。
只把 false 替换为 0,仍可能在 Boolean 参数绑定时失败;只修改参数绑定,关联条件中的 false 也仍然会失败。因此必须同时处理。
三、不要在业务层散落达梦写法
将所有业务 SQL 改成 deleted_flag = 0 可以短暂解决达梦问题,但 PostgreSQL 的 boolean 列不能直接与整数比较。这会带来三个问题:
- 一套 SQL 失去双库兼容性。
- 相同的兼容判断散落到多个业务模块,难以审查和维护。
- 新增查询仍容易遗漏,导致问题反复出现。
更合适的边界是在统一数据库方言层处理,而不是在门户、用户、字典等业务服务中分支判断数据库类型。
四、统一 JDBC 兼容层的实现方式
对于 MyBatis,可以通过拦截器和 Boolean TypeHandler 处理 SQL 与参数。对于直接使用 JdbcTemplate 的代码,还需要提供一个统一的适配模板,并将它注册为主 JdbcTemplate Bean。
核心逻辑如下:
final class DialectAwareJdbcTemplate extends JdbcTemplate {
private final boolean dameng;
@Override
public int update(String sql, Object... args) {
return super.update(adaptSql(sql), adaptArgs(args));
}
@Override
public <T> List<T> query(String sql, RowMapper<T> mapper, Object... args) {
return super.query(adaptSql(sql), mapper, adaptArgs(args));
}
private String adaptSql(String sql) {
return dameng ? rewriteBooleanLiterals(sql) : sql;
}
private Object[] adaptArgs(Object[] args) {
if (!dameng) {
return args;
}
return Arrays.stream(args)
.map(value -> value instanceof Boolean flag ? (flag ? 1 : 0) : value)
.toArray();
}
}在达梦环境中,适配器将 SQL 中非字符串、非注释区域的 true 和 false 分别改写为 1 和 0,并将 JDBC 参数中的 Boolean 转为整数。在 PostgreSQL 环境中,SQL 和参数保持原样。
实际实现应覆盖项目实际使用的 JdbcTemplate 重载,例如 query、queryForObject、queryForList、queryForMap 与 update。否则某些页面虽然修复,另一些调用重载的页面仍可能绕过兼容层。
五、SQL 改写的安全要求
不能使用简单的全局字符串替换。以下内容不应被改写:
select 'false' as example_value; -- false is a text value改写器至少应识别单引号字符串、双引号标识符、单行注释和块注释,只替换真正的 SQL 标识符 token。这样才能避免破坏文本数据、注释和动态 SQL。
六、验证策略
建议为兼容层保留以下回归测试:
- 达梦模式下,
deleted_flag = false被改写为deleted_flag = 0。 - 达梦模式下,
Boolean.FALSE参数按整数0绑定。 - PostgreSQL 模式下,SQL 中的
false和 Java Boolean 参数保持不变。 - 包含关联条件的查询中,主表和关联表的全部逻辑删除条件都会被改写。
- 字符串和注释中的
true、false不会被改写。
除单元测试外,还应在达梦环境执行一次包含 JOIN、逻辑删除条件和分页参数的真实查询,确认驱动与实际表字段定义能够正常协作。
七、结论
双数据库支持的关键不是让每一条 SQL 都“看起来兼容”,而是将数据库差异收敛到统一方言层。对于 Boolean/BIT 不匹配,必须同时处理 SQL 布尔字面量和 JDBC 参数绑定;只修复其中一项,问题仍会在其他查询路径中复现。
这种方案既保持了 PostgreSQL 与达梦的共同业务 SQL,也让后续模块复用同一套兼容能力,减少重复修复和生产环境差异。
评论
0成为第一个留下想法的人。