MyBatis五typeHandlers

typeHandlers又叫类型处理器,就像在JDBC中,我们在PreparedStatement中设置预编译sql所需的参数或执行sql后根据结果集ResultSet对象获取得到的数据时,需要将数据库中的类型和java中字段的类型进行转换一样,在MyBatis中使用typeHandler来实现。所以说白了,typeHandlers就是用来完成javaType和jdbcType之间的转换。举个比较简单的例子,我创建一个博客表,表中的创建时间和修改时间用VARCHAR类型,但是在我的POJO对象中,创建时间和修改时间的类型是Date,这样我在向数据库插入数据时,需要将日期类型转化成VARCHAR,而从数据库中查询出的结果中,又需要将VARCHAR类型转换成Date.在MyBatis中,使用typeHandlers配置来实现这个转换过程。和别名一样,MyBatis中的类型处理器也存在系统定义的和自定义两种,MyBatis会根据javaType和jdbcType来决定采用哪个typeHandler来处理这些转换规则,而且系统定义的能满足大部分需求,可以说是非常好用,用户只需要自定义一些特有的转换规则,如枚举类型。下面分别介绍这两种typeHandler。

一、系统定义的typeHandler

同上一节的typeAliases,typeHandler也能通过Configuration对象获取到,如下:

/**
     * 获取类型处理器
     */
    public static void getTypeHandlers() {
        SqlSession sqlSession = getSqlSession();
        TypeHandlerRegistry typeHandlerRegistry = sqlSession.getConfiguration().getTypeHandlerRegistry();
        Collection<TypeHandler<?>> handlers =  typeHandlerRegistry.getTypeHandlers();
        System.out.println(handlers.size());
        for (TypeHandler<?> typeHandler : handlers) {
            System.out.println(typeHandler.getClass().getName());
        }
    }

执行结果如下:

39
org.apache.ibatis.type.SqlDateTypeHandler
org.apache.ibatis.type.ClobReaderTypeHandler
org.apache.ibatis.type.LocalTimeTypeHandler
org.apache.ibatis.type.YearMonthTypeHandler
org.apache.ibatis.type.NStringTypeHandler
org.apache.ibatis.type.LocalDateTypeHandler
org.apache.ibatis.type.BigIntegerTypeHandler
org.apache.ibatis.type.OffsetDateTimeTypeHandler
org.apache.ibatis.type.ByteObjectArrayTypeHandler
org.apache.ibatis.type.ArrayTypeHandler
org.apache.ibatis.type.BigDecimalTypeHandler
org.apache.ibatis.type.UnknownTypeHandler
org.apache.ibatis.type.OffsetTimeTypeHandler
org.apache.ibatis.type.ByteArrayTypeHandler
org.apache.ibatis.type.DateOnlyTypeHandler
org.apache.ibatis.type.JapaneseDateTypeHandler
org.apache.ibatis.type.TimeOnlyTypeHandler
org.apache.ibatis.type.NClobTypeHandler
org.apache.ibatis.type.BooleanTypeHandler
org.apache.ibatis.type.BlobTypeHandler
org.apache.ibatis.type.YearTypeHandler
org.apache.ibatis.type.BlobByteObjectArrayTypeHandler
org.apache.ibatis.type.MonthTypeHandler
org.apache.ibatis.type.FloatTypeHandler
org.apache.ibatis.type.DateTypeHandler
org.apache.ibatis.type.ClobTypeHandler
org.apache.ibatis.type.BlobInputStreamTypeHandler
org.apache.ibatis.type.ByteTypeHandler
org.apache.ibatis.type.SqlTimestampTypeHandler
org.apache.ibatis.type.ZonedDateTimeTypeHandler
org.apache.ibatis.type.IntegerTypeHandler
org.apache.ibatis.type.LocalDateTimeTypeHandler
org.apache.ibatis.type.CharacterTypeHandler
org.apache.ibatis.type.SqlTimeTypeHandler36 org.apache.ibatis.type.DoubleTypeHandler
org.apache.ibatis.type.ShortTypeHandler
org.apache.ibatis.type.LongTypeHandler
org.apache.ibatis.type.InstantTypeHandler
org.apache.ibatis.type.StringTypeHandler

从结果来看,系统总过定义了39个类型处理器。

现在选择其中的StringTypeHandler来进行分析,看看其源代码

1、获取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,即javaType,几个方法如下:

  • setNonNullParameter:这个方法是用来将javaType转换成jdbcTpe

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

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

  • getNullableResult:这个方法用在存储过程中

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

2、BaseTypeHandler类源码
/**
 *    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接口

3、接口TypeHandler源代码
/**
 *    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接口的方式创建一个MyDateHandler,用来完成javaType中的Date类型与jdbcType中的varchar类型之间的转化。

创建MyDateHandler

package com.daily.handler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.ibatis.type.DateTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.postgresql.jdbc2.optional.SimpleDataSource;

/**
 * 自定义一个TypeHandler用来将javatype的日期类型和jdbctype的VARCHAR进行转换
 */
public class MyDateHandler implements TypeHandler<Date> {
    @Override
    // 设置sql中指定索引的参数,即将javaType转化为jdbcType
    public void setParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
        //设置数据存储到数据库中的格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        ps.setString(i, sdf.format(parameter));
    }

    @Override
    // 根据列名称从结果集获取值,并将jdbcType转换成javaType
    public Date getResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);
        if (null != columnValue) {
            return new Date(Long.valueOf(columnValue));
        }
        return null;
    }

    @Override
    // 根据列名称从结果集获取值,并将jdbcType转换成javaType
    public Date getResult(ResultSet rs, int columnIndex) throws SQLException {
        String columnValue = rs.getString(columnIndex);
        if (null != columnValue) {
            return new Date(Long.valueOf(columnValue));
        }
        return null;
    }

    @Override
    public Date getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String columnValue = cs.getString(columnIndex);
        if (null != columnValue) {
            return new Date(Long.valueOf(columnValue));
        }
        return null;
    }

}

下面用一个例子来说明这个handler的使用。

1⃣️创建一个blog表,表中的时间用varchar类型

image

2⃣️创建POJO对象,将create_time和modify_time的类型定义为Date
package com.daily.pojo;

import java.util.Date;

public class Blog {
    private Integer blogId;

    private String blogTitle;

    private String blogContent;

    private Date createTime;

    private Date modifyTime;

    public Integer getBlogId() {
        return blogId;
    }

    public void setBlogId(Integer blogId) {
        this.blogId = blogId;
    }

    public String getBlogTitle() {
        return blogTitle;
    }

    public void setBlogTitle(String blogTitle) {
        this.blogTitle = blogTitle == null ? null : blogTitle.trim();
    }

    public String getBlogContent() {
        return blogContent;
    }

    public void setBlogContent(String blogContent) {
        this.blogContent = blogContent == null ? null : blogContent.trim();
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime == null ? null : createTime;
    }

    public Date getModifyTime() {
        return modifyTime;
    }

    public void setModifyTime(Date modifyTime) {
        this.modifyTime = modifyTime == null ? null : modifyTime;
    }
}
3⃣️在mybatis-config.xml文件中注册刚才自定义的MyDateHandler
<!--类型处理器 -->
     <typeHandlers>
         <!-- 注册自定义handler,说明它作用的jdbcType和javaType -->
         <typeHandler jdbcType="VARCHAR" javaType="date" handler="com.daily.handler.MyDateHandler" />
     </typeHandlers>
4⃣️在BlogMapper.xml文件中使用
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.daily.dao.BlogMapper">
    <resultMap id="BaseResultMap" type="com.daily.pojo.Blog">
        <id column="blog_id" jdbcType="INTEGER" property="blogId" />
        <result column="blog_title" jdbcType="VARCHAR" property="blogTitle" />
        <result column="blog_content" jdbcType="VARCHAR" property="blogContent" />
        <!-- 不使用MyDateHandler-->
        <result column="create_time" jdbcType="VARCHAR" property="createTime" />
        <!-- 使用MyDateHandler-->
        <result column="modify_time" typeHandler="com.daily.handler.MyDateHandler" property="modifyTime" />
    </resultMap>

    <insert id="insert" parameterType="com.daily.pojo.Blog">
        insert into blog (blog_id, blog_title, blog_content,
        create_time, modify_time)
        values (#{blogId,jdbcType=INTEGER}, #{blogTitle,jdbcType=VARCHAR},
        #{blogContent,jdbcType=VARCHAR},
        #{createTime,jdbcType=VARCHAR}, #{modifyTime,typeHandler=com.daily.handler.MyDateHandler})
    </insert>
</mapper>
5⃣️查看结果

第一种:create_time不设置typeHandler,modify_time设置typeHandler
image

从上面的结果来看,虽然create_time没有设置typeHandler,但是结果跟使用了typeHandler的modify_time是一样的,那么我把两个都去掉呢?

第二种:两个都不设置
image

可以看到结果也是一样,下面我去掉注册的MyDateHandler呢?

第三种:去掉MyDateHandler
image

从上述结果可以看到,当去掉自定义的处理器时,MyBatis会根据结果自动选择合适的handler进行转换。

日常开发中,一般不需要定义,使用默认的就可以,除非是像枚举这种特殊类型就需要自己实现。

posted @ 2022-09-29 14:11  leepandar  阅读(25)  评论(0编辑  收藏  举报