MyBatis Series: Using Type Processors in MyBatis

This blog mainly explains how to use type processors in MyBatis.

1. Define requirements

At the beginning of the design, the enabled field of the sys_role table had two optional values, 0 for disabled and 1 for enabled, and we used the Interger type in the entity class:

/**
 * Valid Sign
 */
private Integer enabled;

public Integer getEnabled() {
    return enabled;
}

public void setEnabled(Integer enabled) {
    this.enabled = enabled;
}

If you want to add or update role information, we must verify that the enabled field must have a value of 0 or 1, so the initial part of the code might be as follows:

if (sysRole.getEnabled() == 0 || sysRole.getEnabled() == 1) {
     sysRoleMapper.updateById(sysRole);

     sysRole = sysRoleMapper.selectById(2L);
     Assert.assertEquals(0, sysRole.getEnabled());
} else {
     throw new Exception("Invalid enabled value");
}

This hard-coding method not only looks unfriendly, but also is not good for later maintenance. If the maintained programmer is not good-tempered, he will also scold you, haha.

So what we need is to reject hard coding and use friendly encoding to verify that the enabled field is valid.

2. Use the Enumeration Type Processor provided by MyBatis

Enumerations are often used to solve this scenario.

First create a new com.zwhnly.mybatisation.type package, then create a new enumeration Enabled under the package:

package com.zwwhnly.mybatisaction.type;

public enum Enabled {
    /**
     * Disable
     */
    disabled,
    
    /**
     * Enable
     */
    enabled;
}

Where disabled has an index of 0 and enabled has an index of 1.

Then modify the enabled field in the SysRole class that was originally an Integer type to:

/**
 * Valid Sign
 */
private Enabled enabled;

public Enabled getEnabled() {
    return enabled;
}

public void setEnabled(Enabled enabled) {
    this.enabled = enabled;
}

The hard-coded code can then be modified to:

if (sysRole.getEnabled() == Enabled.disabled || sysRole.getEnabled() == Enabled.enabled) {
    sysRoleMapper.updateById(sysRole);

    sysRole = sysRoleMapper.selectById(2L);
    Assert.assertEquals(Enabled.disabled, sysRole.getEnabled());
} else {
    throw new Exception("Invalid enabled value");
}

Although the above code perfectly solves the hard coding problem, it raises a new problem:

The database does not recognize the Enabled enumeration type. When adding, updating, or serving as a query condition, the enumeration value needs to be converted to the int type in the database. When querying data, the value of the int type in the database needs to be converted to the Enabled enumeration type.

With this in mind, we add the following test methods to the SysRoleMapperTest test class:

@Test
public void testUpdateById() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysRoleMapper sysRoleMapper = sqlSession.getMapper(SysRoleMapper.class);

        // Query out the role with id=2 first, then change the enabled value of the role to disabled
        SysRole sysRole = sysRoleMapper.selectById(2L);
        Assert.assertEquals(Enabled.enabled, sysRole.getEnabled());

        // Modify role enabled to disabled
        sysRole.setEnabled(Enabled.disabled);

        if (sysRole.getEnabled() == Enabled.disabled || sysRole.getEnabled() == Enabled.enabled) {
            sysRoleMapper.updateById(sysRole);

            sysRole = sysRoleMapper.selectById(2L);
            Assert.assertEquals(Enabled.disabled, sysRole.getEnabled());
        } else {
            throw new Exception("Invalid enabled value");
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        sqlSession.close();
    }
}

Running the test code, the following exception was thrown:

Error querying database. Cause: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'enabled' from result set. Cause: java.lang.IllegalArgumentException: No enum constant com.zwwhnly.mybatisaction.type.Enabled.1

This is because MyBatis uses a TypeHandler (Type Processor) to convert Java types to database types.

MyBatis provides an implementation of the TypeHandler interface for Java types and common types in database JDBC.

MyBatis loads all JDBC type processors at startup and defaults to the org.apache.ibatis.type.EnumTypeHandler processor when processing enumeration types, which converts enumeration types to literal values of string types, which are the "disabled" and "enabled" strings for Enabled enumerations.

The enabled field in the database is of type int, so it is an error to convert value 1 of type int to type Enabled when querying role information.

So how to solve this problem?

MyBatis also provides another enumeration processor: org.apache.ibatis.type.EnumOrdinalTypeHandler, which handles enumerated indexes to resolve conversion errors here.

Using this processor, you need to add the following configuration to the previous resources/mybatis-config.xml:

<typeHandlers>
    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
                 javaType="com.zwwhnly.mybatisaction.type.Enabled"/>
</typeHandlers>

Run the test code again, the test passes, and the output log is as follows:

DEBUG [main] - ==> Preparing: SELECT id,role_name,enabled,create_by,create_time FROM sys_role WHERE id = ?

DEBUG [main] - ==> Parameters: 2(Long)

TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time

TRACE [main] - <== Row: 2, Ordinary user, 1, 1, 2019-06-27 18:21:12.0

DEBUG [main] - <== Total: 1

DEBUG [main] - ==> Preparing: UPDATE sys_role SET role_name = ?,enabled = ?,create_by=?, create_time=? WHERE id=?

DEBUG [main] - ==> Parameters: Ordinary user(String), 0(Integer), 1(Long), 2019-06-27 18:21:12.0(Timestamp), 2(Long)

DEBUG [main] - <== Updates: 1

As you can see from the log, MyBatis converts 1 to Enabled.enabled when querying for role information, and MyBatis converts Enabled.disabled to 0 when updating role information.

3. Use a custom type processor

Assuming that the enabled field value is neither the literal value of the enumeration nor the index value of the enumeration, neither org.apache.ibatis.type.EnumTypeHandler nor org.apache.ibatis.type.EnumOrdinalTypeHandler can meet our needs, in which case we need to implement the type handler ourselves.

First modify the Enabled code for the following enumeration class:

package com.zwwhnly.mybatisaction.type;

public enum Enabled {

    /**
     * Enable
     */
    enabled(1),

    /**
     * Disable
     */
    disabled(0);

    private final int value;

    private Enabled(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

Then create a new type processor EnabledTypeHandler under the package com.zwhnly.mybatisation.type:

package com.zwwhnly.mybatisaction.type;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 * Enabled Type Processor
 */
public class EnabledTypeHandler implements TypeHandler<Enabled> {
    private final Map<Integer, Enabled> enabledMap = new HashMap<Integer, Enabled>();

    public EnabledTypeHandler() {
        for (Enabled enabled : Enabled.values()) {
            enabledMap.put(enabled.getValue(), enabled);
        }
    }

    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, Enabled enabled, JdbcType jdbcType) throws SQLException {
        preparedStatement.setInt(i, enabled.getValue());
    }

    @Override
    public Enabled getResult(ResultSet resultSet, String s) throws SQLException {
        Integer value = resultSet.getInt(s);
        return enabledMap.get(value);
    }

    @Override
    public Enabled getResult(ResultSet resultSet, int i) throws SQLException {
        Integer value = resultSet.getInt(i);
        return enabledMap.get(value);
    }

    @Override
    public Enabled getResult(CallableStatement callableStatement, int i) throws SQLException {
        Integer value = callableStatement.getInt(i);
        return enabledMap.get(value);
    }
}

The custom type processor implements the TypeHandler interface, overrides four methods in the interface, traverses the enumerated type Enabled in the parameterless constructor, and assigns values to the field enabledMap.

To use a custom type processor, you also need to add the following configuration in resources/mybatis-config.xml:

<typeHandlers>
    <!--Other Configurations-->
    <typeHandler handler="com.zwwhnly.mybatisaction.type.EnabledTypeHandler"
                 javaType="com.zwwhnly.mybatisaction.type.Enabled"/>
</typeHandlers>

Run the test code, and the output log, like the output log above, will not be posted again here.

4. Source Code and Reference

Source address: https://github.com/zwwhnly/mybatis-action.git Welcome to download.

Liu Zenghui's MyBatis From Beginner to Master

If you think the article is well written, please pay attention to my WeChat public number: "Shencheng Foreigners". All blogs will be updated synchronously.

If you are interested, you can also add my WeChat: zwhnly_002, to communicate and explore technology together.

Twenty-nine original articles were published, 84 were praised, and 10,000 visits were received+
Private letter follow

Tags: Mybatis Java Apache Database

Posted on Sun, 12 Jan 2020 22:04:15 -0500 by komlos