Mastering MyBatis JSON Field Mapping with Custom TypeHandlers

This article walks through three ways to map JSON columns to Java objects in MyBatis, explains why simple String or JSONObject approaches are fragile, and provides a complete custom TypeHandler implementation, registration, and usage example for complex entity fields.

Architect's Journey
Architect's Journey
Architect's Journey
Mastering MyBatis JSON Field Mapping with Custom TypeHandlers

Mapping JSON columns to Java fields

Three common approaches are presented:

String column – store JSON as plain text and parse manually on each access.

JSONObject libraries – use com.alibaba.fastjson.JSONObject or cn.hutool.json.JSONObject to parse the string, which still requires explicit key extraction and adds an extra dependency.

Custom MyBatis TypeHandler – define a handler that converts between a Java type and the JDBC VARCHAR column.

BaseTypeHandler abstraction

The MyBatis class org.apache.ibatis.type.BaseTypeHandler<T> provides a generic base for custom handlers. The essential methods are:

setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)

– writes the Java value to the SQL statement. getNullableResult(ResultSet rs, String columnName) and overloads – read the column value and convert it back to T.

Two abstract hooks that subclasses must implement: protected abstract String convert(T obj) – serialize the Java object to a string. protected abstract T parse(String result) – deserialize the string to the Java object.

public abstract class BaseTypeHandler<T> extends org.apache.ibatis.type.BaseTypeHandler<T> {
    public Class<T> type() {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), 0);
    }
    protected abstract String convert(T obj);
    protected abstract T parse(String result);
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) return;
        ps.setString(i, this.convert(parameter));
    }
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String str = rs.getString(columnName);
        return str == null || str.isEmpty() ? null : this.parse(str);
    }
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String str = rs.getString(columnIndex);
        return str == null || str.isEmpty() ? null : this.parse(str);
    }
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String str = cs.getString(columnIndex);
        return str == null || str.isEmpty() ? null : this.parse(str);
    }
}

JsonStringTypeHandler

A concrete abstract subclass handles JSON serialization using JsonKit. It supports both single POJOs and arrays of POJOs (array, not List).

public abstract class JsonStringTypeHandler<T> extends BaseTypeHandler<T> {
    private Class<?> componentType;
    private Object[] componentArray;
    public JsonStringTypeHandler() {
        Class<T> tClass = type();
        if (tClass.isArray()) {
            Class<Object[]> arrayClass = (Class<Object[]>) tClass;
            this.componentType = arrayClass.getComponentType();
            this.componentArray = (Object[]) Array.newInstance(componentType, 0);
        }
        log.info("Loading {}, type: {}", this.getClass().getSimpleName(), type().getSimpleName());
    }
    @Override
    protected String convert(T obj) {
        return JsonKit.toJson(obj);
    }
    @Override
    protected T parse(String json) {
        if (this.componentType != null) {
            List<?> list = JsonKit.toList(json, this.componentType);
            if (list == null) return null;
            return (T) list.toArray(this.componentArray);
        }
        return JsonKit.toObject(json, type());
    }
}

Spring‑boot registration

All beans that extend BaseTypeHandler are injected and registered with MyBatis’ TypeHandlerRegistry during application startup.

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(DataSource.class)
@AllArgsConstructor
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisConfiguration.class})
public class MybatisPlusConfig implements InitializingBean {
    final MybatisPlusProperties mybatisPlusProperties;
    final List<BaseTypeHandler> jsonStringTypeHandlers;
    @Override
    public void afterPropertiesSet() {
        MybatisConfiguration configuration = mybatisPlusProperties.getConfiguration();
        if (configuration == null) {
            configuration = new MybatisConfiguration();
            mybatisPlusProperties.setConfiguration(configuration);
        }
        TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry();
        if (jsonStringTypeHandlers != null && !jsonStringTypeHandlers.isEmpty()) {
            for (BaseTypeHandler handler : jsonStringTypeHandlers) {
                registry.register(handler.type(), handler);
            }
        }
    }
}

Entity definition

After registration, JSON fields can be declared directly in entity classes without additional annotations.

@Data
@TableName("order_info")
public class OrderInfo {
    private UserInfoVO userInfo;          // JSON snapshot of the user
    private GoodsSpuVO[] goodsSpus;       // JSON array of product snapshots
}

Two concrete handlers are created and annotated with @Component so that Spring discovers them:

@Component
public class UserInfoTypeHandler extends JsonStringTypeHandler<UserInfoVO> {}

@Component
public class GoodsSpusTypeHandler extends JsonStringTypeHandler<GoodsSpuVO[]> {}

Limitation

The current implementation can map JSON arrays only to Java arrays; mapping to List<T> is not supported by MyBatis.

Additional handlers

Other custom storage formats (e.g., simple string lists, numeric arrays) are implemented as additional TypeHandler subclasses and integrated into the D3Boot framework. Source code is available at https://gitee.com/jensvn/d3boot.

JavaJSONMyBatisORMTypeHandlerD3Boot
Architect's Journey
Written by

Architect's Journey

E‑commerce, SaaS, AI architect; DDD enthusiast; SKILL enthusiast

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.