解决XML映射中的数据类型转换问题

MyBatis 中自定义 typeHandler 不生效,需全局注册并显式声明于 resultMap;#{} 支持类型转换,${} 仅字符串拼接;JSON/LocalDateTime 等需专用 TypeHandler;嵌套 resultMap 中 typeHandler 需单独指定,且必须双向实现。

MyBatis 中 resultMap 的 typeHandler 不生效?检查注册方式和作用域

MyBatis 默认对基本类型(StringIntegerDate)有内置 TypeHandler,但自定义类型(如枚举、LocalDateTime、JSON 字符串转对象)必须显式绑定,否则会报 Cannot convert value of type 'java.lang.String' to required type 或字段为 null

常见错误是只在 resultMap 里写了 typeHandler 属性,却没在全局或映射语句中真正启用:

  • resultMap 中的 仅对当前字段生效,但前提是该 TypeHandler 已被 MyBatis 加载(即已注册)
  • 未注册的 TypeHandler 会被静默忽略,不会报错,只返回 null
  • 推荐在 mybatis-config.xml 中全局注册:
    
      
    
  • 若用 Spring Boot + MyBatis-Plus,改用 @Configuration 类中注册 Configuration.addTypeHandler(...)

XML 中 #{}${} 对数据类型的影响完全不同

#{} 是预编译参数占位符,MyBatis 会根据字段类型自动选择 TypeHandler${} 是字符串拼接,绕过所有类型转换逻辑,直接转成 String 后拼入 SQL —— 这是多数“类型丢失”的根源。

例如传入 LocalDateTime

  • WHERE create_time > #{startTime} → 正确:MyBatis 调用 LocalDateTimeTypeHandler,生成带时区的 JDBC 参数
  • WHERE create_time > '${startTime}' → 错误:调用 toString(),结果可能是 "2025-05-20T14:30:00",数据库报语法错误或隐式转换失败
  • ORDER BY ${sortField} 属于合法动态列名场景,但绝不能用于值参数

PostgreSQL / MySQL 的 jsonb / JSON 字段在 XML 映射中需要专用 TypeHandler

数据库原生 JSON 类型无法被 MyBatis 默认处理,直接映射到 String 会丢失结构,映射到 Object 则抛 ClassCastException

必须使用第三方 TypeHandler,例如 Jackson:

  • Maven 引入:com.fasterxml.jackson.core:jackson-databindorg.mybatis:mybatis-typehandlers-jsr310(后者仅支持 JSR310 时间类型,不支持 JSON)
  • 自定义 JsonTypeHandler,重写 setNonNullParametergetNullableResult,内部用 ObjectMapper 序列化/反序列化
  • resultMap 中指定:
  • 注意:MySQL 8.0+ 的 JSON 类型需 JDBC 驱动开启 allowPublicKeyRetrieval=true 才能读取二进制 JSON 值,否则返回空

嵌套 resultMap 时子映射的 typeHandler 容易被父级覆盖

当主 resultMap 引用另一个 resultMap(如 ),子映射中声明的 typeHandler 可能被忽略——MyBatis 优先使用父级 resultMap 的类型推导逻辑。

解决方法很直接:

  • 不要依赖嵌套映射自动继承 typeHandler,每个字段都显式声明 typeHandler
  • 若子对象字段类型与数据库列类型不一致(如 DB 存 VARCHAR,Java 期望 Enum),必须在子 resultMap 中写死 typeHandler
  • 避免用 autoMapping="true",它会跳过所有手动配置的 typeHandler

最常被忽略的一点:TypeHandler 必须同时实现 setParameter

(写入)和 getResult(读取),缺一不可;只重写一半会导致单向转换失败,现象是插入正常、查询为 null