MyBattis入坑之类型转换实用版

1,131 阅读5分钟

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

MyBatis实现自定义转换类型

MyBatis为我们提供了很多内置的TypeHandler(类型转换), 随便打开一个TypeHandler,看其源码,都可以看到,它继承自一个抽象类:BaseTypeHandler, 那么我们是不是也能通过继承BaseTypeHandler,从而实现自定义的TypeHandler ? 答案是肯定的, 那么现在下面就为大家演示一下自定义TypeHandler。

我们选择继承BaseTypeHandler来完成工作,BaseTypeHandler需要实现4个方法:

  • public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException
    

    用于定义设置参数时,该如何把 Java 类型的参数转换为对应的数据库类型

  • public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException
    

    用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的 Java 类型

  • public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException
    

    用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的 Java 类型

  • public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException
    

    用定义调用存储过程后,如何把数据库类型转换为对应的 Java 类型

1.List<String>varchar类型转换

1.1 环境准备

CREATE TABLE `person` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`hobby`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
);

个人感觉这种情况还是比较使用的,在关系型数据中可以经常搞一下,方便。

public class Person {
  private Integer id;
  private List<String> hobbyList;
	// 省略getter和setter方法
}

1.2 自定义转换类型

其实就是对预处理赋值的操作和结果集的转换,下面的转换器对JavaType和JdbcType进行互转

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

public class MyListTypeHandler extends BaseTypeHandler<List<String>> {

    @Override
    public List<String> getNullableResult( ResultSet rs , String columnName ) throws SQLException {
        return this.getList(rs.getString(columnName));
    }

    @Override
    public List<String> getNullableResult( ResultSet rs , int columnIndex ) throws SQLException {
        return this.getList(rs.getString(columnIndex));
    }

    @Override
    public List<String> getNullableResult( CallableStatement cs , int columnIndex ) throws SQLException {
        return this.getList(cs.getString(columnIndex));
    }

    @Override
    public void setNonNullParameter( PreparedStatement ps , int index , List<String> parameter , JdbcType jdbcType )
            throws SQLException {
        //1.List集合转字符串
        StringBuffer sb = new StringBuffer();
        for (String value : parameter) {
            sb.append(value).append(",");
        }
        //2.设置给ps
        ps.setString(index, sb.toString().substring(0, sb.toString().length() - 1));

    }

    private List<String> getList(String columnValue){
        if (columnValue == null) {
            return null;
        }
        return  Arrays.asList(columnValue.split(","));
    }
}
//参考代码:https://www.cnblogs.com/ifme/p/12766128.html

1.3 查询和变更操作

<mapper namespace="com.shxt.model.Person">
  <resultMap type="com.shxt.model.Person" id="BaseResultMapper">
    <id column="id" property="id"/>
    <!-- 使用自定义的转换 -->
    <result typeHandler="com.hanpang.handler.MyListTypeHandler" 
            column="hobby" jdbcType="VARCHAR"
            property="hobbyList" javaType="list"
            />
  </resultMap>
  <select id="load" parameterType="int" resultMap="BaseResultMapper">
    SELECT id,hobby FROM person WHERE id=#{id}
  </select>
  <insert id="add" parameterType="com.shxt.model.Person">
   INSERT INTO person (hobby) VALUES (
    # {hobbyList,
    javaType=java.util.List,
    jdbcType=VARCHAR,
    typeHandler=com.hanpang.handler.MyListTypeHandler}
    )
    </insert>
</mapper>

2.String[]varchar的类型转换

直接上代码

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

public class MyArrayTypeHander extends BaseTypeHandler<String[]> {
    /**
     * 获取数据结果集时把数据库类型转换为对应的Java类型
     * @param rs 当前的结果集
     * @param columnName 当前的字段名称
     * @return  转换后的Java对象
     * @throws SQLException
     */
    @Override
    public String[] getNullableResult( ResultSet rs , String columnName ) throws SQLException {
        return this.getStringArray(rs.getString(columnName));
    }
    /**
     * 通过字段位置获取字段数据时把数据库类型转换为对应的Java类型
     * @param rs 当前的结果集
     * @param columnIndex 当前字段的位置
     * @return 转换后的Java对象
     * @throws SQLException
     */
    @Override
    public String[] getNullableResult( ResultSet rs , int columnIndex ) throws SQLException {
        return this.getStringArray(rs.getString(columnIndex));
    }
    /**
     * 调用存储过程后把数据库类型的数据转换为对应的Java类型
     * @param cs  当前的CallableStatement执行后的CallableStatement
     * @param columnIndex  当前输出参数的位置
     * @return
     * @throws SQLException
     */
    @Override
    public String[] getNullableResult( CallableStatement cs , int columnIndex ) throws SQLException {
        return this.getStringArray(cs.getString(columnIndex));
    }
    /**
     * 把Java类型参数转换为对应的数据库类型
     * @param ps 当前的PreparedStatement对象
     * @param index 当前参数位置
     * @param parameter 当前参数的Java对象
     * @param jdbcType 当前参数的数据库类型
     * @throws SQLException
     */
    @Override
    public void setNonNullParameter( PreparedStatement ps , int index , String[] parameter , JdbcType jdbcType )
            throws SQLException {
        // 由于BaseTypeHandler中已经把parameter为null的情况做了处理,所以这里我们就不用在判断parameter是否为空,直接用就可以了
        StringBuffer result = new StringBuffer();
        for (String value : parameter) {
            result.append(value).append(",");
        }
        result.deleteCharAt(result.length() - 1);

        ps.setString(index, result.toString());

    }

    /**
     * 讲"book,music"转化为数组对象
     * @param columnValue
     * @return
     */
    private String[] getStringArray(String columnValue) {
        if (columnValue == null) {
            return null;
        }
        return columnValue.split(",");
    }
}

3.Booleanvarchar的类型转换

需求场景:当数据库中保存'Y'/'N',而对应bean字段的值的类型为boolean,这是就需要我们自定义类型转换器,在Mybatis执行SQL得到结果时,通过自定义类型转换器将CHAR或者VARCHAR2类型转换为boolean类型,Java代码如下:

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

public class MyBooleanTypeHandler extends BaseTypeHandler<Boolean> {

    private Boolean getBoolean(String flag){
        Boolean bool = Boolean.FALSE;
        if (flag.equalsIgnoreCase("Y")){
            bool = Boolean.TRUE;
        }
        return bool;
    }

    @Override
    public Boolean getNullableResult( ResultSet rs , String columnName ) throws SQLException {
        return this.getBoolean(rs.getString(columnName));
    }

    @Override
    public Boolean getNullableResult( ResultSet rs , int columnIndex ) throws SQLException {
        return this.getBoolean(rs.getString(columnIndex));
    }

    @Override
    public Boolean getNullableResult( CallableStatement cs , int columnIndex ) throws SQLException {
        return this.getBoolean(cs.getString(columnIndex));
    }

    @Override
    public void setNonNullParameter( PreparedStatement ps , int index , Boolean parameter , JdbcType jdbcType )
            throws SQLException {
        String flag = parameter?"Y":"N";
        ps.setString(index, flag);
    }
}
<mapper namespace="com.shxt.model.Person">
  <resultMap type="com.shxt.model.Person" id="BaseResultMapper">
    <id column="id" property="id"/>
    <!-- 使用自定义的转换 -->
    <result typeHandler="com.shxt.type.MyListTypeHandler" 
            column="hobby" jdbcType="VARCHAR"
            property="hobbyList" javaType="list"
            />

    <result typeHandler="com.shxt.type.MyBooleanTypeHandler"
            column="flag" jdbcType="VARCHAR"
            property="flag" javaType="boolean"
            />
  </resultMap>
  <select id="load" parameterType="int" resultMap="BaseResultMapper">
    SELECT id,hobby,flag FROM person WHERE id=#{id}
  </select>
  <insert id="add" parameterType="com.shxt.model.Person">
    INSERT INTO person
    (hobby,flag)
    VALUES
    (
    #{hobbyList,
    javaType=java.util.List,
    jdbcType=VARCHAR,
    typeHandler=com.shxt.type.MyListTypeHandler},
    #{flag,typeHandler=com.shxt.type.MyBooleanTypeHandler}
    )
  </insert>
</mapper>

4.枚举类型转换器

public interface BaseEnum {
    int getCode();
}
public enum ProcessStatus implements BaseEnum{
    RUNNING(100, "running"),
    BLOCKED(101, "blocked"),
    STOPPED(102, "stopped");

    private int code;
    private String desc;

    ProcessStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    @Override
    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

然后再使用一个枚举工作类来完成从枚举 code 值获得枚举实例的工作:

public class EnumUtils {

  public static <T extends Enum<?> & BaseEnum> T codeOf(Class<T> enumClass, int code) {
    T[] enumConstants = enumClass.getEnumConstants();
    for (T t : enumConstants) {
      if (t.getCode() == code) {
        return t;
      }
    }
    return null;
  }
}

接下来完成自定义枚举处理器:

import com.foo.BaseEnum;
import com.foo.EnumUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class EnumCodeTypeHandler<E extends Enum<E> & BaseEnum> extends BaseTypeHandler<E> {

  private final Class<E> type;

  public EnumCodeTypeHandler(Class<E> type) {
    if (type == null) {
      throw new IllegalArgumentException("Type argument cannot be null");
    }
    this.type = type;
  }

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

  @Override
  public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
    int code = rs.getInt(columnName);
    return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
  }

  @Override
  public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    int code = rs.getInt(columnIndex);
    return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
  }

  @Override
  public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    int code = cs.getInt(columnIndex);
    return cs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
  }
}

在 mybatis-config.xml 中注册枚举处理器

<typeHandlers>
  <typeHandler handler="com.foo.EnumCodeTypeHandler" javaType="com.foo.ProcessStatus" />
</typeHandlers>

假设数据库process表有如下记录:

idnamestatus
1first101

ProcessMapper.java使用如下查询:

@Select("select * from process where id=#{id}")
public Process findById(int id);

查询结果 status 值将会对应到枚举实例 ProcessStatus.BLOCKED上。

查询结果Process{id=1, name='first', status=BLOCKED}

注意:设置默认枚举处理器

在 mybatis-config.xml 中为单个枚举注册枚举处理器的方式在需要处理的枚举数量增长时,会带来很多不必要的工作量,根据官方文档,我们可以在 configuration - settings节点下设置默认枚举处理器,没有特殊指定处理器的枚举都将默认使用这个处理器。

<settings>
    <setting name="defaultEnumTypeHandler" value="com.foo.EnumCodeTypeHandler" />    
</settings>

参考文档A:segmentfault.com/a/119000001…

参考文档B:www.cnblogs.com/lori/p/1146…