MyBatis 3自定义类型处理器完全指南:解决90%的特殊数据转换问题

【免费下载链接】mybatis-3 MyBatis SQL mapper framework for Java 【免费下载链接】mybatis-3 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-3

你是否还在为MyBatis中日期、JSON、枚举等特殊数据类型的转换而头疼?是否遇到过数据库字段与Java对象属性类型不匹配导致的异常?本文将带你全面掌握自定义类型处理器(TypeHandler)的实现方法,通过简单三步即可解决90%的特殊数据转换场景,让你的数据交互更流畅。

读完本文你将学会:

  • 理解TypeHandler的工作原理及核心接口
  • 掌握自定义类型处理器的完整实现步骤
  • 学会在XML和注解中配置并使用自定义处理器
  • 解决LocalDateTime、JSON等复杂类型的映射问题
  • 了解高级特性如类型别名和null值处理技巧

什么是TypeHandler

TypeHandler(类型处理器)是MyBatis中负责Java类型与JDBC类型之间转换的组件。当MyBatis执行SQL语句时,会使用TypeHandler将Java对象参数转换为JDBC兼容的类型;当查询结果返回时,又会使用TypeHandler将JDBC类型转换为Java对象。

MyBatis已经内置了大量常用类型的处理器,如IntegerTypeHandlerStringTypeHandler等,这些处理器都继承自BaseTypeHandler抽象类。你可以在src/main/java/org/apache/ibatis/type/BaseTypeHandler.java查看基础实现。

public abstract class BaseTypeHandler<T> implements TypeHandler<T> {
  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      // null值处理逻辑
    } else {
      setNonNullParameter(ps, i, parameter, jdbcType);
    }
  }
  
  // 抽象方法需要子类实现
  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
  // 其他抽象方法...
}

类型处理器工作流程

以下是TypeHandler在MyBatis中的工作流程:

mermaid

自定义TypeHandler的步骤

实现自定义TypeHandler只需三步:

1. 继承BaseTypeHandler

创建一个类继承BaseTypeHandler<T>,其中T是你要处理的Java类型。需要实现以下四个抽象方法:

  • setNonNullParameter(): 设置非空参数时调用
  • getNullableResult(ResultSet, String): 通过列名从结果集获取值
  • getNullableResult(ResultSet, int): 通过列索引从结果集获取值
  • getNullableResult(CallableStatement, int): 从存储过程结果获取值

2. 实现转换逻辑

以处理Java 8中的LocalDateTime类型为例,MyBatis已提供src/main/java/org/apache/ibatis/type/LocalDateTimeTypeHandler.java实现:

public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setObject(i, parameter); // 将LocalDateTime转换为JDBC支持的类型
  }

  @Override
  public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getObject(columnName, LocalDateTime.class); // 将JDBC类型转换为LocalDateTime
  }

  @Override
  public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getObject(columnIndex, LocalDateTime.class);
  }

  @Override
  public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getObject(columnIndex, LocalDateTime.class);
  }
}

3. 配置TypeHandler

有两种方式可以配置自定义TypeHandler:

XML配置方式

在MyBatis配置文件中添加typeHandlers配置:

<typeHandlers>
  <!-- 配置自定义类型处理器 -->
  <typeHandler handler="com.example.typehandler.JsonTypeHandler"/>
  
  <!-- 包扫描方式配置 -->
  <package name="com.example.typehandler"/>
</typeHandlers>
注解配置方式

使用@MappedTypes@MappedJdbcTypes注解指定处理器支持的Java类型和JDBC类型:

@MappedTypes({LocalDateTime.class})
@MappedJdbcTypes({JdbcType.TIMESTAMP})
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
  // 实现代码...
}

实战案例:处理JSON类型

假设我们需要将Java对象与数据库JSON类型字段进行映射,以下是实现步骤:

1. 创建JSON类型处理器

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private final Class<T> type;

    public JsonTypeHandler(Class<T> type) {
        this.type = type;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        try {
            ps.setString(i, objectMapper.writeValueAsString(parameter));
        } catch (JsonProcessingException e) {
            throw new SQLException("Error converting object to JSON", e);
        }
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String json = rs.getString(columnName);
        return json == null ? null : parseJson(json);
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String json = rs.getString(columnIndex);
        return json == null ? null : parseJson(json);
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String json = cs.getString(columnIndex);
        return json == null ? null : parseJson(json);
    }

    private T parseJson(String json) {
        try {
            return objectMapper.readValue(json, type);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Error parsing JSON", e);
        }
    }
}

2. 配置JSON类型处理器

在MyBatis配置文件中注册:

<typeHandlers>
  <typeHandler handler="com.example.typehandler.JsonTypeHandler" javaType="com.example.model.UserInfo"/>
</typeHandlers>

或者使用注解方式:

@MappedTypes({UserInfo.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
  // 实现代码...
}

3. 在映射文件中使用

<resultMap id="userResultMap" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <!-- 使用typeHandler属性指定处理器 -->
  <result property="info" column="info" typeHandler="com.example.typehandler.JsonTypeHandler"/>
</resultMap>

<select id="getUser" resultMap="userResultMap">
  SELECT id, name, info FROM users WHERE id = #{id}
</select>

<insert id="insertUser">
  INSERT INTO users (id, name, info) 
  VALUES (#{id}, #{name}, #{info,typeHandler=com.example.typehandler.JsonTypeHandler})
</insert>

高级特性

类型别名

为了简化配置,你可以为TypeHandler创建类型别名。MyBatis的TypeAliasRegistry类(src/main/java/org/apache/ibatis/type/TypeAliasRegistry.java)负责管理类型别名。

在配置文件中注册别名:

<typeAliases>
  <typeAlias type="com.example.typehandler.JsonTypeHandler" alias="json"/>
</typeAliases>

<!-- 使用别名 -->
<result property="info" column="info" typeHandler="json"/>

MyBatis已为常用类型预定义了别名,如:

public TypeAliasRegistry() {
  registerAlias("string", String.class);
  registerAlias("int", Integer.class);
  registerAlias("long", Long.class);
  registerAlias("date", Date.class);
  // 更多预定义别名...
}

null值处理

在处理null值时,需要注意BaseTypeHandlersetParameter方法已经处理了null值判断:

@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.");
    }
    ps.setNull(i, jdbcType.TYPE_CODE);
  } else {
    setNonNullParameter(ps, i, parameter, jdbcType);
  }
}

当处理可能为null的参数时,建议在SQL中显式指定JdbcType:

INSERT INTO users (id, name, email) 
VALUES (#{id}, #{name}, #{email,jdbcType=VARCHAR})

处理枚举类型

MyBatis提供了两个枚举处理器:

  • EnumTypeHandler: 使用枚举名称映射(默认)
  • EnumOrdinalTypeHandler: 使用枚举索引映射

你可以在配置文件中全局配置默认枚举处理器:

<settings>
  <setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</settings>

或者在字段级别指定:

<result property="status" column="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>

最佳实践

  1. 保持单一职责:每个TypeHandler只处理一种类型转换
  2. 处理null值:确保你的处理器能正确处理null值情况
  3. 线程安全:TypeHandler是单例的,确保其无状态或线程安全
  4. 异常处理:适当捕获并转换异常为MyBatis的TypeException
  5. 测试覆盖:为TypeHandler编写单元测试,覆盖各种边界情况

总结

自定义TypeHandler是解决MyBatis中特殊类型映射的强大工具。通过继承BaseTypeHandler并实现四个核心方法,你可以轻松处理任何复杂的数据类型转换需求。无论是Java 8日期时间类型、JSON对象,还是自定义业务类型,TypeHandler都能提供优雅的解决方案。

官方文档中关于类型处理器的更多配置细节,请参考src/site/markdown/configuration.md

掌握TypeHandler将极大提升你使用MyBatis处理复杂数据类型的能力,让数据访问层代码更加简洁和可维护。

【免费下载链接】mybatis-3 MyBatis SQL mapper framework for Java 【免费下载链接】mybatis-3 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-3

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐