Mybatis系列(六)typeHandler源码解析

上篇文章Mybatis系列(五)typeAliases源码解析,主要介绍了mybatis中别名的使用,这一节我们来看TypeHandler节点,翻译成中文就是类型处理器 。
在JDBC中,需要在PrepareStatement对象中设置那些已经预编译过的SQL语句的参数。执行SQL后,会通过ResultSet对象获取得到数据库的数据,而这些mybatis是数据的类型是通过typeHandler来实现的。在typeHandler中,分为jdbcType和JavaType,其中jdbcType用于定义数据库类型,而JavaType用于定义java类型,那么typeHandler的作用就是承担jdbcType和javaType之间的相互转换

一、typeHandler配置

<configuration>
    <typeHandlers>
      <!-- 
          当配置package的时候,mybatis会去配置的package扫描TypeHandler
          <package name="com.sean.entity"/>
       -->
      
      <!-- handler属性直接配置我们要指定的TypeHandler -->
      <typeHandler handler=""/>
      
      <!-- javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 -->
      <typeHandler javaType="" handler=""/>
      
      <!-- jdbcType 配置数据库基本数据类型,例如varchar, 如果配上jdbcType, 那么指定的typeHandler就只作用于指定的类型  -->
      <typeHandler jdbcType="" handler=""/>
      
      <!-- 也可两者都配置 -->
      <typeHandler javaType="" jdbcType="" handler=""/>
      
  </typeHandlers>
  
  ......
  
</configuration>

二、typeHandlerElement源码

    private void typeHandlerElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator i$ = parent.getChildren().iterator();

            while(i$.hasNext()) {
                XNode child = (XNode)i$.next();
                String typeHandlerPackage;
                //子节点为package时,获取name属性的值,然后自动扫描package下的自定义的typeHandler
                if ("package".equals(child.getName())) {
                    typeHandlerPackage = child.getStringAttribute("name");
                    this.typeHandlerRegistry.register(typeHandlerPackage);
                } else {
                	//子节点为typeHandler时,可以指定JavaType,jdbcType属性
                    typeHandlerPackage = child.getStringAttribute("javaType");
                    String jdbcTypeName = child.getStringAttribute("jdbcType");
                    //handler就是我们配置的typeHandler
                    String handlerTypeName = child.getStringAttribute("handler");
                    //resolveClass方法就是我们上篇文章所讲的TypeAliasRegistry里面处理别名的方法
                    Class<?> javaTypeClass = this.resolveClass(typeHandlerPackage);
                    //JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值
                    JdbcType jdbcType = this.resolveJdbcType(jdbcTypeName);
                    Class<?> typeHandlerClass = this.resolveClass(handlerTypeName);
                    if (javaTypeClass != null) {
                    	注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
                        if (jdbcType == null) {
                            this.typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                        } else {
                            this.typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                        }
                    } else {
                        this.typeHandlerRegistry.register(typeHandlerClass);
                    }
                }
            }
        }

    }

根据配置属性的不同,TypeHandlerRegistry一共提供了有三种注册方式。

  1. 只配置了typeHandler, 没有配置jdbcType 或者javaType
register(Class<?> typeHandlerClass)
  1. 配置了javaType和typeHandler
register(Class<?> javaTypeClass, Class<?> typeHandlerClass)
  1. typeHandlerhe、javaType、jdbcType都配置了
register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass)

最终都是调用下面的方法

	//注册typeHandler的核心方法
	//就是想Map中新增数据
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (javaType != null) {
            Map<JdbcType, TypeHandler<?>> map = (Map)this.TYPE_HANDLER_MAP.get(javaType);
            if (map == null) {
                map = new HashMap();
                this.TYPE_HANDLER_MAP.put(javaType, map);
            }

            ((Map)map).put(jdbcType, handler);
            if (reversePrimitiveMap.containsKey(javaType)) {
                this.register((Type)reversePrimitiveMap.get(javaType), jdbcType, handler);
            }
        }

        this.ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }

仔细观察源码,可以看到typeHandlertypeAliases类似,mybatis内部都提供了系统的默认配置,初始化的时候放入到一个Map中,当然用户也可以自定义配置,最终新增到Map。

三、系统定义的typeHandler

mybatis系统已经创建好了typeHandler,在大部分情况下无须显式地声明jdbcTypejavaType,或者用typeHandler去指定对应的typeHandler来实现数据类型的转换,因为mybatis系统会自动检测。

    public TypeHandlerRegistry() {
        this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));
        this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));
        this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler()));
        this.register((JdbcType)JdbcType.BIT, (TypeHandler)(new BooleanTypeHandler()));
        this.register((Class)Byte.class, (TypeHandler)(new ByteTypeHandler()));
        this.register((Class)Byte.TYPE, (TypeHandler)(new ByteTypeHandler()));
        this.register((JdbcType)JdbcType.TINYINT, (TypeHandler)(new ByteTypeHandler()));
        this.register((Class)Short.class, (TypeHandler)(new ShortTypeHandler()));
        this.register((Class)Short.TYPE, (TypeHandler)(new ShortTypeHandler()));
        this.register((JdbcType)JdbcType.SMALLINT, (TypeHandler)(new ShortTypeHandler()));
        this.register((Class)Integer.class, (TypeHandler)(new IntegerTypeHandler()));
        this.register((Class)Integer.TYPE, (TypeHandler)(new IntegerTypeHandler()));
        this.register((JdbcType)JdbcType.INTEGER, (TypeHandler)(new IntegerTypeHandler()));
        this.register((Class)Long.class, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)Long.TYPE, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)Float.class, (TypeHandler)(new FloatTypeHandler()));
        this.register((Class)Float.TYPE, (TypeHandler)(new FloatTypeHandler()));
        this.register((JdbcType)JdbcType.FLOAT, (TypeHandler)(new FloatTypeHandler()));
        this.register((Class)Double.class, (TypeHandler)(new DoubleTypeHandler()));
        this.register((Class)Double.TYPE, (TypeHandler)(new DoubleTypeHandler()));
        this.register((JdbcType)JdbcType.DOUBLE, (TypeHandler)(new DoubleTypeHandler()));
        this.register((Class)String.class, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));
        this.register((Class)String.class, JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));
        this.register((Class)String.class, JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((Class)String.class, JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((Class)String.class, JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));
        this.register((JdbcType)JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((JdbcType)JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((JdbcType)JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));
        this.register((JdbcType)JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));
        this.register((JdbcType)JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((JdbcType)JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((JdbcType)JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));
        this.register((Class)Object.class, JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));
        this.register((JdbcType)JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));
        this.register((Class)BigInteger.class, (TypeHandler)(new BigIntegerTypeHandler()));
        this.register((JdbcType)JdbcType.BIGINT, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)BigDecimal.class, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.REAL, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.DECIMAL, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.NUMERIC, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((Class)Byte[].class, (TypeHandler)(new ByteObjectArrayTypeHandler()));
        this.register((Class)Byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));
        this.register((Class)Byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));
        this.register((Class)byte[].class, (TypeHandler)(new ByteArrayTypeHandler()));
        this.register((Class)byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));
        this.register((Class)byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));
        this.register((JdbcType)JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));
        this.register((JdbcType)JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));
        this.register(Object.class, this.UNKNOWN_TYPE_HANDLER);
        this.register(Object.class, JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);
        this.register(JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);
        this.register((Class)Date.class, (TypeHandler)(new DateTypeHandler()));
        this.register((Class)Date.class, JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));
        this.register((Class)Date.class, JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));
        this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));
        this.register((JdbcType)JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));
        this.register((JdbcType)JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));
        this.register((Class)java.sql.Date.class, (TypeHandler)(new SqlDateTypeHandler()));
        this.register((Class)Time.class, (TypeHandler)(new SqlTimeTypeHandler()));
        this.register((Class)Timestamp.class, (TypeHandler)(new SqlTimestampTypeHandler()));
        this.register((Class)Character.class, (TypeHandler)(new CharacterTypeHandler()));
        this.register((Class)Character.TYPE, (TypeHandler)(new CharacterTypeHandler()));
    }

在mybatis中typeHandler都要实现接口org.apache.ibatis.type.typeHandler,我们来看下这个接口

public interface TypeHandler<T> {
    void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    T getResult(ResultSet var1, String var2) throws SQLException;

    T getResult(ResultSet var1, int var2) throws SQLException;

    T getResult(CallableStatement var1, int var2) throws SQLException;
}
  • 其中T是泛型,专之javaType,比如我们需要String的时候,那么实现类就可以写为implements TypeHandler < String>
  • setParameter方法,是使用typeHandler通过PreparedStatement对象进行设置SQL参数的时候使用的具体方法,其中i是参数在SQL的下标,parameter是参数,jdbcType是数据库类型
  • 其中3个getResult的方法,它的作用是从JDBC结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据,其中最后一个getResult方法是存储过程专用的。

四、自定义typeHandler

从系统定义的typeHandler可以知道,要实现typeHandler就需要实现接口typeHandler,或者继承BaseTypeHandler(实际上上,BaseTypeHandler实现了typeHandler接口),这里我们自定义一个typeHandler——MyTypeHander

/**
 * @Author: sean.xu
 * @Date: 2020/4/17
 */
public class MyTypeHandler implements TypeHandler<String> {
    Logger logger = Logger.getLogger(MyTypeHandler .class);

    public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
        logger.info("设置String参数:-----"+s);
        preparedStatement.setString(i,s);
    }

    public String getResult(ResultSet resultSet, String s) throws SQLException {
        String result = resultSet.getString(s);
        logger.info("读取String参数1:-----"+result);
        return result;
    }

    public String getResult(ResultSet resultSet, int i) throws SQLException {
        String result = resultSet.getString(i);
        logger.info("读取String参数2:-----"+result);
        return result;
    }

    public String getResult(CallableStatement callableStatement, int i) throws SQLException {
        String result = callableStatement.getString(i);
        logger.info("读取String参数3:-----"+result);
        return result;
    }
}

配置typeHandler

    <typeHandlers>
        <typeHandler jdbcType="VARCHAR" javaType="String" handler="com.sean.handler.MyTypeHandler "/>
    </typeHandlers>

配置完成后系统才会读取它,这样注册后,当jdbcType和javaType能与MytypeHandler对应的时候,就会启动MytypeHandler。一般而言启动这个typeHandler有两种方式。

    <resultMap id="userMapper" type="user">
        <result property="id" column="id"/>
        <result property="name" column="name" typeHandler="com.sean.handler.MyTypeHander"/>
        <result property="mobile" column="mobile" jdbcType="VARCHAR" javaType="String"/>
    </resultMap>

    <select id="findUserByName" parameterType="String" resultMap="userMapper">
        SELECT id,name FROM user WHERE name = #{name,typeHandler=com.sean.handler.MyTypeHander}
    </select>

    <select id="findUserByMobile" parameterType="String" resultMap="userMapper">
        SELECT id,name,mobile FROM user WHERE mobile = #{mobile,jdbcType=VARCHAR,javaType=string}
    </select>

一种是指定了自定义typeHandler一致的jdbcType和javaType,另一种是直接使用typeHandler指定具体的实现类。

运行代码查看日志:
在这里插入图片描述
显然我们配置的MytypeHandler已经启用了。当我们自定义的typeHandler太多时,也可以像别名一样,直接指定对应的package。

五、枚举typeHandler

在大多数情况下,typeHandler因为枚举而使用,mybatis已经定义了两个类作为枚举类型的支持:

  • EnumOrdinalTypeHandler
  • EnumTypeHandler

我们来测试下,先创建枚举类

public enum SexEnum {

   MALE(0,"男"),

   FEMALE(1,"女");

    private int id;

    private String name;

    SexEnum(int id,String name){
        this.id = id;
        this.name = name;
    }
    public int getId(){
        return id;
    }

    public String getName(){
        return name;
    }

    public static SexEnum getSex(int id){
        for (SexEnum sex : SexEnum.values()){
            if (sex.getId() == id){
                return sex;
            }
        }
        return null;
    }
}

EnumOrdinalTypeHandler是根据枚举数组下标索引的方式进行匹配的,它要求数据返回一个整数作为其下标,它会根据下标找到对应的枚举类型。
我们配置下userMapper.xml

<mapper namespace="com.sean.mapper.UserMapper">
    <resultMap id="userMapper" type="user">
        <result property="id" column="id"/>
        <result property="name" column="name" typeHandler="com.sean.handler.MyTypeHander"/>
        <result property="mobile" column="mobile" jdbcType="VARCHAR" javaType="String"/>
        <result property="gender" column="gender" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
    </resultMap>


    <select id="findUserByName" parameterType="String" resultMap="userMapper">
        SELECT * FROM user WHERE name = #{name}
    </select>
</mapper>

我们数据库的数据,0代表男,1代表女
在这里插入图片描述
执行Test代码,查询名字为"ss"

        User user = userMapper.findUserByName("ss");
        logger.debug(user.getGender().getName());

打印结果
在这里插入图片描述
EnumTypeHandler会把使用的名称转换为对应的枚举,比如它会根据数据库返回的字符串“FEMALE”进行转换。为了测试我们把数据库名字为“ss”的性别修改为“FEMALE”。
在这里插入图片描述
userMapper.xml改为

<result property="gender" column="gender" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>

执行结果:
在这里插入图片描述
和上边一样的结果。有同学可能会问了,如果我数据库gender不改为"FEMALE",还是"1"呢。
这里我贴出执行结果,大家可以思考一下
在这里插入图片描述
mybatis内部提供两种转换的typeHandler,但是有很大的局限性,就比如我们上边的报错,必须要指定数据库类型,那怎么行,所以更多时候需要我们自定义枚举的typeHandler。

六、自定义枚举typeHandler——SexEnumHandler

@MappedTypes(value = {SexEnum.class})
@MappedJdbcTypes(JdbcType.INTEGER)
public class SexEnumHandler extends BaseTypeHandler<SexEnum> {

    public void setNonNullParameter(PreparedStatement preparedStatement, int i, SexEnum e, JdbcType jdbcType) throws SQLException {
        preparedStatement.setInt(i,e.getId());
    }

    public SexEnum getNullableResult(ResultSet resultSet, String s) throws SQLException {
        int id = resultSet.getInt(s);
        return SexEnum.getSex(id);
    }

    public SexEnum getNullableResult(ResultSet resultSet, int i) throws SQLException {
        int id = resultSet.getInt(i);
        return SexEnum.getSex(id);
    }

    public SexEnum getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        int id = callableStatement.getInt(i);
        return SexEnum.getSex(id);
    }
}

typeHandler换成自定义的SexEnumHandler,xml文件就可以改成这样

<result property="gender" column="gender" typeHandler="com.sean.handler.SexEnumHandler"/>

运行程序就可以得到我们想要的结果。
好了本章的typeHandler讲解就到此结束了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis 字典转换 TypeHandler 是一种用于处理数据库中字段和 Java 实体属性之间的转换的机制。它允许开发人员在数据库中存储枚举值或其他常量,并在 MyBatis 查询时将其转换为相应的 Java 对象。 通常,在数据库设计中,我们会将某些字段的取值限制为预定义的有限选项,通常以整数或字符串形式存储在数据库中。然而,在 Java 代码中,我们更倾向于使用枚举类型或其他自定义对象来表示这些选项。 为了解决数据库字段和 Java 对象之间的转换问题,MyBatis 提供了 TypeHandler 接口。该接口定义了将字段值转换为 Java 对象和将 Java 对象转换为字段值的方法。开发人员可以根据自己的需求实现该接口并注册自己的 TypeHandler。 当 MyBatis 执行查询时,如果遇到了定义了 TypeHandler 的字段,它将使用相应的 TypeHandler 对象来处理字段的转换。类型转换可以是双向的,也就是说可以将 Java 对象转换为数据库字段,以及将数据库字段转换为 Java 对象。 字典转换的一个典型应用场景是将数据库中的整数值转换为对应的枚举类型。通过实现自定义的 TypeHandler,开发人员可以将数据库表中的整数字段映射为相应的枚举对象,从而在程序中更方便地使用枚举值。这种转换可以在查询结果映射时自动进行,也可以在参数设置时手动进行。 总之,MyBatis 字典转换 TypeHandler 是一项非常实用的功能,它允许我们在数据库和 Java 对象之间进行灵活的转换,使程序开发更加方便和高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值