MyBatis/MyBatis-Plus类型转换器深度解析:从基础原理到自定义实践

目录

前言

这篇博客主要讲一下mybatis/mybatisplus框架的类型转换器的相关知识和用法,最近项目有一个技术场景,需要将 java String 字符串类型和数据库bigint字段类型互相自动转换,借此机会总结一下相关知识点。

基本概念

  1. MyBatis-Plus 底层依托 MyBatis 实现,而 MyBatis 最终是通过 JDBC 的 PreparedStatement 操作数据库。为防范 SQL 注入风险,PreparedStatement 会先将包含参数占位符的 SQL 语句发送至数据库完成编译和优化;随后,它会调用内部的类型转换器,把传入的 Java 参数转换成数据库对应的类型并传递给数据库,生成最终可执行的完整 SQL。数据库执行 SQL 后将结果返回给 Java 客户端,框架内部再通过反射机制,借助类型转换器把返回的字段数据映射为 Java 对象的属性。
  2. 关于类型转换器在上述过程的作用,可以借助这个流程图理解。

一、TypeHandler

TypeHandler作用

TypeHandler(类型处理器)​ 是 MyBatis 实现 Java 类型与 JDBC 类型之间转换的核心组件。它充当"翻译官"的角色,负责:

  1. 参数设置:将 Java 对象转换为 JDBC 类型并设置到 PreparedStatement中
  2. 结果获取:从 ResultSet或 CallableStatement中取出数据并转换为 Java 对象

内置 TypeHandler 的自动映射

  1. MyBatis 为常见数据类型提供了丰富的内置处理器,实现了"约定优于配置":

    Java 类型 JDBC 类型 对应 TypeHandler
    String VARCHAR StringTypeHandler
    Long BIGINT LongTypeHandler
    Integer INTEGER IntegerTypeHandler
    Date TIMESTAMP DateTypeHandler
    LocalDateTime TIMESTAMP LocalDateTimeTypeHandler
    Boolean BOOLEAN BooleanTypeHandler
  2. 为什么大多数情况下,我们在使用mybatis、mybatisplus框架时不需要显式配置TypeHandler?​ 因为 MyBatis 3.4.2 后增强了类型推断能力,在参数非空时能自动选择正确的处理器。比如传入的参数是一个java字符串时,mybatis能通过反射判断这是一个String类型数据,然后会启用StringTypeHandler 这个类型转换器 工作进行数据转换

二、jdbcType 的必要性:处理 NULL 值的核心

  1. 当参数为 null时,MyBatis 无法推断其类型,必须通过 jdbcType明确告知数据库如何处理该 NULL值。这是 JDBC 规范的要求,而非 MyBatis 的"多此一举"。

  2. 因此,如果传参可能为null值时,我们可以显性配置jdbcType

    xml 复制代码
    <!-- 为可能为 NULL 的参数指定 jdbcType -->
    <insert id="insertUser">
      INSERT INTO user (name, age, email) 
      VALUES (
        #{name, jdbcType=VARCHAR}, 
        #{age, jdbcType=INTEGER},
        #{email, jdbcType=VARCHAR}
      )
    </insert>
  3. jdbcType 和 javaType 会直接影响mybatis框架选择哪个TypeHandler,如果这两个都不配置,mybatis会通过反射判断java 参数类型,进而直接选择TypeHandler,比如说,如果参数类型是字符串Sting,框架会自动选择StringTypeHandler 处理器。如果显性配置了jdbcType,那么框架会选择适配 javaType 和显性配置的jdbcType 的类型处理器TypeHandler,比如java参数类型是String,jdbcType显性配置了bigint类型,那么框架会选择 适配这两个类型的TypeHandler(这种处理器需要自己定义,内置处理器并没有这种类型的)

三、自定义 TypeHandler

  1. 当内置的类型处理器TypeHandler不满足需求时,比如 javatype 为 String 类型,而jdbcType为bigint类型时,就需要我们自己定义一个适配的TypeHandler。

  2. 定义一个TypeHandler,只需要继承 BaseTypeHandler(因为 JavaType 是 String),实现 4 个核心方法,并通过注解绑定 @MappedTypes(JavaType)和 @MappedJdbcTypes(JDBCType)

    java 复制代码
    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.*;
    /**
     * 自定义 TypeHandler:处理 JavaType=String ↔ JDBCType=BIGINT 的转换
     */
    // 绑定 Java 类型为 String
    @MappedTypes(String.class)
    // 绑定 JDBC 类型为 BIGINT(可指定多个,比如 {JdbcType.BIGINT, JdbcType.INTEGER})
    @MappedJdbcTypes(JdbcType.BIGINT)
    public class StringToBigIntTypeHandler extends BaseTypeHandler<String> {
    
        // ========== 【设置参数】Java(String) → 数据库(BIGINT) ==========
        // 用于 PreparedStatement 设置参数(INSERT/UPDATE/SELECT 传参时)
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
            // 把 String 类型的参数转为 Long,再设置到 PreparedStatement 中(适配 BIGINT 类型)
            // 注意:需处理参数为非数字的情况,避免转换异常
            if (parameter == null || !parameter.matches("\\d+")) {
                throw new IllegalArgumentException("String 参数必须是数字格式,才能转为 BIGINT,当前值:" + parameter);
            }
            Long bigIntValue = Long.parseLong(parameter);
            ps.setLong(i, bigIntValue);
        }
    
        // ========== 【获取结果】数据库(BIGINT) → Java(String) ==========
        // 1. 根据列名获取结果(ResultSet.getString(columnName) 本质也是转字符串,这里统一转 Long 再 toString 更严谨)
        @Override
        public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
            Long result = rs.getLong(columnName);
            return rs.wasNull() ? null : result.toString();
        }
    
        // 2. 根据列索引获取结果
        @Override
        public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            Long result = rs.getLong(columnIndex);
            return rs.wasNull() ? null : result.toString();
        }
    
        // 3. 从 CallableStatement(存储过程)获取结果
        @Override
        public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            Long result = cs.getLong(columnIndex);
            return cs.wasNull() ? null : result.toString();
        }
    }

四,TypeHandler的配置使用

  1. 定义完成之后,我们可以通过yaml全局配置自定义的TypeHandler,如果不想全局配置的话,也可以直接在mybatis中的sql 映射文件、或者在mybatisplus 的@Tablefield注解中单独配置使用,局部生效。

  2. 全局配置的话,只需要在yaml配置文件配置TypeHandler的包路径即可,框架在启动时会加载这个自定义类型处理器

    yaml 复制代码
    	mybatis:
    	  type-handlers-package: com.example.handler  # 扫描 TypeHandler 所在包,自动注册

4.1传参

mybatis

  1. 如果全局配置TypeHandler的情况下,我们只需在mybatis的sql 映射文件中的参数占位符中指定jdbcType即可,一般不需要指定javaType,mybatis框架会自动推断javaType.

    xml 复制代码
    	<!-- Mapper XML 写法 -->
    	<insert id="insertUser">
    	    INSERT INTO user (id, name) 
    	    VALUES (#{id, jdbcType=BIGINT}, #{name})
    	</insert>
    java 复制代码
    	// Mapper 接口
    	public interface UserMapper {
    	    int insertUser(@Param("id") String id, @Param("name") String name);
    	}
  2. 如果不想全局配置TypeHandler,可以在参数占位符中直接指定TypeHandler,类型转换器只在当前位置起作用

xml 复制代码
		<insert id="insertUser">
		    INSERT INTO user (id, name) 
		    VALUES (#{id, jdbcType=JdbcType.bigint, typeHandler="com.example.handler.StringToBigIntTypeHandler"}, #{name})
		</insert>

mybatisplus

  1. 这里重点说明的是mybatisplus的querywrapper等条件构造器,比如,wrapper.set(SomeEntity::getField, value)或 wrapper.eq(SomeEntity::getField, value)等方法时,传入的参数值会被直接用作 SQL 语句中的参数,而不会经过实体类映射配置的 typeHandler进行处理

  2. 只有insert 、update 等方法才会读取实体类配置的jdbcType 以及typeHandler等配置

  3. 和mybatis一样,只要全局配置了类型转换器,那么直接在实体类中配置jdbcType即可

    java 复制代码
    		@TableName("user") // 对应数据库表名
    		public class User {
    		    @TableField(
    		            value = "id",
    		            jdbcType = JdbcType.BIGINT // 指定 JDBC 类型,在这里可以忽略不写
    		    )
    		    private String id; // Java 类型为 String,对应数据库 BIGINT
    		    // 普通字段(无需自定义转换)
    		    @TableField("name")
    		    private String name;
    		}
  4. 如果没有全局配置,那么就需要在实体类中直接指定类型转换器了

    java 复制代码
    	@TableName("user") // 对应数据库表名
    	public class User {
    
    	    @TableField(
    	            value = "id",
    	            typeHandler = StringToBigIntTypeHandler.class, // 自定义 TypeHandler
    	    )
    	    private String id; // Java 类型为 String,对应数据库 BIGINT
    	    // 普通字段(无需自定义转换)
    	    @TableField("name")
    	    private String name;
    	}

4.2 结果映射

mybatis

  1. 一样,如果全局配置,只需要在 resultMap 标签中配置上jdbcType即可,框架会自动选择我们自定义的TypeHandler

    xml 复制代码
    <resultMap id="userResultMap" type="com.example.entity.User">
        <!-- 局部指定 TypeHandler -->
        <result property="idStr" column="id" jdbcType="BIGINT"/> 
    </resultMap>
  2. 如果没有全局配置,就需要我们在标签内指定TypeHandler了

    xml 复制代码
    <resultMap id="userResultMap" type="com.example.entity.User">
        <!-- 局部指定 TypeHandler -->
        <result property="idStr" column="id" 
                typeHandler="com.example.handler.StringToBigIntTypeHandler"/>
    </resultMap>

mybatisplus

  1. 无论我们是否全局配置了TypeHandler,只要java类型和jdbc类型不一致,我们需要开启@TableName(autoResultMap = true)

    java 复制代码
    		@TableName(value = "user", autoResultMap = true) // 对应数据库表名
    		public class User {
    		    @TableField(
    		            value = "id",
    		            jdbcType = JdbcType.BIGINT // 全局配置TypeHandler的时候,配置这个就够
    		            typeHandler = StringToBigIntTypeHandler.class  // 没有全局配置TypeHandler,在这指定
    		    )
    		    private String id; // Java 类型为 String,对应数据库 BIGINT
    		    // 普通字段(无需自定义转换)
    		    @TableField("name")
    		    private String name;
    		}
相关推荐
cyhysr2 小时前
sql将表字段不相关的内容关联到一起
数据库·sql
九皇叔叔2 小时前
MySQL 数据库 MVCC 机制
数据库·mysql
古城小栈2 小时前
Spring Boot 数据持久化:MyBatis-Plus 分库分表实战指南
spring boot·后端·mybatis
此生只爱蛋2 小时前
【Redis】Set 集合
数据库·redis·缓存
bjzhang753 小时前
C#操作SQLite数据库
数据库·sqlite·c#
无名-CODING3 小时前
SQL 注入指南
sql·mybatis
hans汉斯3 小时前
嵌入式操作系统技术发展趋势
大数据·数据库·物联网·rust·云计算·嵌入式实时数据库·汉斯出版社
Coder_Boy_3 小时前
Spring 核心思想与企业级最佳特性(实践级)事务相关
java·数据库·spring
+VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue宠物寄养系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计·宠物