Backend Development 9 min read

Custom MyBatis TypeHandler for Automatic JSON and Enum Conversion

This article explains how to create custom MyBatis TypeHandler classes to automatically convert Java objects—such as JSON strings and enum codes—to appropriate JDBC types, simplifying data persistence in Spring Boot applications and improving code maintainability.

Java Captain
Java Captain
Java Captain
Custom MyBatis TypeHandler for Automatic JSON and Enum Conversion

Introduction

The author frequently encounters two conversion problems in recent projects: storing a JSON string from a Java entity into a VARCHAR column and storing an Enum's integer code into an INT column. Manually calling toJSONString() and parseObject() for JSON, or extracting the enum's int value for the database, works but is cumbersome.

What is a TypeHandler?

When using MyBatis or MyBatis‑Plus, every parameter setting and result retrieval passes through a TypeHandler, which maps Java types to JDBC types. MyBatis provides many built‑in handlers for primitive types, and developers can create custom handlers for complex types such as JSON strings or enums.

Conversion Steps

During execution of a prepared SQL statement, MyBatis locates the appropriate TypeHandler (defined in configuration or mapper XML), converts the Java value to a JDBC‑compatible value, and sets it on the PreparedStatement . The reverse occurs when reading results.

1. JSON Conversion

Add the following to the .yml configuration to scan custom handlers:

mybatis-plus:
  type-handlers-package: # package where custom handlers reside

Custom handler implementation:

@MappedTypes({JSONObject.class, JSONArray.class})
public class JSONTypeHandler
extends BaseTypeHandler
{
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T param, JdbcType jdbcType) throws SQLException {
        ps.setString(i, JSON.toJSONString(param));
    }
    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String jsonStr = rs.getString(columnName);
        return StringUtils.isNotBlank(jsonStr) ? JSON.parseObject(jsonStr, getRawType()) : null;
    }
    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String jsonStr = rs.getString(columnIndex);
        return StringUtils.isNotBlank(jsonStr) ? JSON.parseObject(jsonStr, getRawType()) : null;
    }
    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String jsonStr = cs.getString(columnIndex);
        return StringUtils.isNotBlank(jsonStr) ? JSON.parseObject(jsonStr, getRawType()) : null;
    }
}

2. Enum Conversion

Custom handler for mapping an enum's code field to an INT column:

@MappedTypes(MyEnum.class)
public class EnumCodeTypeHandler
extends BaseTypeHandler
{
    private final Class
type;
    private final Map
enumMap = new ConcurrentHashMap<>();
    public EnumCodeTypeHandler(Class
type) {
        Assert.notNull(type, "argument cannot be null");
        this.type = type;
        for (E e : type.getEnumConstants()) {
            enumMap.put(e.toCode(), e);
        }
    }
    @Override
    public void setNonNullParameter(PreparedStatement ps, int index, E e, JdbcType jdbcType) throws SQLException {
        ps.setInt(index, e.toCode());
    }
    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        if (rs.wasNull()) return null;
        return enumMap.get(code);
    }
    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        if (rs.wasNull()) return null;
        return enumMap.get(code);
    }
    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        if (cs.wasNull()) return null;
        return enumMap.get(code);
    }
}

Supporting interface that enums must implement:

public interface MyEnum {
    MyEnum fromCode(int code);
    int toCode();
}

Example enum implementing the interface:

@Getter
@RequiredArgsConstructor
public enum StudyStatusEnum implements MyEnum {
    ONE(1, "枚举1"),
    TWO(2, "枚举2"),
    THREE(3, "枚举3"),
    FOUR(4, "枚举4"),
    FIVE(5, "枚举5");
    private final Integer code;
    private final String desc;
    @Override
    public MyEnum fromCode(int code) {
        return Arrays.stream(values())
                     .filter(v -> v.getCode().equals(code))
                     .findFirst()
                     .orElse(null);
    }
    @Override
    public int toCode() {
        return this.getCode();
    }
}

Conclusion

By leveraging built‑in and custom TypeHandlers, developers can seamlessly handle various data‑type conversion needs, boost development efficiency, and keep code maintainable. In a Spring Boot environment, registering custom TypeHandlers is straightforward, allowing you to focus on business logic.

JavadatabaseenumJSONSpring BootMyBatisTypeHandler
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login 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.