MyBatis配置のtypeHandler类型转换器

初始typeHandler

JDBC中,需要在PreparedStatement对象中设置那些已经预编译过的SQL语句 参数。 执行SQL后,会通过ResultSet对象获取得到数据库的数据,而这些MyBatis是根据数据的类型通过typeHandler来实现的。

在typeHandler中,分为jdbcType和javaType,其中jdbcType用于定义数据库类型,javaType用于定义Java类型,那么typeHandler的作用就是承担jdbcType和javaType之间的相互转换。

在MyBatis中存在系统定义的typeHandler和自定义的typeHandler。MyBatis会根据javaType和数据库的jdbcType决定采用哪个typeHandler处于这些转换规则。

系统提供的typeHandler能覆盖大部分场景的要求,但是有些情况下是不够的,比如我们有特殊的转换规则,枚举类就是这样。

系统定义的typeHandler

MyBatis内部定义了许多有用的typeHandler,这里列举一些

类型处理器 Java类型 JDBC类型
IntegerTypeHandler java.lang.Integer,int 数据库兼容的NUMERIC或SHORT INTEGER
StringTypeHandler java.lang.String CHAR、VARCHAR

在MyBatis中typeHandler都要实现接口org.apache.ibatis.type.TypeHandler。

// T是泛型,专指javaType
public interface TypeHandler<T> {
    // 通过PreparedStatement 对象进行设置SQL参数 
    // i是参数在SQL的下标
    // parameter是参数
    // jdbcType是数据库类型
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

    // 从JDBC结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据
    T getResult(ResultSet var1, String var2) throws SQLException;

    T getResult(ResultSet var1, int var2) throws SQLException;
    // 存储过程专用的
    T getResult(CallableStatement var1, int var2) throws SQLException;
}

我们这里以StringTypeHandler为例,研究一下MyBatis系统的typeHandler是如何实现的。
查看StringTypeHandler的源码,可以发现它继承了BaseTypeHandler

// BaseTypeHandler本身是一个抽象类,需要子类去实现其定义的4个抽象方法,而它本身实现了typeHandler接口的4个方法
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        // 当parameter和jdbcType同时为空,MyBatis将抛出异常
        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 var7) {
                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: " + var7, var7);
            }
        } else {            
            try {
                // 设置参数
                this.setNonNullParameter(ps, i, parameter, jdbcType);
            } catch (Exception var6) {
                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: " + var6, var6);
            }
        }

    }

    public T getResult(ResultSet rs, String columnName) throws SQLException {
        Object result;
        try {
            // 返回非空结果集
            result = this.getNullableResult(rs, columnName);
        } catch (Exception var5) {
            throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + var5, var5);
        }

        return rs.wasNull() ? null : result;
    }

    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        Object result;
        try {
            result = this.getNullableResult(rs, columnIndex);
        } catch (Exception var5) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + var5, var5);
        }

        return rs.wasNull() ? null : result;
    }

    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        Object result;
        try {
            result = this.getNullableResult(cs, columnIndex);
        } catch (Exception var5) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + var5, var5);
        }

        return cs.wasNull() ? null : result;
    }

    public abstract void setNonNullParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;

    public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException;

    public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;
}

接下来再看

public class StringTypeHandler extends BaseTypeHandler<String> {
    
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }

    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getString(columnIndex);
    }

    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getString(columnIndex);
    }
}

在这里MyBatis就把javaType和jdbcType进行了相互转换。

那么它们是如何注册的?
是通过org.apache.ibatis.type.TypeHandlerRegistry类对象的register方法进行注册

public TypeHandlerRegistry() {
    this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));
    this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));
    ...
}

这样就实现了自带的typeHanlder注册。
自定义的typeHandler一般不会使用代码注册,而是通过配置或扫描

自定义typeHandler

这里我们仿造一个StringTypeHandler

/**
 * 自定义的typeHandler 实现StringTypeHandler的功能
 * @author Why
 *
 */
public class MyTypeHandler implements TypeHandler<String>{
    
    Logger logger = Logger.getLogger(MyTypeHandler.class);

    @Override
    public String getResult(ResultSet rs, String columnName) throws SQLException {
        String result = rs.getString(columnName);
        logger.info("读取string参数1【" + result +"】");
        return result;
    }

    @Override
    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
        String result = rs.getString(columnIndex);
        logger.info("读取string参数2【" + result +"】");
        return result;
    }

    @Override
    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String result = cs.getString(columnIndex);
        logger.info("读取string参数3【" + result +"】");
        return result;
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        logger.info("设置string参数【" + parameter + "】");
        ps.setString(i, parameter);
    }
}

在完成了typeHandler的自定义之后,我们还需要配置扫描

<typeHandlers>
    <typeHandler handler="com.whyalwaysmea.typeHandler.MyTypeHandler" jdbcType="VARCHAR" javaType="string"/>
</typeHandlers>

最后就是自定义typeHandler的使用了

<mapper namespace="com.whyalwaysmea.mapper.RoleMapperWithTypeHandler">  

    <resultMap type="role" id="roleMapper">
        <result property="id" column="id"/>
        <result property="roleName" column="role_name" jdbcType="VARCHAR" javaType="string"/>
        <result property="note" column="note" typeHandler="com.whyalwaysmea.typeHandler.MyTypeHandler"/>
    </resultMap>
    
    <select id="findRoles1" parameterType="string" resultType="role">
        select id, role_name, note from t_role where role_name like concat('%', #{roleName, jdbcType=VARCHAR, javaType=string}, '%')
    </select>
    
    <select id="findRoles2" parameterType="string" resultType="role">
        select id, role_name, note from t_role where role_name like concat('%', #{roleName, typeHandler=com.whyalwaysmea.typeHandler.MyTypeHandler}, '%')
    </select>
</mapper>

这里展示两种使用自定义typeHandler的方式:

  1. 指定了与自定义typeHandler一致的jdbcType和javaType
  2. 直接使用typeHandler指定具体的实现类

有时候配置的typeHandler太多,也可以使用包扫描的方式

<typeHandlers>      
    <package name="com.whyalwaysmea.typeHandler"/>
</typeHandlers>

这样还需要处理一下自定义的typeHandler类,指定jdbcType和javaType

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(String.class)
public class MyTypeHandler implements TypeHandler<String>{
    ...
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 1.2 建立TypeHandler我们知道java有java的数据类型,数据库有数据库的数据类型,那么我们...
    默默无痕阅读 11,292评论 0 9
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,429评论 0 4
  • MyBatis配置xml层次结构,而且必须注意其顺序。 MyBatis官网中文XML映射配置文件 1.proper...
    落叶飞逝的恋阅读 2,335评论 0 4
  • 我想用自己的亲身经历来解答这个问题,也算是记录一下自己最近做过让自己骄傲的事情。 五月底我把实习辞了,专心在家备考...
    利晓琳阅读 1,106评论 2 1
  • 阳光爬上了白墙 月季花成了一尊白色的雕塑 种子在泥土里发了芽 西瓜被搬上了餐桌 你变成了心里的秘密
    启恒数码阅读 134评论 0 0