【MyBatis 填坑日记】IllegalStateException: Type handler was null on parameter mapping for property ‘options’
作者:@Neoest
时间:2025-07-29
关键词:MyBatis, TypeHandler, List, IllegalStateException
1. 问题现场
今天在联调一个新增接口时,后端突然抛出如下异常,整个服务直接 500:
Caused by: java.lang.IllegalStateException:
Type handler was null on parameter mapping for property 'options'.
It was either not specified and/or could not be found for the
javaType (java.util.List) : jdbcType (null) combination.
异常栈截图:
2. 先定位代码
DAO 层的 Mapper 接口方法签名:
int insertQuestion(@Param("question") QuestionDTO question);
对应的 XML 片段:
<insert id="insertQuestion">
INSERT INTO t_question(title, options, answer)
VALUES (
#{question.title},
#{question.options}, <!-- 这里挂了 -->
#{question.answer}
)
</insert>
QuestionDTO 里 options
的定义:
private List<String> options; // java.util.List
3. 为什么报错?
MyBatis 在解析 #{question.options}
时,发现:
- javaType = java.util.List
- jdbcType = null(你没显式写)
- 没有合适的 TypeHandler 能把
List<String>
直接塞进一个 JDBC 列。
于是它只能抛出 IllegalStateException
告诉你:“臣妾做不到”。
4. 三种解决方案
✅ 方案 A:把 List 转成 JSON 字符串(推荐)
步骤 1:引入 Jackson / Fastjson 依赖
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
步骤 2:在 DTO 里加转换逻辑
@Data
public class QuestionDTO {
private String title;
@TableField(typeHandler = JacksonTypeHandler.class) // MyBatis-Plus 写法
private List<String> options;
private String answer;
}
步骤 3:XML 保持不动即可
#{question.options} <!-- 现在 JacksonTypeHandler 会接管 -->
✅ 方案 B:手写 List → VARCHAR 的 TypeHandler
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ListStringTypeHandler extends BaseTypeHandler<List<String>> {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
List<String> parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, MAPPER.writeValueAsString(parameter));
}
@Override
public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
return MAPPER.readValue(rs.getString(columnName),
new TypeReference<List<String>>(){});
}
// 省略其余两个重载...
}
然后在 mybatis-config.xml
注册:
<typeHandlers>
<typeHandler handler="com.xxx.ListStringTypeHandler"/>
</typeHandlers>
✅ 方案 C:直接改表结构 —— 拆表
如果业务上 options
是可变长度、需要查询元素,建议拆成子表:
t_question_option
-----------------
id | question_id | option_value
这样就不需要任何 TypeHandler,MyBatis 自动搞定。
5. 小结
方案 | 适用场景 | 侵入性 | 备注 |
---|---|---|---|
JSON + Jackson | 90% 场景 | 低 | 最常用 |
自定义 TypeHandler | 需要精细控制 | 中 | 代码多 |
拆表 | 高频查询元素 | 高 | 最规范 |
6. 一键复制版补丁
如果你只想快速修完跑路:
<!-- pom.xml -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- DTO -->
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> options;
7. 参考资料
- MyBatis 官方文档 – Type Handlers
- Jackson 处理 JSON 数组最佳实践
如果本文帮到你,记得点赞收藏!评论区一起交流更多 MyBatis 坑位~