在 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 = ?,应同时检查以下两类输入。

  1. SQL 布尔字面量:deleted_flag = falseset deleted_flag = true
  2. 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 中非字符串、非注释区域的 truefalse 分别改写为 10,并将 JDBC 参数中的 Boolean 转为整数。在 PostgreSQL 环境中,SQL 和参数保持原样。

实际实现应覆盖项目实际使用的 JdbcTemplate 重载,例如 queryqueryForObjectqueryForListqueryForMapupdate。否则某些页面虽然修复,另一些调用重载的页面仍可能绕过兼容层。

五、SQL 改写的安全要求

不能使用简单的全局字符串替换。以下内容不应被改写:

select 'false' as example_value; -- false is a text value

改写器至少应识别单引号字符串、双引号标识符、单行注释和块注释,只替换真正的 SQL 标识符 token。这样才能避免破坏文本数据、注释和动态 SQL。

六、验证策略

建议为兼容层保留以下回归测试:

  1. 达梦模式下,deleted_flag = false 被改写为 deleted_flag = 0
  2. 达梦模式下,Boolean.FALSE 参数按整数 0 绑定。
  3. PostgreSQL 模式下,SQL 中的 false 和 Java Boolean 参数保持不变。
  4. 包含关联条件的查询中,主表和关联表的全部逻辑删除条件都会被改写。
  5. 字符串和注释中的 truefalse 不会被改写。

除单元测试外,还应在达梦环境执行一次包含 JOIN、逻辑删除条件和分页参数的真实查询,确认驱动与实际表字段定义能够正常协作。

七、结论

双数据库支持的关键不是让每一条 SQL 都“看起来兼容”,而是将数据库差异收敛到统一方言层。对于 Boolean/BIT 不匹配,必须同时处理 SQL 布尔字面量和 JDBC 参数绑定;只修复其中一项,问题仍会在其他查询路径中复现。

这种方案既保持了 PostgreSQL 与达梦的共同业务 SQL,也让后续模块复用同一套兼容能力,减少重复修复和生产环境差异。