news 2026/6/23 16:14:54

MyBatis 扩展BaseTypeHandler 转换泛型 JSON 列表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis 扩展BaseTypeHandler 转换泛型 JSON 列表

最近发现一个mybatis里面json转换的bug, 写了这么多年Java这方面还是没有理清楚, 把正确的处理方法记录一下.

一. 对象JSON转换

这个是比较简单的情况, 有通用的处理方法, 例如

用Jackson实现一个通用的 TypeHandler

@Slf4j public class JacksonTypeHandler<T> extends BaseTypeHandler<T> { private static ObjectMapper OBJECT_MAPPER; private final Class<T> clazz; public JacksonTypeHandler(Class<T> clazz) { if (log.isTraceEnabled()) { log.trace("JacksonTypeHandler[{}]", clazz); } this.clazz = clazz; } @Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, toJson(parameter)); } @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { return parse(rs.getString(columnName)); } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return parse(rs.getString(columnIndex)); } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return parse(cs.getString(columnIndex)); } protected T parse(String json) { if (json == null || json.isEmpty()) return null; try { return getObjectMapper().readValue(json, clazz); } catch (IOException e) { throw new RuntimeException(e); } } protected String toJson(T obj) { try { return getObjectMapper().writeValueAsString(obj); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } private static ObjectMapper getObjectMapper() { if (null == OBJECT_MAPPER) { OBJECT_MAPPER = new ObjectMapper(); OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); } return OBJECT_MAPPER; } }

使用时直接指定 typeHandler 就行

<result property="groupFilter" column="group_filter" typeHandler="com.somewhere.mybatis.JacksonTypeHandler"/>

以及

#{groupFilter, typeHandler=com.somewhere.mybatis.JacksonTypeHandler},

二. 列表JSON Array转换

字段中更常见的是 List 类型的JSON Array结构, 这种情况通过扩展 BaseTypeHandler 没有通用的处理方法, 有两种实现途径

通用的TypeHandler, 需要指定javaType

用 Jackson 实现一个通用的 TypeHandler, 注意构造函数中的 clazz, 如果不指定, 在默认情况下 mybatis 传进来的是一个不带泛型参数的 List

@Slf4j public class JacksonListTypeHandler<T> extends BaseTypeHandler<List<T>> { private static ObjectMapper OBJECT_MAPPER; private final TypeReference<List<T>> type; public JacksonListTypeHandler(Class<T> clazz) { log.info("JacksonListTypeHandler[{}]", clazz); this.type = new TypeReference<>() { @Override public Type getType() { // 返回参数化类型 List<T> return new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[]{clazz}; } @Override public Type getRawType() { return List.class; } @Override public Type getOwnerType() { return null; } }; } }; } @Override public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, toJson(parameter)); } @Override public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException { return parse(rs.getString(columnName)); } @Override public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return parse(rs.getString(columnIndex)); } @Override public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return parse(cs.getString(columnIndex)); } private List<T> parse(String json) { if (json == null || json.isEmpty()) return null; try { return getObjectMapper().readValue(json, type); } catch (IOException e) { throw new RuntimeException(e); } } protected String toJson(List<T> obj) { try { return getObjectMapper().writeValueAsString(obj); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } public static ObjectMapper getObjectMapper() { if (null == OBJECT_MAPPER) { OBJECT_MAPPER = new ObjectMapper(); OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); } return OBJECT_MAPPER; } }

除了用 TypeReference, 还可以用 JavaType

@Slf4j public class ListJacksonTypeHandler<T> extends BaseTypeHandler<List<T>> { private final JavaType type; public ListJacksonTypeHandler(Class<T> clazz) { log.info("ListJacksonTypeHandler[{}]", clazz); // 创建 List<T> 类型的 JavaType this.type = JacksonUtil.getObjectMapper() .getTypeFactory() .constructCollectionType(List.class, clazz); } // 其它一样 }

需要在 mapper 中强制指定javaType, 如果是MyBatis自带的简单类型, 可以直接用 alias(见下面的表格), 如果是自定义的对象, 则需要用对象类的完整包路径

<result property="ips" column="ips" javaType="string" typeHandler="com.somewhere.mybatis.JacksonListTypeHandler"/>

以及

#{users,javaType=string,typeHandler=com.somewhere.mybatis.JacksonListTypeHandler},

这样启动后, 如果初始化的type是正确的string 才能正确解析

2025-12-15T10:33:42.896+08:00 INFO 1 --- [some-service] [main] c.somewhere.mybatis.JacksonListTypeHandler: JacksonListTypeHandler[class java.lang.String]

抽象类, 根据对象类型实现具体的 TypeHandler

如果不在 mapper 中指定类型, 就需要在 TypeHandler 中指定, 这样就不是通用的了

写一个基类 AbstractListTypeHandler

@Slf4j public abstract class AbstractListTypeHandler<T> extends BaseTypeHandler<List<T>> { private static ObjectMapper OBJECT_MAPPER; @Override public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, toJson(parameter)); } @Override public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException { return parse(rs.getString(columnName)); } @Override public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return parse(rs.getString(columnIndex)); } @Override public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return parse(cs.getString(columnIndex)); } protected List<T> parse(String json) { if (json == null || json.isEmpty()) return null; try { return getObjectMapper().readValue(json, specificType()); } catch (IOException e) { throw new RuntimeException(e); } } protected String toJson(List<T> obj) { try { return getObjectMapper().writeValueAsString(obj); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } private static ObjectMapper getObjectMapper() { if (null == OBJECT_MAPPER) { OBJECT_MAPPER = new ObjectMapper(); OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); } return OBJECT_MAPPER; } protected abstract TypeReference<List<T>> specificType(); }

根据具体的使用场景, 创建对应的实现类

public class ListLongTypeHandler extends AbstractListTypeHandler<Long> { @Override protected TypeReference<List<Long>> specificType() { return new TypeReference<>() {}; } }

使用时, 直接用 typeHandler 指定, 不需要指定类型

#{setIds, typeHandler=com.somewhere.mybatis.ListLongTypeHandler}, ... @Result(column="set_ids", property="setIds", typeHandler= ListLongTypeHandler.class),

附: MyBatis 内建的 javaType Alias

链接: https://mybatis.org/mybatis-3/configuration.html#typeAliases

aliasjavaType
_bytebyte
_char (since 3.5.10)char
_character (since 3.5.10)char
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
char (since 3.5.10)Character
character (since 3.5.10)Character
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
bigintegerBigInteger
objectObject
date[]Date[]
decimal[]BigDecimal[]
bigdecimal[]BigDecimal[]
biginteger[]BigInteger[]
object[]Object[]
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/17 14:20:54

PKHeX.Mobile终极指南:移动端宝可梦存档编辑完全教程

PKHeX.Mobile终极指南&#xff1a;移动端宝可梦存档编辑完全教程 【免费下载链接】PKHeX.Mobile Pokmon save editor for Android and iOS! 项目地址: https://gitcode.com/gh_mirrors/pk/PKHeX.Mobile 你是否曾经遇到过这样的困扰&#xff1f;辛苦培养的宝可梦个体值不…

作者头像 李华
网站建设 2026/6/17 17:40:04

21、时间点与持续时间建模:原理、应用与挑战

时间点与持续时间建模:原理、应用与挑战 1. 获取当前状态 在处理无持续时间和有效性上限,且下一状态会终止其直接前一状态的情况时,准确识别和获取当前有效状态至关重要。可以通过将各个状态分类到由对象形成的分区组中来实现这一目标。 1.1 使用RANK函数获取当前状态 为…

作者头像 李华
网站建设 2026/6/22 20:35:27

如何快速掌握网站下载:WebSite-Downloader 完整使用指南

想要轻松将整个网站保存到本地吗&#xff1f;WebSite-Downloader 就是你的终极解决方案&#xff01;这款基于 Python 开发的网站整站下载工具&#xff0c;能够高效抓取网页内容、CSS 样式、JavaScript 脚本以及各类媒体文件&#xff0c;为你构建完整的本地网站镜像。无论是个人…

作者头像 李华
网站建设 2026/6/14 14:03:20

27、时态数据库概念解析

时态数据库概念解析 1. 面向属性的方法 1.1 基本原理 当数据变化同步且同时发生时,对象级时态解决方案是合适的。若不同步,会出现重复值,导致性能下降和磁盘存储成本上升,这是主键扩展处理时间元素所致。而面向属性的粒度则与属性本身相关,每个属性单独处理,强调变化的…

作者头像 李华
网站建设 2026/6/15 14:14:09

30、数据库镜像重建中的闪回管理与SQL注入防范

数据库镜像重建中的闪回管理与SQL注入防范 1. 闪回技术基础 在数据库管理中,闪回技术是一项强大的工具,用于将数据库恢复到过去的某个状态。要使用闪回技术,首先需要设置归档日志模式。当数据库处于MOUNT模式时,可以使用以下命令设置归档日志模式: ALTER DATABASE ARC…

作者头像 李华
网站建设 2026/6/21 20:17:49

Wan2.2-T2V-5B模型API封装实践:集成到Web应用的步骤详解

Wan2.2-T2V-5B模型API封装实践&#xff1a;集成到Web应用的步骤详解 在短视频内容爆炸式增长的今天&#xff0c;企业与创作者对“快速生成高质量视频”的需求从未如此迫切。传统制作流程依赖专业剪辑师、动画设计师和昂贵渲染设备&#xff0c;从创意到成品往往需要数小时甚至数…

作者头像 李华