MyBatis TypeHandler详解:原理与自定义实践

时间:2024-11-07 09:37:29
❃博主首页 : 「码到三十五」 ,同名公众号 :「码到三十五」,wx号 : 「liwu0213」
☠博主专栏 :
♝博主的话 : 搬的每块砖,皆为峰峦之基;关注这个爱发技术干货的coder,一起筑基

在MyBatis中,TypeHandler扮演着一个至关重要的角色,它负责Java类型和JDBC类型之间的映射和转换。本文将详细介绍MyBatis TypeHandler的概念、工作原理,以及如何在Spring Boot环境中自定义TypeHandler,并通过案例来展示其应用场景。

目录

    • 一、TypeHandler简介
    • 二、TypeHandler的工作原理
      • 1. 设置参数(Parameter Setting)
      • 2. 获取结果(Result Getting)
      • 3. 类型映射和转换规则
      • 4. 自定义TypeHandler的扩展性
    • 三、内置TypeHandler介绍
    • 四、自定义TypeHandler实践
      • 1. 创建自定义TypeHandler类
        • ``
        • ``
      • 2. 注册自定义TypeHandler
      • 3. 在Mapper中使用自定义TypeHandler
      • 自定义时间转换案例
    • 五、应用场景
    • 六、结语

一、TypeHandler简介

TypeHandler是MyBatis中用于处理Java类型与JDBC类型之间转换的接口。在SQL语句执行过程中,无论是设置参数还是获取结果集,都需要通过TypeHandler进行类型转换。MyBatis提供了丰富的内置TypeHandler实现,以支持常见的数据类型转换。同时,也可以根据需要自定义TypeHandler来处理特殊的数据类型或转换逻辑。

二、TypeHandler的工作原理

TypeHandler在MyBatis中是一个核心概念,其工作原理主要涉及Java类型和JDBC类型之间的转换。下面将详细介绍TypeHandler的工作原理。

1. 设置参数(Parameter Setting)

当MyBatis执行一个预编译的SQL语句(如INSERT、UPDATE等)时,它需要将Java对象中的属性值设置到SQL语句中对应的占位符上。这个过程就是通过TypeHandler来实现的。

具体步骤如下:

  • MyBatis会根据映射配置找到对应的TypeHandler实例。这个映射配置可以在MyBatis的配置文件或者Mapper的XML文件中定义。
  • TypeHandler实例会接收到Java对象中的属性值,并将其转换为JDBC能够识别的类型。这个转换过程是根据Java类型和JDBC类型之间的映射关系来实现的。
  • 转换后的值会被设置到PreparedStatement对象中对应的占位符上,以便数据库能够正确解析和执行SQL语句。

2. 获取结果(Result Getting)

当数据库执行查询操作并返回结果集时,MyBatis需要将结果集中的数据提取出来,并转换为Java对象中的对应属性类型。这个过程同样是通过TypeHandler来实现的。

具体步骤如下:

  • MyBatis会根据映射配置找到对应的TypeHandler实例。
  • TypeHandler实例会从ResultSet对象中提取数据,这个提取过程是根据数据库字段和Java属性之间的映射关系来实现的。
  • 提取出的数据会被转换为Java对象中的对应属性类型。这个转换过程同样是根据Java类型和JDBC类型之间的映射关系来实现的。
  • 转换后的值会被设置到Java对象中对应的属性上,以便应用程序能够正确处理和使用这些数据。

3. 类型映射和转换规则

TypeHandler的核心功能是实现Java类型和JDBC类型之间的映射和转换。这个映射和转换规则是根据Java类型和JDBC类型的特性和语义来定义的。

  • 对于基本数据类型(如int、long、float等),MyBatis提供了内置的TypeHandler实现,这些实现能够直接将Java基本数据类型转换为对应的JDBC基本数据类型,反之亦然。
  • 对于复杂数据类型(如自定义对象、集合等),MyBatis允许开发者自定义TypeHandler来实现复杂的类型转换逻辑。例如,开发者可以定义一个自定义的TypeHandler来将数据库中的JSON字符串转换为Java中的对象,或者将Java对象转换为JSON字符串存储到数据库中。

4. 自定义TypeHandler的扩展性

MyBatis的TypeHandler机制具有很高的扩展性。开发者可以通过实现TypeHandler接口或继承BaseTypeHandler类来创建自定义的TypeHandler实现。自定义的TypeHandler可以实现任意复杂的类型转换逻辑,以满足特定业务需求。

此外,MyBatis还提供了丰富的API和扩展点来支持开发者自定义TypeHandler的注册和使用方式。开发者可以通过配置文件、注解或编程方式将自定义的TypeHandler注册到MyBatis中,并在Mapper的XML映射文件中引用它们来处理特定的数据类型转换需求。

三、内置TypeHandler介绍

MyBatis为了简化开发者的工作,提供了一系列内置的TypeHandler,这些内置的TypeHandler能够处理大部分常见的数据类型转换。以下是一些MyBatis中常见的内置TypeHandler:

  1. BooleanTypeHandler
    处理Java的Boolean类型与数据库中的BOOLEANBIT等类型的映射关系。

  2. IntegerTypeHandlerLongTypeHandlerShortTypeHandler 等:
    这些TypeHandler分别处理Java中的IntegerLongShort等整数类型与数据库中的相应整数类型的映射,如INTBIGINTSMALLINT等。

  3. FloatTypeHandlerDoubleTypeHandlerBigDecimalTypeHandler
    处理Java中的浮点数和定点数类型,如FloatDoubleBigDecimal,与数据库中的FLOATDOUBLEDECIMAL等类型的映射。

  4. StringTypeHandler
    处理Java的String类型与数据库中的字符类型如VARCHARCHARTEXT等的映射关系。

  5. DateTypeHandlerTimeTypeHandlerTimestampTypeHandler
    这些TypeHandler处理Java中的日期和时间类型,如DateTimeTimestamp,与数据库中的DATETIMETIMESTAMP等类型的映射。

  6. ByteArrayTypeHandler
    处理Java的byte[]类型与数据库中的二进制类型的映射,如BLOBBINARY等。

  7. ClobTypeHandlerBlobTypeHandler
    分别处理Java中的Clob(字符大对象)和Blob(二进制大对象)类型与数据库中的CLOBBLOB类型的映射。

  8. EnumTypeHandlerEnumOrdinalTypeHandler
    这两个TypeHandler用于处理Java中的枚举类型。EnumTypeHandler将枚举名称存储到数据库,而EnumOrdinalTypeHandler将枚举的序数(ordinal)存储到数据库。

  9. JdbcTypeHandler
    这是一个通用的TypeHandler,它根据JDBC类型(中的常量)来确定具体的类型处理方式。

  10. UnknownTypeHandler
    当MyBatis无法确定具体的类型处理方式时,会使用这个TypeHandler。通常,这是一个最后的备选方案,它会尝试将值作为对象(Object)来处理。

以上只是MyBatis内置TypeHandler的一部分示例,实际上MyBatis提供了更多的内置TypeHandler以支持各种不同类型的数据转换需求。在使用MyBatis时,可以根据具体的数据库类型和Java类型选择合适的内置TypeHandler,或者根据需要自定义TypeHandler来处理特殊的数据类型转换场景。

四、自定义TypeHandler实践

在某些情况下,我们可能需要处理一些特殊的数据类型或者实现复杂的类型转换逻辑。这时,就需要自定义TypeHandler。下面是在Spring Boot环境中自定义TypeHandler的步骤:

1. 创建自定义TypeHandler类

自定义TypeHandler需要实现接口,或者继承类,并重写相应的方法。

TypeHandler是一个接口,用于定义如何处理JDBC类型和Java类型之间的转换。当你需要创建一个自定义的类型处理器时,通常需要实现这个接口。这个接口定义了以下主要方法:

  1. setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType):用于设置PreparedStatement对象的指定参数。
  2. getResult(ResultSet rs, String columnName):从结果集中根据列名获取数据。
  3. getResult(ResultSet rs, int columnIndex):从结果集中根据列索引获取数据。
  4. getResult(CallableStatement cs, int columnIndex):从存储过程的结果集中根据列索引获取数据。

这些方法分别负责在SQL语句执行时将Java类型的参数转换成JDBC类型,以及在执行SQL查询后将JDBC类型的结果转换成Java类型。

BaseTypeHandler是TypeHandler接口的一个抽象实现类,它提供了一些基本的实现,使得创建自定义类型处理器时只需要覆盖必要的方法。通常,如果你不需要处理所有的JDBC类型,可以选择继承BaseTypeHandler以减少工作量。

BaseTypeHandler 提供了对null值的处理以及部分TypeHandler接口方法的默认实现。当你继承BaseTypeHandler时,通常需要实现或覆盖以下方法:

  1. setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType):设置非空的参数值。
  2. getNullableResult(ResultSet rs, String columnName):根据列名获取可能为null的结果。
  3. getNullableResult(ResultSet rs, int columnIndex):根据列索引获取可能为null的结果。
  4. getNullableResult(CallableStatement cs, int columnIndex):从存储过程的结果集中根据列索引获取可能为null的结果。

这些方法专注于处理非空值的转换以及处理从数据库中检索的可能为null的值。

总的来说,TypeHandler接口提供了完整的JDBC类型和Java类型转换的契约,而BaseTypeHandler则是一个便利的基类,提供了一些基本的和通用的实现,以减少自定义类型处理器时的代码量。在创建自定义类型处理器时,可以根据具体需求选择直接实现TypeHandler接口还是继承BaseTypeHandler类。

2. 注册自定义TypeHandler

在MyBatis的配置文件中注册自定义的TypeHandler。如果是在Spring Boot环境中使用MyBatis,可以通过在文件中配置-handlers-package属性来指定TypeHandler所在的包路径,MyBatis会自动扫描并注册该包下的所有TypeHandler。

3. 在Mapper中使用自定义TypeHandler

在Mapper的XML映射文件中,通过resultTypeparameterType属性引用自定义的TypeHandler。例如,在<select>标签中设置resultType=""来指定使用自定义的TypeHandler处理查询结果。

自定义时间转换案例

首先,创建一个自定义的TypeHandler来处理LocalDateTime类型与数据库中的时间戳类型之间的转换。

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;
import java.time.LocalDateTime;

@MappedJdbcTypes(JdbcType.TIMESTAMP) // 指定对应的JDBC类型
@MappedTypes(LocalDateTime.class) // 指定对应的Java类型
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType) throws SQLException {
        ps.setTimestamp(i, java.sql.Timestamp.valueOf(parameter));
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
        java.sql.Timestamp timestamp = rs.getTimestamp(columnName);
        return timestamp != null ? timestamp.toLocalDateTime() : null;
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        java.sql.Timestamp timestamp = rs.getTimestamp(columnIndex);
        return timestamp != null ? timestamp.toLocalDateTime() : null;
    }

    @Override
    public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        java.sql.Timestamp timestamp = cs.getTimestamp(columnIndex);
        return timestamp != null ? timestamp.toLocalDateTime() : null;
    }
}

然后,在Spring Boot的配置中,你需要确保MyBatis能够扫描到这个TypeHandler。通常,MyBatis会自动扫描与Mapper接口相同的包路径下的TypeHandler。但是,如果你想要更明确地指定TypeHandler的位置,你可以通过MyBatis的配置文件来做到这一点。

src/main/resources目录下创建MyBatis的配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-////DTD Config 3.0//EN"
        "/dtd/">
<configuration>
    <typeHandlers>
        <package name=""/> <!-- 指定TypeHandler的包名 -->
    </typeHandlers>
</configuration>

中指定MyBatis配置文件的位置:

# 
-location=classpath:

确保你的Mapper接口使用了LocalDateTime类型的字段,并且你的数据库表中有对应的时间戳字段。

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.time.LocalDateTime;
import java.util.List;

@Mapper
public interface ExampleMapper {

    @Select("SELECT * FROM example_table")
    List<ExampleEntity> selectAll();

    // 其他CRUD操作...
}

// 对应的实体类
public class ExampleEntity {
    private Long id;
    private LocalDateTime createdAt;

    // 省略getter和setter方法...
}

最后,在Spring Boot的启动类或配置类上使用@MapperScan注解来指定Mapper接口所在的包名:

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("") // 指定Mapper接口所在的包名
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

确保你的Spring Boot项目中包含了MyBatis和MyBatis Spring Boot Starter的依赖项。你可以在中添加如下依赖:

<dependency>
    <groupId></groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version> <!-- 使用适合你项目的版本 -->
</dependency>

现在,当你运行Spring Boot应用时,MyBatis将会使用你自定义的LocalDateTimeTypeHandler来处理LocalDateTime类型与数据库中的TIMESTAMP类型之间的转换。注意,由于我们使用了@MappedJdbcTypes@MappedTypes注解,MyBatis将自动识别并使用这个TypeHandler。如果你没有在TypeHandler上使用这些注解,你需要在MyBatis配置文件中显式注册TypeHandler

要在MyBatis中注册自定义的TypeHandler,你通常有两种方法:

  1. 使用注解:直接在TypeHandler类上使用@MappedJdbcTypes和@MappedTypes注解。这种方法在你已经使用的示例中展示了。当MyBatis扫描到带有这些注解的TypeHandler时,它会自动注册。
  2. 在MyBatis配置文件中注册:如果你没有使用注解,或者想要更明确地注册TypeHandler,你可以在MyBatis的配置文件中手动添加它。

五、应用场景

自定义TypeHandler的应用场景非常广泛。以下是一些常见的应用场景:

  1. 处理枚举类型:将数据库中的整数值映射为Java中的枚举类型,或者将枚举类型转换为数据库中的整数值。

  2. 处理复杂数据类型:如将数据库中的JSON字符串映射为Java中的对象,或者将Java对象转换为JSON字符串存储到数据库中。

  3. 实现特殊的类型转换逻辑:如将数据库中的日期字符串转换为Java中的特定日期对象格式。

  4. 兼容不同的数据库类型:当使用不同类型的数据库时,可能需要处理不同类型之间的转换差异。通过自定义TypeHandler可以实现数据库类型之间的兼容转换。

六、结语

MyBatis的TypeHandler机制为Java类型和JDBC类型之间的转换提供了灵活且强大的支持。通过内置和自定义的TypeHandler,我们可以轻松处理各种数据类型转换需求,提高开发效率和代码可维护性。在Spring Boot环境中使用自定义TypeHandler更是简化了配置和注册过程,使得开发者能够更专注于业务逻辑的实现。


关注它获取更多技术干货 !