Playing with Type Conversion in mybatis

1. Scene

This requirement is common in everyday java development, where 0 or 1 code (not limited to numbers) is used to represent a certain state. For example, 0 for women and 1 for men. And writing to the database may be an identifier, read from the database and restore to specific instructions. And in general, in order to better understand or eliminate magic values, the usual solution is to define an enumeration:

Some enumerations are defined in this way

 public enum GenderType{
      FEMALE,MALE,UNKNOWN
 }

Usually a lot of people do this (java pseudocode)

  if(GenderType.MALE){
   // Write 1
  }else if(GenderType.FEMALE){
   // Write 0
  }else{
  //Or maybe it's the one Thailand came back with.
  } 

When reading, either do the same reverse processing as above or write DTO directly using database sql grammar case when

 CASE gender
 WHEN 1 THEN 'male'
 WHEN 0 THEN 'female'
 ELSE 'Unknown' END

This approach does not look very elegant. And a lot more judgment and processing logic is not very relevant to our business. So we can choose a better way to deal with it.

2. TypeHandler in Mybatis

If your ORM framework uses Mybatis. It will be easy to solve this problem through the TypeHandler < T > interface.

2.1 TypeHandler Analysis

public interface TypeHandler<T> {
  
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
 
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

Source code analysis:

  • The setParameter method writes your own logic through the incoming T type, and chooses a set method that calls the PreparedStatement object to write the data to the database. This method is used to write libraries.
  • GetResult (ResultSets, String Column Name) reads the library by field name and converts it to T type.
  • GetResult (ResultSets, int column nIndex) reads the library by field index and converts it to T type.
  • GetResult (Callable Statements, int column nIndex) calls stored procedures to get results and convert them to T type.

2.2 EnumOrdinalTypeHandler

We found that TypeHandler has an implementation class EnumOrdinalTypeHandler. Literally, types can be processed by enumerating serial numbers.

@Override
  public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
    ps.setInt(i, parameter.ordinal());
  }

Let's not consider setNull first. Through this method, we find that the order value of enumeration is actually stored (starting from 0), for example, if GenderType.FEMALE is 0, if GenderType.MALE is 1, but when GenderType.UNKNOWN, the order is 3. It is also handled naturally backward for specific GenderType enumerations.

2.3 EnumTypeHandler

We also found another enumeration type processor. Its set method is as follows:

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
    if (jdbcType == null) {
      ps.setString(i, parameter.name());
    } else {
      ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); // see r3589
    }
  }

We did not consider the jdbcType problem and found that the value of Enum.name() was written to the database. Take the example above, if GenderType.FEMALE is FEMALE, if GenderType.MALE is MALE, but when GenderType.UNKNOWN, it saves UNKNOWN. Reading libraries is reversed by Enum. valueOf (Class < T > enumType, String name).

2.4 Custom TypeHandler

What if our enumeration type or we use other ways to handle category conversion? Of course, Mybatis won't help you do such specific things. You need to do it yourself. We also take enumeration as an example, and then imitate the above two TypeHandler s. Or take the example from the beginning. Usually I prefer to define enumeration as follows:


public enum GenderTypeEnum {
    /**
     * female.
     */
    FEMALE(0, "female"),
     /**
     * male.
     */
    MALE(1,"male"),
    /**
     * unknown.
     */
    UNKNOWN(2, "Unknown");

    private int value;
    private String description;

    GenderType(int value, String description) {
        this.value = value;
        this.description = description;
    }
    
 
    public int value() {
        return this.value;
    }
    
 
    public String description() {
        return this.description;
    }
}

By inheriting BaseTypeHandler, we can implement three hook methods of the abstract class:

@MappedTypes({GenderTypeEnum.class})
@MappedJdbcTypes({JdbcType.INTEGER})
public class GenderTypeEnumTypeHandler extends BaseTypeHandler<GenderTypeEnum> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, GenderTypeEnum parameter, JdbcType jdbcType) throws SQLException {
        if (jdbcType == null) {
            ps.setInt(i, parameter.value());
        } else {
            // see r3589
            ps.setObject(i, parameter.value(), jdbcType.TYPE_CODE);
        }
    }

    @Override
    public GenderTypeEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return getGenderType(rs.getInt(columnName));
    }

    @Override
    public GenderTypeEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return getGenderType(rs.getInt(columnIndex));

    }

    @Override
    public GenderTypeEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return getGenderType(cs.getInt(columnIndex));
    }

    private GenderTypeEnum getGenderType(int value) {
        Class<GenderTypeEnum> genderTypeClass = GenderTypeEnum.class;
        return Arrays.stream(genderTypeClass.getEnumConstants())
                .filter(genderType -> genderType.value() == value)
                .findFirst().orElse(GenderTypeEnum.UNKNOWN);
    }
}

The TypeHandler implementation is written, so how can it work? Let's go on.

2.5 Key Points of TypeHandler

The role of TypeHandler is to convert javaType to jdbcType. So when declaring a TypeHandler, it's important to specify the two types that the TypeHandler handles. This is a principle that must be clearly defined. MyBatis does not use database meta-information to determine which JDBC type to use, so you have to specify which type of field in the parameter and result mapping so that it can be bound to the correct type processor. MyBatis does not know the data type until the statement is executed. Through the example above@ MappedJdbcTypes and @MappedTypes are used for binding type conversion relationships, and can also be specified by jdbcType or javaType in the xml typeHandler element. If specified at the same time, xml has a higher priority. Note that it is possible that you will override the built-in TypeHandler. So it's important to know some of the default processors Mybatis provides when customizing. Avoid any impact on other businesses. So one of the important principles for using custom TypeHandler is to declare Java Type and JdbcType. Although these are more obscure, they are very important for using a good TypeHandler. Next, let's talk about the specific configuration.

2.6 Registered-free TypeHandler

Let's just talk about configurations in xml:

  • A declaration in a rultMap element is commonly used for queries. We must pay attention to some of the principles in 2.5.
    <resultMap id="StudentMap" type="cn.felord.mybatis.entity.Student">
       <id column="student_id" property="studentId"/>
       <result column="student_name" property="studentName"/>
       <result column="gender" property="genderType" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
       <result column="age" property="age"/>
   </resultMap>
  • Then it is used in insert and update statements. They are all the same. Here's just one example of insertion.
    <insert id="saveStu">
        insert into student (student_name, gender, age)
        values (#{studentName},
                #{genderType,javaType=cn.felord.mybatis.enums.GenderTypeEnum,jdbcType=INTEGER,typeHandler=cn.felord.mybatis.type.GenderTypeEnumTypeHandler},
                #{age})
    </insert>

Aliases can be used if aliases are registered. The advantage is that you don't have to register in TypeHandler Registry.

2.7 Register TypeHandler

The TypeHandler is declared to be registered in the configuration, and Mybatis automatically matches between the two types. So here's the core point of 2.5.

  • If you are an xml configuration, you need to register declaratively in the <type Handlers> tag in the Configuration configuration file
<typeHandlers>
<typeHandler jdbcType="JdbcType Enumeration of Existence" javaType="typeAliases Alias or fully qualified class names"  handler="Fully qualified class name"/>
<package name="Designate all typeHandler The package name of the package in which it is located"/>
</typeHandlers>
  • In Java Config mode, the first is that you can register typeHandler through the SqlSessionFactory object to the Configuration object. If you use the mybatis-spring component, you can use SqlSession FactoryBean Configure the centralized package path of typeHandler in the setTypeHandlersPackage method, and the framework will automatically scan and register them. The corresponding configuration attribute in spring boot is mybatis.typeHandlersPackage.

If you sign up for TypeHandler. In Mapper.xml, you only need to declare jdbcType and javaType, and you don't need to declare a specific type handler. Mybatis automatically maps to a specific registered TypeHandler through jdbcType and javaType. Like the following example

    <insert id="saveAutomaticStu">
        insert into student (student_name, gender, age)
        values (#{studentName}, #{genderType,javaType=cn.felord.mybatis.enums.GenderTypeEnum,jdbcType=INTEGER}, #{age})
    </insert>

3. Summary

Today we learned how to use type processors for type conversion, enumeration, and custom processors in mybatis development. I believe it will be of great help to you in the process of java development. Relevant code in my code cloud warehouse: https://gitee.com/felord/mybatis-test.git

Keywords: Programming Mybatis Database Java xml

Added by BrandonK on Tue, 30 Jul 2019 15:49:26 +0300