news 2026/6/15 15:25:15

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/15 1:31:00

23、时间间隔表示与类型关系解析

时间间隔表示与类型关系解析 1. 持续时间管理与建模 1.1 单时间点建模 最初,人们尝试用单个时间点来表达有效性。但这种方法存在明显局限,数据存储和后续处理效率低下,因为可能出现大量重复元组。不过,当把这个时间点定义为有效性的起始点时,能对持续时间进行建模。每个…

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

32、构建可靠解决方案:避免 SQL 注入与时间戳增强

构建可靠解决方案:避免 SQL 注入与时间戳增强 1. SQL 注入问题解析 SQL 注入是一种通过用户输入扩展原始 SQL 语句代码的技术。用户不提供所需的值,而是提供可执行的 SQL 代码,这可能导致访问通常不可用(隐藏)的数据、进行未经授权的值更改或删除个别对象,严重损害系统的…

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

33、时间戳处理与跨时区转换

时间戳处理与跨时区转换 1. TIMESTAMP 与时区处理 1.1 TIMESTAMP 构造函数与时区 TIMESTAMP 构造函数本身不处理时区。不过,可以使用 FROM_TZ 函数为其添加时区信息。例如: -- 示例中展示 FROM_TZ 函数的使用 --> T1: 28.03.00 08:00:00,000000000 +03:00 --> …

作者头像 李华
网站建设 2026/6/13 18:48:15

LobeChat不再受支持的扩展程序问题解决办法

LobeChat不再受支持的扩展程序问题解决办法 在构建个人AI助手或团队知识库系统时&#xff0c;越来越多开发者选择开源方案替代官方闭源平台。LobeChat 作为一款现代化、高度可定制的聊天界面框架&#xff0c;凭借其优雅的UI设计和强大的插件生态&#xff0c;成为不少人的首选。…

作者头像 李华
网站建设 2026/6/15 1:29:59

英格卡与高和资本将成立不动产基金,共同持有无锡、北京、武汉荟聚三座聚会体验中心

、美通社消息&#xff1a;12月12日&#xff0c;英格卡购物中心宣布与高和资本达成战略合作&#xff0c;双方将携手成立一支专项不动产基金&#xff0c;共同持有无锡荟聚、北京荟聚、武汉荟聚三座聚会体验中心。此项合作将在获得中国相关主管部门的批准后正式生效。在这一新合作…

作者头像 李华
网站建设 2026/6/12 0:33:20

英雄联盟身份自由定制神器:LeaguePrank完整使用教程

英雄联盟身份自由定制神器&#xff1a;LeaguePrank完整使用教程 【免费下载链接】LeaguePrank 项目地址: https://gitcode.com/gh_mirrors/le/LeaguePrank 想要在英雄联盟中展现与众不同的个性风采吗&#xff1f;LeaguePrank正是你需要的完美解决方案&#xff01;这款基…

作者头像 李华