乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

typeHandlers

导学

本节学习目标:

  • 理解 typeHandlers 在 Java 类型与 JDBC 类型之间的"翻译官"角色
  • 掌握 MyBatis 内置类型处理器的覆盖范围与使用场景
  • 能够根据业务需求自定义 TypeHandler,实现特殊类型的存取转换
  • 了解类型处理器的注册方式与优先级规则

定义

MyBatis 执行 SQL 时,需要将 Java 方法的参数转换为 JDBC 预编译语句(PreparedStatement)能接受的类型,同时将查询结果集(ResultSet)中的列值转换为 Java 对象属性类型。例如:

  • Java 的 String ↔ JDBC 的 VARCHAR/CHAR
  • Java 的 Integer ↔ JDBC 的 INTEGER
  • Java 的 Date ↔ JDBC 的 TIMESTAMP

当遇到 MyBatis 未内置支持的类型转换(如自定义枚举 ↔ 数据库 TINYINT,或 JSON 字符串 ↔ Java 对象),就需要通过 typeHandlers 注册自定义的类型处理器。


适用位置与核心属性

typeHandlers 位于 mybatis-config.xml 中 typeAliases 之后、objectFactory 之前。

单个处理器注册

<typeHandlers>
  <typeHandler 
    javaType="com.feixiang.enums.EmployeeStatus" 
    jdbcType="TINYINT" 
    handler="com.feixiang.handler.EmployeeStatusTypeHandler"/>
</typeHandlers>
属性是否必填说明
javaType可选该处理器处理的 Java 类型全限定名
jdbcType可选该处理器处理的 JDBC 类型(来自 JdbcType 枚举)
handler必填处理器类的全限定名,必须实现 TypeHandler 接口

包扫描注册

<typeHandlers>
  <package name="com.feixiang.handler"/>
</typeHandlers>

包扫描时,MyBatis 会自动识别类路径下所有实现了 TypeHandler 接口的类并注册。


核心原理

类型转换流程图(Java 类型 → JDBC 类型)

查询时的反向流程:

核心接口:TypeHandler<T> 定义了三个核心方法:

  • setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType):Java → JDBC
  • getResult(ResultSet rs, String columnName):JDBC → Java(按列名)
  • getResult(ResultSet rs, int columnIndex):JDBC → Java(按列索引)

完整示例

场景说明

乐途公司员工管理系统中,员工状态 EmployeeStatus 是一个枚举:

public enum EmployeeStatus {
    ACTIVE(1, "在职"),
    INACTIVE(0, "离职"),
    SUSPENDED(2, "停薪留职");

    private final int code;
    private final String desc;
    // 构造方法、getter 省略
}

数据库表 employee 中,status 字段类型为 TINYINT,存储枚举的 code 值。需要自定义 TypeHandler 实现枚举与 TINYINT 的互转。

操作前的状态

若不配置类型处理器,MyBatis 默认将枚举按名称(name())存入数据库,即 ACTIVE 字符串,而非期望的 1。

完整配置代码

步骤一:实现自定义 TypeHandler

package com.feixiang.handler;

import com.feixiang.enums.EmployeeStatus;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

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

// 注解方式声明映射关系(可选,也可在 XML 中配置)
@MappedTypes(EmployeeStatus.class)
@MappedJdbcTypes(JdbcType.TINYINT)
public class EmployeeStatusTypeHandler extends BaseTypeHandler<EmployeeStatus> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, 
            EmployeeStatus parameter, JdbcType jdbcType) throws SQLException {
        // Java 枚举 -> JDBC TINYINT
        ps.setInt(i, parameter.getCode());
        System.out.println("【TypeHandler】写入数据库: " + parameter.name() + " -> " + parameter.getCode());
    }

    @Override
    public EmployeeStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // JDBC TINYINT -> Java 枚举
        int code = rs.getInt(columnName);
        return convert(code);
    }

    @Override
    public EmployeeStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return convert(code);
    }

    @Override
    public EmployeeStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return convert(code);
    }

    private EmployeeStatus convert(int code) {
        for (EmployeeStatus status : EmployeeStatus.values()) {
            if (status.getCode() == code) {
                System.out.println("【TypeHandler】读取数据库: " + code + " -> " + status.name());
                return status;
            }
        }
        return null;
    }
}

步骤二:在 mybatis-config.xml 中注册

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

  <properties resource="db.properties"/>
  <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>
  <typeAliases>
    <package name="com.feixiang.entity"/>
  </typeAliases>

  <!-- 类型处理器配置 -->
  <typeHandlers>
    <!-- 方式一:显式注册 -->
    <typeHandler 
      javaType="com.feixiang.enums.EmployeeStatus" 
      jdbcType="TINYINT" 
      handler="com.feixiang.handler.EmployeeStatusTypeHandler"/>
    
    <!-- 方式二:包扫描(若处理器类上有 @MappedTypes/@MappedJdbcTypes 注解) -->
    <!-- <package name="com.feixiang.handler"/> -->
  </typeHandlers>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="mapper/EmployeeMapper.xml"/>
  </mappers>

</configuration>

步骤三:Mapper XML 中使用

<mapper namespace="com.feixiang.mapper.EmployeeMapper">

  <resultMap id="BaseResultMap" type="employee">
    <id column="employee_id" property="employeeId"/>
    <result column="employee_name" property="employeeName"/>
    <!-- 显式指定 typeHandler,也可依赖全局注册自动匹配 -->
    <result column="status" property="status" 
            typeHandler="com.feixiang.handler.EmployeeStatusTypeHandler"/>
  </resultMap>

  <insert id="insert">
    INSERT INTO employee (employee_name, department_code, status)
    VALUES (#{employeeName}, #{departmentCode}, 
            #{status, typeHandler=com.feixiang.handler.EmployeeStatusTypeHandler})
  </insert>

  <select id="selectById" resultMap="BaseResultMap">
    SELECT employee_id, employee_name, department_code, status
    FROM employee
    WHERE employee_id = #{id}
  </select>

</mapper>

步骤四:Java 测试代码

public class TypeHandlerDemo {
    public static void main(String[] args) throws Exception {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

        try (SqlSession session = factory.openSession()) {
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);

            Employee emp = new Employee();
            emp.setEmployeeName("李四");
            emp.setDepartmentCode("HR001");
            emp.setStatus(EmployeeStatus.ACTIVE);

            mapper.insert(emp);
            session.commit();

            Employee result = mapper.selectById(emp.getEmployeeId());
            System.out.println("查询到的状态: " + result.getStatus().getDesc());
        }
    }
}

实际效果/结果

控制台输出:

【TypeHandler】写入数据库: ACTIVE -> 1
【TypeHandler】读取数据库: 1 -> ACTIVE
查询到的状态: 在职

数据库中 status 列存储值为 1,而非字符串 ACTIVE。

分析

  • BaseTypeHandler 是 MyBatis 提供的抽象类,处理了 null 值的边界情况,自定义处理器只需关注非空逻辑
  • 在 Mapper XML 的 #{status} 中显式指定 typeHandler 可以覆盖全局注册,适合同一 Java 类型在不同场景需要不同转换策略的情况
  • @MappedTypes 和 @MappedJdbcTypes 注解配合包扫描,可以省去 XML 中的繁琐配置

易错场景/常见误区

误区错误表现正解
继承 TypeHandler 但不处理 null数据库 NULL 值导致 NullPointerException继承 BaseTypeHandler,它已封装 null 判断逻辑
注册了全局处理器但 Mapper 中又显式指定不同处理器实际使用的处理器与预期不符记住优先级:Mapper 中显式指定 > 全局注册。确保两者一致
认为 javaType 和 jdbcType 必须同时指定对简单场景过度配置若处理器类上有 @MappedTypes 注解,可省略 javaType;MyBatis 也常能自动推断 jdbcType
枚举默认行为与预期不符枚举存入的是 ACTIVE 字符串而非 1枚举默认使用 EnumTypeHandler(存名称),如需存数值必须自定义 TypeHandler 或使用 EnumOrdinalTypeHandler(存 ordinal,但不稳定)
包扫描后处理器未生效处理器类缺少无参构造方法TypeHandler 实现类必须提供公共无参构造方法,否则 MyBatis 无法实例化

面试考点

Q1:MyBatis 有哪些常用的内置类型处理器?

A:MyBatis 内置了覆盖绝大多数基础类型的处理器,例如:

  • StringTypeHandler:String ↔ VARCHAR/CHAR
  • IntegerTypeHandler:Integer ↔ INTEGER
  • LongTypeHandler:Long ↔ BIGINT
  • DateTypeHandler:Date ↔ TIMESTAMP
  • BooleanTypeHandler:Boolean ↔ BOOLEAN(部分数据库映射为 BIT)
  • BlobTypeHandler:byte[] ↔ BLOB 完整列表可参考 org.apache.ibatis.type 包下的类。

Q2:自定义 TypeHandler 时,为什么要继承 BaseTypeHandler 而不是直接实现 TypeHandler?

A:BaseTypeHandler 是抽象类,它实现了 TypeHandler 接口,并在三个 getResult 方法中统一处理了 null 值判断(通过 wasNull())。子类只需实现 setNonNullParameter 和 getNullableResult,代码更简洁且不易遗漏 null 边界情况。

Q3:如果同一个 Java 类型注册了多个 TypeHandler,MyBatis 如何选择?

A:选择优先级为:① Mapper 语句中显式指定的 typeHandler;② 全局注册时同时指定了 javaType 和 jdbcType 的精确匹配;③ 仅指定 javaType 的匹配;④ 包扫描自动注册的匹配。若存在多个同优先级处理器,后注册的会覆盖先注册的。


小结

typeHandlers 是 MyBatis 类型系统的核心桥梁,它让 Java 世界与 JDBC 世界能够无缝对话。内置处理器已覆盖绝大多数基础场景,但在枚举映射、JSON 存储、加密字段等特殊需求下,自定义 TypeHandler 是优雅且可复用的解决方案。记住继承 BaseTypeHandler、处理好 null、合理选择注册方式,是编写健壮处理器的关键。


下一章引子

类型转换问题解决后,MyBatis 还需要将查询结果转换为 Java 对象。这个"对象创建"的工作由 ObjectFactory 负责。虽然大多数情况下使用默认实现即可,但了解它的扩展机制有助于理解 MyBatis 的对象实例化原理,也为某些特殊场景(如对象池、代理创建)提供可能。

上一页
typeAliases
下一页
objectFactory