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<T> extends BaseTypeHandler<T> {
    @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<E extends MyEnum> extends BaseTypeHandler<E> {
    private final Class<E> type;
    private final Map<Integer, E> enumMap = new ConcurrentHashMap<>();
    public EnumCodeTypeHandler(Class<E> 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.