MyBatis 3.6.0 更新 JSONB 报事务已中止,检查参数元数据
MyBatis 3.6.0 探测 JDBC 参数类型时,to_jsonb 的未知参数可能先让 PostgreSQL 事务失败。显式声明 jdbcType,区分描述阶段与后续25P02,并处理原事务。
升级 MyBatis 3.6.0 后,原来能执行的 PostgreSQL JSON 更新在参数设置阶段失败,最外层只看到:
ERROR: current transaction is aborted, commands ignored until end of transaction block
向前找同一连接的异常链,还可能发现 PgPreparedStatement.getParameterMetaData() 报 could not determine polymorphic type because input has type unknown。这时要查的是参数绑定前的类型探测,而不只是最后一条 UPDATE。
该组合见于 PostgreSQL 18、PgJDBC 42.7.14 与 MyBatis 3.6.0,SQL 中含 to_jsonb(?),且连接已经进入事务。仅设置 autoCommit=false,但还没执行语句开启事务,不一定出现后续的事务失败状态。不能把所有 25P02 都归到 MyBatis 升级上。
参数还没赋值,类型描述已经失败
MyBatis 3.6.0 在映射没有明确 jdbcType 时,会先调用 JDBC 参数元数据接口,为 type handler 选择提供 JDBC 类型。PgJDBC 获取这份元数据需要让服务器描述语句;此时占位参数还没有通过 setBoolean()、setString() 等方法确定类型。
to_jsonb(anyelement) 是多态函数。单独的未知类型参数不足以决定 anyelement,描述阶段就可能失败。MyBatis 虽然捕获了这次 SQLException 并继续尝试后面的参数处理,已经开启的 PostgreSQL 事务却可能保持失败状态,后续执行因而得到 25P02。
MyBatis 3.5.19 的原参数处理没有这次相同的元数据探测。这个差别解释了“SQL 没改,升级后才坏”的入口;不要因此把问题解释成 PostgreSQL 不再支持 JSONB 更新,或归因于业务对象没有值。
在受影响的参数上明确 JDBC 类型
若 mapper 原本就明确接收布尔值,可以给该占位参数声明 jdbcType=BOOLEAN。下面是只读 mapper 的示例片段,enabled 对应现有 mapper 方法中的同名参数:
<select id="flagAsJson" resultType="string">
SELECT to_jsonb(#{enabled,jdbcType=BOOLEAN})
</select>
3.6.0 的参数处理会优先使用已经声明的 JDBC 类型,这个参数便不必先走 getParameterMetaData()。实际 UPDATE 中有多个未知类型参数时,应逐个按业务类型声明;布尔参数用 BOOLEAN,普通文本参数可以用 VARCHAR,不能给全部参数套同一个类型。
声明必须符合真实 Java 值、数据库表达式和已有自定义 type handler。尤其是允许 NULL 的参数,要分别检查 SQL NULL、JSON null 与 JSON 字符串的原有约定;不要用空字符串或 false 填掉缺失值来绕过探测。只设置全局 jdbcTypeForNull 也不能等同于在映射上显式声明类型,因为这里的元数据探测发生得更早。
另一条路径是在 SQL 中给多态参数写明类型,例如 mapper 表达式中的 to_jsonb(CAST(#{enabled} AS boolean))。业务值确实是文本时才使用 text 转换;把布尔量强转成 text,会把 JSON 布尔值变成 JSON 字符串。优先选择与项目既有映射方式一致的改法,不必同时重写全部 JSON 更新。
修正映射后,原来的失败事务仍要结束
已经出现 25P02 的连接不会因为换了一份 mapper 就自动恢复。应由应用现有的事务管理入口回滚这笔失败事务,再按业务重试规则重新开始;回滚会撤销其中尚未提交的修改,不能当作无影响的清理动作。受 Spring 等框架管理的事务,不要另开连接执行 ROLLBACK 来“修复”原事务。
只要最早的异常不在参数元数据或多态类型解析路径,就继续按首条数据库错误定位。批量导入中某条语句失败后反复出现同样提示的情形,可对照失败事务的处理边界,其中 psql 参数不适用于 Java 事务管理。
在隔离数据和原有事务边界下,覆盖先执行一条查询再调用 mapper 的路径、正常布尔及文本值,以及允许的 NULL。确认不再由元数据探测产生首条错误,更新后的 JSON 类型和值正确,回滚与重试也没有重复业务修改。保留原 mapper 和依赖锁定;回退代码不等于恢复已经提交的数据。


