• MyBatis学习笔记(四) 类型处理器(typeHandlers)


    一、类型处理器(typeHandlers)简介

    typeHandlers又名类型管理器,类似于JDBC里面将数据库类型转换成JAVA类型的功能一样,typeHandlers就是MyBatis的类型转换器。和别名一样,MyBatis中的类型处理器也存在系统定义的和自定义两种,MyBatis会根据javaType和jdbcType来决定采用哪个typeHandler来处理这些转换规则,而且系统定义的能满足大部分需求,可以说是非常好用,用户只需要自定义一些特有的转换规则,如枚举类型。无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

    在myBatis中注册类型管理器非常简单,只需要在配置文件中配置下面的的标签:

    <!-- Configuration中有两种方式注册类型管理器 -->
    <typeHandlers>
      <!-- 直接注册类型管理器 -->
      <typeHandler handler="org.mybatis.example.ExampleTypeHandler" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
      <!-- 定义一个扫描包,该包下面的所有类全部注册 -->
      <package name="org.mybatis.example"/>
    </typeHandlers>

    具体MyBatis是如何将配置文件解析注册的可以参考我的上一篇文章:MyBatis学习笔记(三) Configuration类

    二、系统自定义的typeHandlers

    MyBatis在初始化配置的时候,默认注册了丰富的类型处理器,基本上这些处理器就能满足我们大部分需求,下面我们来看看都有那些:

    以下是在TypeHandlerRegistry类里面注册的

        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());
    
        register(Byte.class, new ByteTypeHandler());
        register(byte.class, new ByteTypeHandler());
        register(JdbcType.TINYINT, new ByteTypeHandler());
    
        register(Short.class, new ShortTypeHandler());
        register(short.class, new ShortTypeHandler());
        register(JdbcType.SMALLINT, new ShortTypeHandler());
    
        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());
    
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());
    
        register(Float.class, new FloatTypeHandler());
        register(float.class, new FloatTypeHandler());
        register(JdbcType.FLOAT, new FloatTypeHandler());
    
        register(Double.class, new DoubleTypeHandler());
        register(double.class, new DoubleTypeHandler());
        register(JdbcType.DOUBLE, new DoubleTypeHandler());
    
        register(Reader.class, new ClobReaderTypeHandler());
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.CLOB, new ClobTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
        register(JdbcType.CHAR, new StringTypeHandler());
        register(JdbcType.VARCHAR, new StringTypeHandler());
        register(JdbcType.CLOB, new ClobTypeHandler());
        register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(JdbcType.NVARCHAR, new NStringTypeHandler());
        register(JdbcType.NCHAR, new NStringTypeHandler());
        register(JdbcType.NCLOB, new NClobTypeHandler());
    
        register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
        register(JdbcType.ARRAY, new ArrayTypeHandler());
    
        register(BigInteger.class, new BigIntegerTypeHandler());
        register(JdbcType.BIGINT, new LongTypeHandler());
    
        register(BigDecimal.class, new BigDecimalTypeHandler());
        register(JdbcType.REAL, new BigDecimalTypeHandler());
        register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
        register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
    
        register(InputStream.class, new BlobInputStreamTypeHandler());
        register(Byte[].class, new ByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
        register(byte[].class, new ByteArrayTypeHandler());
        register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
        register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.BLOB, new BlobTypeHandler());
    
        register(Object.class, UNKNOWN_TYPE_HANDLER);
        register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
        register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    
        register(Date.class, new DateTypeHandler());
        register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
        register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
        register(JdbcType.TIMESTAMP, new DateTypeHandler());
        register(JdbcType.DATE, new DateOnlyTypeHandler());
        register(JdbcType.TIME, new TimeOnlyTypeHandler());
    
        register(java.sql.Date.class, new SqlDateTypeHandler());
        register(java.sql.Time.class, new SqlTimeTypeHandler());
        register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
    
        // mybatis-typehandlers-jsr310
        try {
          register("java.time.Instant", "org.apache.ibatis.type.InstantTypeHandler");
          register("java.time.LocalDateTime", "org.apache.ibatis.type.LocalDateTimeTypeHandler");
          register("java.time.LocalDate", "org.apache.ibatis.type.LocalDateTypeHandler");
          register("java.time.LocalTime", "org.apache.ibatis.type.LocalTimeTypeHandler");
          register("java.time.OffsetDateTime", "org.apache.ibatis.type.OffsetDateTimeTypeHandler");
          register("java.time.OffsetTime", "org.apache.ibatis.type.OffsetTimeTypeHandler");
          register("java.time.ZonedDateTime", "org.apache.ibatis.type.ZonedDateTimeTypeHandler");
        } catch (ClassNotFoundException e) {
          // no JSR-310 handlers
        }
    
        // issue #273
        register(Character.class, new CharacterTypeHandler());
        register(char.class, new CharacterTypeHandler());

    其中当属StringTypeHandler用的最频繁,下面我们看下它的源码

    /**
     *    Copyright 2009-2015 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.type;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @author Clinton Begin
     */
    public class StringTypeHandler extends BaseTypeHandler<String> {
    
      @Override
      public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
          throws SQLException {
        ps.setString(i, parameter);
      }
    
      @Override
      public String getNullableResult(ResultSet rs, String columnName)
          throws SQLException {
        return rs.getString(columnName);
      }
    
      @Override
      public String getNullableResult(ResultSet rs, int columnIndex)
          throws SQLException {
        return rs.getString(columnIndex);
      }
    
      @Override
      public String getNullableResult(CallableStatement cs, int columnIndex)
          throws SQLException {
        return cs.getString(columnIndex);
      }
    }

    从上述代码可以看出它继承了一个叫做BaseTypeHandler<String>的类,这个类的范型是String,即javaType,几个方法如下:

    1⃣️setNonNullParameter:这个方法是用来将javaType转换成jdbcTpe

    2⃣️getNullableResult:这个方法用来将从结果集根据列名称获取到的数据的jdbcType转换成javaType

    3⃣️getNullableResult:这个方法用来将从结果集根据列索引获取到的数据的jdbcType转换成javaType

    4⃣️getNullableResult:这个方法用在存储过程中

    其实主要就是完成不同类型之间的转换,下面来看一下它所继承的BaseTypeHandler<String>类

    再来看看BaseTypeHandler<String>类的源码

    /**
     *    Copyright 2009-2015 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.type;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.apache.ibatis.executor.result.ResultMapException;
    import org.apache.ibatis.session.Configuration;
    
    /**
     * @author Clinton Begin
     * @author Simone Tripodi
     */
    public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    
      protected Configuration configuration;
    
      public void setConfiguration(Configuration c) {
        this.configuration = c;
      }
    
      @Override
      public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
          if (jdbcType == null) {
            throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
          }
          try {
            ps.setNull(i, jdbcType.TYPE_CODE);
          } catch (SQLException e) {
            throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
                    "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
                    "Cause: " + e, e);
          }
        } else {
          try {
            setNonNullParameter(ps, i, parameter, jdbcType);
          } catch (Exception e) {
            throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
                    "Try setting a different JdbcType for this parameter or a different configuration property. " +
                    "Cause: " + e, e);
          }
        }
      }
    
      @Override
      public T getResult(ResultSet rs, String columnName) throws SQLException {
        T result;
        try {
          result = getNullableResult(rs, columnName);
        } catch (Exception e) {
          throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
        }
        if (rs.wasNull()) {
          return null;
        } else {
          return result;
        }
      }
    
      @Override
      public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        T result;
        try {
          result = getNullableResult(rs, columnIndex);
        } catch (Exception e) {
          throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from result set.  Cause: " + e, e);
        }
        if (rs.wasNull()) {
          return null;
        } else {
          return result;
        }
      }
    
      @Override
      public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        T result;
        try {
          result = getNullableResult(cs, columnIndex);
        } catch (Exception e) {
          throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from callable statement.  Cause: " + e, e);
        }
        if (cs.wasNull()) {
          return null;
        } else {
          return result;
        }
      }
    
      public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    
      public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
    
      public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
    
      public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
    
    }

    此类是一个抽象类,重写了四个接口之外,还有四个抽象方法,也就是在StringTypeHandler中实现的接口,那么重写的四个接口是什么呢?我们去看一下它所实现的TypeHandler<T>接口

    最后看一下TypeHandler<T>接口的源码

    /**
     *    Copyright 2009-2015 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.type;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @author Clinton Begin
     */
    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;
    
    }

    这里也定义了四个接口,一个set方法用来将javaType转为jdbcType,三个get方法用来将jdbcType转为javaType,而在BaseTypeHandler中实现的就是这四个方法。

    所以当我们自定义自己的typeHandler时有两种方法:

    第一种:继承BaseTypeHandler类

    第二种:实现TypeHandler接口

    三、自定义typeHandler

    自定义typeHandler用的最多的应该是枚举类型了

    若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用。

    比如说我们想存储取近似值时用到的舍入模式。默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。

    注意 EnumTypeHandler 在某种意义上来说是比较特别的,其他的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。

    不过,我们可能不想存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样轻而易举: 在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形数值。

    四、使用typeHandler

    MyBatis定义注册的typeHandler不需要在mapper文件中显示的声明,MyBatis会自动的使用,我们自己定义的需要在结果集中显示的指出,告诉MyBatis,“这两个类型按照我定义的类转换”,使用也非常简单,下面是官方给出的实例。

    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
    
        <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="funkyNumber" property="funkyNumber"/>
            <result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
        </resultMap>

    <select id="getUser2" resultMap="usermap2"> select * from users2 </select>

    <insert id="insert2"> insert into users2 (id, name, funkyNumber, roundingMode) values ( #{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler} ) </insert> </mapper>
  • 相关阅读:
    【Spring】9、Spring中的事件Event
    【Spring】8、Spring框架中的单例Beans是线程安全的么
    【Spring】7、拦截器HandlerInterceptor
    【Spring】5、利用自定义注解在SpringMVC中实现自定义权限检查
    【Spring】4、Spring中 @Autowired标签与 @Resource标签 的区别
    【Dubbo&&Zookeeper】2、 windows平台dubbo-admin管理平台搭建
    【Dubbo&&Zookeeper】4、 Java实现Dubbo服务提供者及消费者注册
    【Dubbo&&Zookeeper】3、Failed to read schema document 'http://code.alibabatech.com/schema/dubbo/dubbo.xsd'问题解决方法
    【Dubbo&&Zookeeper】1、Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错)
    【Spring】3、BeanFactory 和 ApplicationContext的区别
  • 原文地址:https://www.cnblogs.com/hpuiotcl/p/11111746.html
Copyright © 2020-2023  润新知