Mybatis TypeHandler介绍使用举例

时间:2024-11-07 09:24:26

目录

一、Mybatis工作原理简单回顾

(一)Mybatis框架分层

(二)原理分析

二、TypeHandler自定义转换类型介绍

(一)基本介绍

(二)注意事项和建议

三、业务使用基本分析展示

(一)自定义TypeHandler实现类

举例一:枚举类型的处理

举例二: LocalDateTime类型的处理

举例三: List 类型的处理

举例四: List 类型的处理

举例五:自定义TimePeriod 类型的处理

举例六: InetAddress 类型的处理

(二)在文件中进行反序列化配置

(三)在文件中进行序列化配置

(四)在mybatis的配置文件中配置typeHandlers


干货分享,感谢您的阅读!

一、Mybatis工作原理简单回顾

(一)Mybatis框架分层

(二)原理分析

MyBatis 是一款优秀的持久层框架,它的核心原理包括以下几个方面:

  1. 配置文件解析:MyBatis 使用 XML 配置文件来描述数据库连接、SQL 映射关系、缓存等信息。在启动时,MyBatis 会解析配置文件,将配置信息加载到内存中,并构建相应的对象模型来表示这些配置。
  2. SqlSessionFactory:SqlSessionFactory 是 MyBatis 的核心接口,它是用于创建 SqlSession 的工厂。SqlSessionFactory 是基于配置文件构建的,通过读取配置文件中的信息,创建出 SqlSessionFactory 对象,以供后续的数据库操作使用。
  3. SqlSession:SqlSession 是 MyBatis 的核心接口之一,它提供了执行 SQL 语句、提交事务、关闭连接等数据库操作的方法。SqlSession 是基于 SqlSessionFactory 创建的,每个 SqlSession 对象都代表一个对数据库的会话,它提供了对数据库的各种操作方法。
  4. 映射器(Mapper)接口:在 MyBatis 中,我们可以使用接口来定义 SQL 映射关系,这些接口被称为映射器(Mapper)接口。映射器接口通过注解或 XML 配置与具体的 SQL 语句进行绑定,并提供了对数据库的操作方法。MyBatis 会为这些接口动态生成实现类,从而实现对数据库的访问。
  5. SQL 语句的解析与执行:MyBatis 通过解析映射文件或注解中的 SQL 语句,将其转换为对应的数据库操作。它支持动态 SQL,可以根据条件生成不同的 SQL 语句,从而实现更灵活的数据库操作。
  6. 参数处理:MyBatis 提供了对 SQL 语句中参数的处理支持。它可以根据参数的类型、注解或配置文件中的映射规则,将 Java 对象与 SQL 语句中的参数进行自动转换。
  7. 结果集映射:MyBatis 支持将查询结果映射为 Java 对象。通过配置文件或注解,可以定义查询结果与 Java 对象之间的映射关系,包括属性名、类型转换、关联关系等。MyBatis 使用反射机制将查询结果转换为对应的 Java 对象。
  8. 一级缓存和二级缓存:MyBatis 提供了缓存机制来提高数据库访问的性能。一级缓存是 SqlSession 级别的缓存,它缓存了查询的结果对象。二级缓存是全局级别的缓存,它可以被多个 SqlSession 共享,提供了跨会话的缓存功能。
  9. 事务管理:MyBatis 支持事务管理,可以通过配置文件或注解来指定事务的隔离级别、传播行为以及异常回滚规则。在进行数据库操作时,MyBatis 会根据配置的事务管理方式来开启、提交或回滚事务,确保数据的一致性和完整性。
  10. 插件机制:MyBatis 提供了插件机制,可以在 SQL 语句执行的前后进行拦截和扩展。开发者可以编写自定义的插件,并在配置文件中注册,从而对 SQL 的执行过程进行拦截和增强,实现自定义的功能扩展。

总的来说,MyBatis 的核心原理是基于配置文件和接口定义的,通过解析配置文件、创建工厂、会话管理和数据库操作等步骤来实现与数据库的交互。它提供了灵活的 SQL 映射、参数处理、结果集映射、缓存和事务管理等功能,让开发者能够方便地进行数据库操作。同时,MyBatis 还支持插件机制,提供了扩展性和定制化的能力,使得开发者能够根据具体需求进行功能的增强和定制。

二、TypeHandler自定义转换类型介绍

(一)基本介绍

在 Java 开发中,TypeHandler 是 MyBatis 框架中的一个核心组件,用于实现数据库与 Java 类型之间的相互转换。它允许开发人员自定义类型处理器,以满足特定的业务需求。

TypeHandler 的作用是在 MyBatis 执行 SQL 查询或更新操作时,将数据库中的列值转换为 Java 对象,并在将 Java 对象写入数据库时执行相反的转换。它提供了一种灵活且可扩展的方式,用于处理数据库类型与 Java 类型之间的映射关系。

以下是 TypeHandler 的一些关键特点和使用方式:

  1. 类型转换:TypeHandler 负责将数据库中的列值转换为 Java 对象,并将 Java 对象转换为数据库可接受的类型。
  2. 自定义类型处理器:MyBatis 提供了一些默认的类型处理器,例如处理整数、字符串、日期等常见类型。同时,开发人员可以根据需要自定义类型处理器,实现特定类型的转换逻辑
  3. 注册类型处理器:自定义的类型处理器需要在 MyBatis 的配置文件中进行注册,以便 MyBatis 在执行数据库操作时能够找到并使用它们。
  4. 支持复杂类型:TypeHandler 不仅可以处理基本类型,还可以处理复杂类型,例如枚举、自定义对象等。
  5. 映射规则:TypeHandler 的使用是基于映射规则的,即在 MyBatis 的映射文件中,通过指定列和属性之间的映射关系,TypeHandler 才能正确地进行类型转换。

(二)注意事项和建议

在 MyBatis 中使用自定义 TypeHandler 进行类型转换时,还有一些注意事项和建议:

  1. 数据库字段与 Java 对象属性类型的匹配:确保数据库字段的类型与 Java 对象属性的类型匹配。如果数据库字段的类型与自定义 TypeHandler 处理的 Java 对象类型不一致,可能会导致转换错误或数据丢失。
  2. 处理 NULL 值:在自定义 TypeHandler 中处理 NULL 值是很重要的。可以通过判断传入的参数或结果集中的值是否为 NULL,并在处理过程中进行相应的处理。例如,可以将 NULL 值转换为 Java 对象的默认值,或者通过特定的标识值来表示 NULL。
  3. 注册 TypeHandler:确保在 MyBatis 的配置文件中正确注册自定义的 TypeHandler。如果没有正确注册,MyBatis 将无法找到并使用该 TypeHandler,导致类型转换失败。
  4. 测试和验证:在使用自定义 TypeHandler 进行类型转换之前,建议进行充分的测试和验证。确保自定义 TypeHandler 能够正确地将数据库类型转换为相应的 Java 对象类型,并且在查询和更新操作中能够正常工作。
  5. 了解官方提供的默认 TypeHandler:MyBatis 提供了一些默认的 TypeHandler,用于常见的数据类型转换,如字符串、整数、日期等。在自定义 TypeHandler 之前,建议先查看官方文档,了解是否已经有现成的 TypeHandler 可以满足你的需求。
  6. 可重用性和扩展性:在设计自定义 TypeHandler 时,考虑其可重用性和扩展性。如果有多个字段需要进行相同的类型转换,可以将转换逻辑封装在一个通用的 TypeHandler 中,并在映射文件中多次引用它。另外,可以通过继承或组合的方式扩展已有的 TypeHandler,以满足不同的业务需求。
  7. 注意线程安全性:自定义 TypeHandler 应该是线程安全的,因为 MyBatis 在多线程环境下可能会同时使用同一个 TypeHandler 实例进行类型转换。确保在实现自定义 TypeHandler 时考虑线程安全性,并采取相应的线程安全措施,如使用线程局部变量或同步机制。

以上是关于使用自定义 TypeHandler 进行类型转换的注意事项和建议。根据具体的业务需求,可以根据以上的指导来创建适合自己的自定义 TypeHandler,并在 MyBatis 中应用它。但是如果害怕出错还是建议直接使用默认的来处理即可,相关不合理的可能就直接通过查询或插入时进行转换了。

三、业务使用基本分析展示

(一)自定义TypeHandler实现类

自定义TypeHandler实现类,通常有两种方式:实现TypeHandler接口;继承BaseTypeHandler类。以扩展BaseTypeHandler类为例,说明如何实现想要转换成的数据库对象的具体逻辑。

举例一:枚举类型的处理

假设我们有一个订单实体类 Order,其中包含一个 Status 枚举类型的状态字段,在数据库中,我们将状态字段存储为一个字符串类型的列,例如 status。

package ;

/**
 * @author yanfengzhang
 * @description 订单实体类 Order
 * 其中包含一个 Status 枚举类型
 * @date 2023/5/13  23:34
 */
public enum Status {
    CREATED,
    PROCESSING,
    COMPLETED,
    CANCELLED
}

我们可以使用自定义的 TypeHandler 来实现 Status 枚举类型与数据库列值之间的转换。以下是一个示例的 TypeHandler 实现:

package ;

import ;
import ;

import ;
import ;
import ;
import ;

/**
 * @author yanfengzhang
 * @description 自定义的 TypeHandler 来实现 Status 枚举类型与数据库列值之间的转换
 * @date 2023/5/13  23:36
 */
public class StatusTypeHandler extends BaseTypeHandler<Status> {

    @Override
    public Status getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 从 ResultSet 中获取列值
        int data = (columnName);
        // 将列值转换为枚举对象
        return (data);
    }

    @Override
    public Status getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int data = (columnIndex);
        return (data);
    }

    @Override
    public Status getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        int data = (columnIndex);
        return (data);
    }

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Status status, JdbcType jdbcType) throws SQLException {
        // 将枚举值的code存储到数据库中
        (i, ());
    }
}

在上述示例中,我们自定义了一个 TypeHandler 类 StatusTypeHandler,用于处理 Status 枚举类型与数据库列值之间的转换。我们需要继承 BaseTypeHandler 并重写相应的方法来实现转换逻辑。

举例二: LocalDateTime类型的处理

假设我们有一个订单实体类 Order,其中包含一个 LocalDateTime 类型的创建时间字段,在数据库中,我们将创建时间字段存储为一个 DATETIME 类型的列,例如 create_time。

我们可以使用自定义的 TypeHandler 来实现 LocalDateTime 类型与数据库列值之间的转换。以下是一个示例的 TypeHandler 实现:

package ;

import ;
import ;

import ;
import ;
import ;
import ;
import ;
import ;

/**
 * @author yanfengzhang
 * @description 自定义的 TypeHandler 来实现 LocalDateTime 类型与数据库列值之间的转换
 * @date 2023/5/14  10:28
 */
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType) throws SQLException {
        // 将 LocalDateTime 转换为 Timestamp 并设置到 PreparedStatement 中
        (i, (parameter));
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 从 ResultSet 中获取 Timestamp 值
        Timestamp timestamp = (columnName);
        // 将 Timestamp 转换为 LocalDateTime
        return timestamp != null ? () : null;
    }

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

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

在上述示例中,我们自定义了一个 TypeHandler 类 LocalDateTimeTypeHandler,用于处理 LocalDateTime 类型与数据库列值之间的转换。我们需要继承 BaseTypeHandler 并重写相应的方法来实现转换逻辑。

现在,当使用 MyBatis 进行数据库查询时,会自动将数据库中的列值转换为对应的 LocalDateTime 对象,使我们能够方便地在 Java 代码中使用 LocalDateTime 类型。

举例三: List<String> 类型的处理

当处理 List 类型时,可以使用自定义的 TypeHandler 将 List 对象转换为字符串进行存储,或者从数据库中读取字符串并还原为 List 对象。

假设有一个订单实体类 Order,其中包含一个 List<String> 类型的商品列表字段(products),我们可以使用自定义的 TypeHandler 来实现 List<String> 类型与数据库列值之间的转换。以下是一个示例的 TypeHandler 实现:

package ;

import ;
import ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;

/**
 * @author yanfengzhang
 * @description 自定义的 TypeHandler 来实现 List<String> 类型与数据库列值之间的转换
 * @date 2023/5/14  10:39
 */
public class StringListTypeHandler extends BaseTypeHandler<List<String>> {
    private static final String DELIMITER = ",";

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
        // 将 List<String> 转换为以逗号分隔的字符串
        String rolesString = (DELIMITER, parameter);
        // 将字符串存储到数据库中
        (i, rolesString);
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 从 ResultSet 中获取字符串值
        String rolesString = (columnName);
        // 将字符串转换为 List<String>
        return parseRolesString(rolesString);
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String rolesString = (columnIndex);
        return parseRolesString(rolesString);
    }

    @Override
    public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String rolesString = (columnIndex);
        return parseRolesString(rolesString);
    }

    private List<String> parseRolesString(String rolesString) {
        if (rolesString == null || ()) {
            return ();
        }
        // 将字符串按逗号分隔转换为 List<String>
        return ((DELIMITER));
    }
}

在上述示例中,我们自定义了一个 TypeHandler 类 StringListTypeHandler,用于处理 List<String> 类型与数据库列值之间的转换。我们需要继承 BaseTypeHandler 并重写相应的方法来实现转换逻辑。现在,当使用 MyBatis 进行数据库查询时,会自动将数据库中的列值转换为对应的 List<String> 对象,使我们能够方便地在 Java 代码中使用 List<String> 类型。

举例四: List<Long> 类型的处理

同样的,可以自定义 TypeHandler 中将 VARCHAR 类型的数据库列与 List<Long> 类型的 Java 对象进行转换

package ;

import ;
import ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;

/**
 * @author yanfengzhang
 * @description 自定义 TypeHandler 中将 VARCHAR 类型的数据库列与 List<Long> 类型的 Java 对象进行转换
 * @date 2023/5/14  10:48
 */
public class LongListTypeHandler extends BaseTypeHandler<List<Long>> {

    private static final String DELIMITER = ",";

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<Long> parameter, JdbcType jdbcType) throws SQLException {
        String joinedString = ().map(String::valueOf).collect((DELIMITER));
        (i, joinedString);
    }

    @Override
    public List<Long> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = (columnName);
        return convertStringToList(columnValue);
    }

    @Override
    public List<Long> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String columnValue = (columnIndex);
        return convertStringToList(columnValue);
    }

    @Override
    public List<Long> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String columnValue = (columnIndex);
        return convertStringToList(columnValue);
    }

    private List<Long> convertStringToList(String columnValue) {
        if (columnValue == null || ()) {
            return null;
        }
        List<Long> resultList = new ArrayList<>();
        String[] values = (DELIMITER);
        for (String value : values) {
            ((value));
        }
        return resultList;
    }
}

举例五:自定义TimePeriod 类型的处理

同样的,可以自定义 TypeHandler 中将数据库中的时间段(以秒为单位)与 Java 对象中的 TimePeriod 类型进行转换。

首先,定义 TimePeriod 类表示时间段:

package ;

import ;

import ;

/**
 * @author yanfengzhang
 * @description
 * @date 2023/5/14  10:58
 */
@Data
public class TimePeriod {
    private LocalDateTime startTime;
    private LocalDateTime endTime;

    public TimePeriod(LocalDateTime startTime, LocalDateTime endTime) {
         = startTime;
         = endTime;
    }
}

然后,定义 TimePeriodTypeHandler 类实现 TypeHandler 接口来处理 TimePeriod 类型的转换:

package ;

import ;
import ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;

/**
 * @author yanfengzhang
 * @description 自定义 TypeHandler 中将数据库中的时间段(以秒为单位)与 Java 对象中的 TimePeriod 类型进行转换
 * @date 2023/5/14  10:52
 */
public class TimePeriodTypeHandler implements TypeHandler<TimePeriod> {
    @Override
    public void setParameter(PreparedStatement ps, int i, TimePeriod parameter, JdbcType jdbcType) throws SQLException {
        // 将 Java 对象转换为数据库类型,并设置到 PreparedStatement 中的指定参数位置
        if (parameter != null) {
            (i, (()));
            (i + 1, (()));
        } else {
            (i, );
            (i + 1, );
        }
    }

    @Override
    public TimePeriod getResult(ResultSet rs, String columnName) throws SQLException {
        // 从 ResultSet 中获取指定列名的数据库值,并将其转换为对应的 Java 对象
        LocalDateTime startTime = (columnName).toLocalDateTime();
        LocalDateTime endTime = (columnName + "_end").toLocalDateTime();
        return new TimePeriod(startTime, endTime);
    }

    @Override
    public TimePeriod getResult(ResultSet rs, int columnIndex) throws SQLException {
        // 从 ResultSet 中获取指定列索引的数据库值,并将其转换为对应的 Java 对象
        LocalDateTime startTime = (columnIndex).toLocalDateTime();
        LocalDateTime endTime = (columnIndex + 1).toLocalDateTime();
        return new TimePeriod(startTime, endTime);
    }

    @Override
    public TimePeriod getResult(CallableStatement cs, int columnIndex) throws SQLException {
        // 从 CallableStatement 中获取指定列索引的数据库值,并将其转换为对应的 Java 对象
        LocalDateTime startTime = (columnIndex).toLocalDateTime();
        LocalDateTime endTime = (columnIndex + 1).toLocalDateTime();
        return new TimePeriod(startTime, endTime);
    }
}

举例六: InetAddress 类型的处理

假设我们有一个订单实体类 Order,其中包含一个 InetAddress 类型的 IP 地址字段,在数据库中,我们将 IP 地址存储为一个字符串类型的列,例如 ip_address。

我们可以使用自定义的 TypeHandler 来实现 InetAddress 类型与数据库列值之间的转换。以下是一个示例的 TypeHandler 实现:

package ;

import ;
import ;

import ;
import ;
import ;
import ;
import ;
import ;

/**
 * @author yanfengzhang
 * @description 用于处理 InetAddress 类型与数据库列值之间的转换
 * @date 2023/5/14  11:20
 */
public class InetAddressTypeHandler extends BaseTypeHandler<InetAddress> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, InetAddress parameter, JdbcType jdbcType) throws SQLException {
        // 获取 IP 地址的字符串表示
        String ipAddressString = ();
        // 将字符串存储到数据库中
        (i, ipAddressString);
    }

    @Override
    public InetAddress getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 从 ResultSet 中获取字符串值
        String ipAddressString = (columnName);
        // 将字符串转换为 InetAddress
        return parseIpAddressString(ipAddressString);
    }

    @Override
    public InetAddress getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String ipAddressString = (columnIndex);
        return parseIpAddressString(ipAddressString);
    }

    @Override
    public InetAddress getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String ipAddressString = (columnIndex);
        return parseIpAddressString(ipAddressString);
    }

    private InetAddress parseIpAddressString(String ipAddressString) {
        try {
            // 将字符串解析为 InetAddress
            return (ipAddressString);
        } catch (UnknownHostException e) {
            // 处理解析错误,例如返回默认的 IP 地址或抛出异常
            return null;
        }
    }
}

(二)在文件中进行反序列化配置

该写法只对返回此resultMap的查询语句有效

 <resultMap  type="">
    <id column="id" jdbcType="BIGINT" property="id"/>
    <result column="name" jdbcType="VARCHAR" property="name"/>
    <result column="status" jdbcType="INTEGER" property="status" 
            typeHandler="" />
    <result column="limit_period" jdbcType="VARCHAR" property="limitPeriod" 
            typeHandler=""/>
    <result column="create_time" jdbcType="DATETIME" property="createTime"         
            typeHandler=""/>
    <result column="sku_ids" jdbcType="VARCHAR" property="skuIds" 
            typeHandler=""/>/>
    <result column="products" jdbcType="VARCHAR" property="products"
            typeHandler=""/>
    <result column="ip_address" jdbcType="VARCHAR" property="ipAddress"
            typeHandler=""/>
    <result column="cname" jdbcType="VARCHAR" property="cname"/>
    <result column="uname" jdbcType="VARCHAR" property="uname"/>
</resultMap>

(三)在文件中进行序列化配置

如果希望在写入数据库的时候,按照指定规则对写入对象进行序列化,则需要在写入sql语句处显式指定typeHandler

<update  parameterType="map"  >
    UPDATE
        <include ref/>
    SET
        name = #{},
        limit_period = #{, typeHandler=},
        sku_ids =#{, typeHandler=},
        uname = #{},
        utime = unix_timestamp()
    where id = #{}
</update>

(四)在mybatis的配置文件中配置typeHandlers

方法一:启动时会扫描包下的所有文件

<typeHandlers>
        <package name=""/>
</typeHandlers>

方法二:或单个写入要注册的TypeHandler

<typeHandlers>
     <typeHandler handler=""
                  javaType=""
                  jdbcType="VARCHAR"/>
     <typeHandler handler=""
                  javaType=""
                  jdbcType="VARCHAR"/>
</typeHandlers>

参考文章链接

  1. Mybatis之工作原理_mybatis 的原理_Javxuan的博客-****博客
  2. MyBatis 官方文档 - TypeHandlers:MyBatis 官方文档中关于 TypeHandler 的详细说明和使用示例。
  3. MyBatis TypeHandler 使用详解:一篇博客文章介绍了如何使用 MyBatis 中的 TypeHandler,并提供了详细的示例和说明。
  4. MyBatis TypeHandler 的自定义与应用:该文章介绍了如何自定义 TypeHandler,并通过示例演示了各种类型转换的场景。
  5. MyBatis 之 TypeHandler 实现原理:一篇知乎专栏文章,深入解析了 MyBatis 中的 TypeHandler 实现原理和相关的核心类。
  6. MyBatis 之 TypeHandler 源码解析:该文章通过分析 MyBatis 中的 TypeHandler 源码,解释了其内部工作原理和实现细节。