【微服务】mybatis typehandler使用详解

目录

一、前言

二、TypeHandler简介

2.1 什么是TypeHandler

2.1.1 TypeHandler特点

2.2 TypeHandler原理

2.3 mybatis自带的TypeHandler

三、环境准备

3.1 准备一张数据表

3.2 搭建一个springboot工程

3.2.1 基础依赖如下

3.2.2 核心配置文件

3.2.3 测试接口

四、TypeHandler在项目中的使用

4.1 时间类型转换问题

4.1.1 添加自定义TypeHandler

4.1.2 添加扫描

4.1.3 xml配置字段引用自定义TypeHandler

4.1.4 接口效果测试

4.1.5 新增数据

4.2 常用的TypeHandler

4.2.1 List类型的处理

4.2.2 枚举类型的处理

4.2.3 json类型的处理

4.3 注意事项

4.3.1 自定义TypeHandler步骤

4.3.2 自定义TypeHandler使用注意事项

五、写在文末


一、前言

在springboot为基础框架的微服务开发中,经常要涉及到数据库的操作,此时mybatis或mybatis-plus便成为技术选型中必不可少的组件。基本上来说,springboot+mybatis成为很多公司微服务项目的基础选型。使用mybatis的好处很多,这里不再赘述了,接下来要探讨的是在业务代码中,如何使用mybtais完成一些字段的类型转换。

二、TypeHandler简介

2.1 什么是TypeHandler

TypeHandler,即:类型转换器,在mybatis中用于实现 java类型 和 JDBC类型 的相互转换,在 Java 与mybatis的整合开发中,TypeHandler 是 MyBatis 框架的一个核心组件,用于实现数据库表字段类型与和Java 数据类型之间的相互转换。这个组件的存在,它允许开发人员自定义类型处理器,以满足特定场景下的业务需求。

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

 

2.1.1 TypeHandler特点

TypeHandler在实际项目中该如何使用呢?需要搞清它的特点才能更好的掌握其使用场景,以下列举了TypeHandler 一些特点和使用方式;

  • 类型转换,TypeHandler 负责将数据库表中的字段值转换为 Java 对象,并将 Java 对象转换为数据库表中可被接受的类型;
  • 映射规则,TypeHandler 是基于映射规则的(在下文的表中详细给出了常用的映射规则),即在 MyBatis 映射文件中,需要指定表字段和对象属性之间的映射关系,这样TypeHandler才能正确进行类型转换;
  • 支持自定义类型转换器,MyBatis 提供了一些默认的类型处理器,例如整数、字符串、日期等常见的数据类型,同时,开发人员可根据业务需要自定义类型处理器,实现特定类型的转换逻辑,比如你可以在自定义处理器中处理字段加密的逻辑;
  • 注册类型处理器,自定义类型处理器需要在 MyBatis 配置文件中进行注册,这样MyBatis 在执行数据库操作时才能找到并使用它们;
  • 支持复杂的类型,TypeHandler 不仅可以处理基本数据类型,还可以处理复杂类型,例如枚举、自定义对象等;

2.2 TypeHandler原理

还记得在使用jdbc操作数据库的时候,我们是如何设置一个查询的sql语句中的字段参数值的吗,没错,就是使用PrepareStatement对象中的set方法。

对mybatis原理有所了解的同学应该不陌生,其实mybatis框架就是对jdbc的完美封装。所以,mybatis使用 prepareStatement 进行参数设置的时候,需要通过 TypeHandler 将传入的java参数设置成合适的jdbc类型参数,这个过程实际上是通过调用 PrepareStatement 不同的set方法实现的。

String sql = "SELECT * tb_user where usernmae=? and pwd=?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "jerry");
pstmt.setString(1, "root@321");
ResultSet rs = pstmt.executeQuery();

在获取到返回结果之后,也需要将返回的结果转换成需要的java数据类型,这时候是通过调用 ResultSet 对象不同类型的get方法实现的。比如我们在得到jdbc的结果集之后,会通过下面的方式获得某个字段的结果:

String id=rs.getString("id");
int number=rs.getInt("number");

所以不同类型的 TypeHandler 其实就是调用 PrepareStatement 和 ResultSet 的不同方法来进行类型的转换,有些时候会在调用 PrepareStatement 和 ResultSet 的相关方法之前,可以对传入的参数进行一定的处理.。

而当我们没有指定 TypeHandler 时 ,mybatis 会根据入参类型和返回值类型调用默认的 TypeHandler 进行处理。

对于一个 TypeHandler来说, 需要配置 java类型(javaType) 和 JDBC类型(jdbcType),TypeHandler 作用就是实现这两种类型的转换,在传入参数为指定的Java类型时,通过TypeHandler ,将其转换为指定的JDBC类型,当返回值为指定JDBC类型时,TypeHandler 再将其转换为配置的Java类型。

2.3 mybatis自带的TypeHandler

MyBatis 默认已经内置了一些常用的TypeHandler,大部分场景下无须显式声明 jdbcType 和 javaType,或者用 TypeHandler 去指定对应的字段来实现数据类型转换,因为 mybatis会自动探测待映射的数据类型,从而自动选择合适的 TypeHandler。下面列举了mybatis常用的TypeHandler

类型处理器Java类型JDBC类型
BooleanTypeHandlerjava.lang.Boolean,boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERIC 或 LONG INTEGER
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandlerjava.lang.Double,double数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的索引(而不是名称)

三、环境准备

3.1 准备一张数据表

为了方便后面实验演示,这里准备一张数据表,建表sql如下

CREATE TABLE `tb_user` (
  `id` int(12) NOT NULL,
  `user_name` varchar(32) DEFAULT NULL,
  `pwd` varchar(64) DEFAULT NULL,
  `user_image` blob,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.2 搭建一个springboot工程

技术栈,springboot+mybatis+mysql

3.2.1 基础依赖如下

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.23</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

    </dependencies>

    <build>
        <finalName>hook-service</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

3.2.2 核心配置文件

主要是配置连接mysql与mybatis的配置

server:
  port: 8088

spring:
 application:
   name: user-service

 datasource:
   url: jdbc:mysql://IP:3306/db-base
   driverClassName: com.mysql.jdbc.Driver
   username: root
   password: root

mybatis:
  mapper-locations: classpath:mapper/*.xml
  #目的是为了省略resultType里的代码量
  type-aliases-package: com.congge.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.2.3 测试接口

@RestController
@RequestMapping("/user")
public class TbUserController {

    @Autowired
    private TbUserService tbUserService;

    //http:localhost:8088/user/get?id=1
    @GetMapping("/get")
    public TbUser getById(@RequestParam int id){
        return tbUserService.getById(id);
    }

}

业务实现逻辑比较简单就忽略了,最后在mybatis的查询文件中,一般建议配置一resultMap,在这个resultMap中,编写Java实体类属性与数据表的映射关系对应规则,而后面将要说到的自定义TypeHandler也将在这里进行配置,参考如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.congge.dao.TbUserDao">

    <resultMap id="baseMap" type="com.congge.entity.TbUser">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="user_name" jdbcType="VARCHAR" property="userName" />
        <result column="pwd" jdbcType="VARCHAR" property="pwd" />
        <result column="user_image" jdbcType="BLOB" property="userImage" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
    </resultMap>

    <select id="getById" resultMap="baseMap">
        select * from tb_user where id = #{id}
    </select>
</mapper>

启动工程后,测试一下接口,可以看到直接报错了

为什么会出现这个问题呢?这该如何处理呢?

四、TypeHandler在项目中的使用

4.1 时间类型转换问题

在上面的接口测试中,发现接口返回的时间类型并不是我们想要的,遇到这个问题,就是数据库的字段类型与java中的时间字段类型不匹配,或者说无法通过默认的TypeHandler进行转换了,该如何解决呢?

4.1.1 添加自定义TypeHandler

自定义一个TypeHandler类,只需要继承BaseTypeHandler类,然后重写里面的setNonNullParameter和getNullableResult方法,在重写的方法里面编写相关的业务逻辑即可。

import org.apache.ibatis.type.*;

import java.sql.*;

/**
 * 自定义类型转换:将数据库中的日期类型,转换成long类型的时间戳
 *
 * 三种注册方式:
 * 1.直接在 result标签中,指定typeHandler,如@Result(property = "createTime", column = "create_time", jdbcType = JdbcType.TIMESTAMP, typeHandler = Timestamp2LongHandler.class)
 * 2.在SqlSessionFactory实例中,注册 在SqlSessionFactory实例中.setTypeHandlers(new Timestamp2LongHandler());
 * 3.mapper 的xml配置,<typeHandler handler="com.congge.handler.Timestamp2LongHandler"/>
 */
@MappedTypes(value = Long.class)
@MappedJdbcTypes(value = {JdbcType.DATE, JdbcType.TIME, JdbcType.TIMESTAMP})
public class Timestamp2LongHandler extends BaseTypeHandler<Long> {

    /**
     * 将java类型,转换为jdbc类型
     *
     * @param preparedStatement
     * @param i
     * @param aLong             毫秒时间戳
     * @param jdbcType          db字段类型
     * @throws SQLException
     */
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Long aLong, JdbcType jdbcType) throws SQLException {
        if (jdbcType == JdbcType.DATE) {
            preparedStatement.setDate(i, new Date(aLong));
        } else if (jdbcType == JdbcType.TIME) {
            preparedStatement.setTime(i, new Time(aLong));
        } else if (jdbcType == JdbcType.TIMESTAMP) {
            preparedStatement.setTimestamp(i, new Timestamp(aLong));
        }
    }

    @Override
    public Long getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return parse2time(resultSet.getObject(s));
    }

    @Override
    public Long getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return parse2time(resultSet.getObject(i));
    }

    @Override
    public Long getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return parse2time(callableStatement.getObject(i));
    }

    private Long parse2time(Object value) {
        if (value instanceof Date) {
            return ((Date) value).getTime();
        } else if (value instanceof Time) {
            return ((Time) value).getTime();
        } else if (value instanceof Timestamp) {
            return ((Timestamp) value).getTime();
        }
        return null;
    }
}

对自定义的TypeHandler类,对类上的两个注解做如下补充说明:

  • @MappedJdbcTypes,代表对应的数据库中字段的类型;

  • @MappedTypes,代表要转换后的的JavaBean中的属性(对象)类型;

4.1.2 添加扫描

当一个自定义的TypeHandler编写完成后,如何生效并被使用呢?在上述代码的注释中,其实给出了好几种方式,一般比较常用的像注册到bean中,或者配置到mybatis的全局配置文件中,这里提供另一种方式,就是直接在application.yaml中配置一下扫描路径也可以。(最后一行)

mybatis:
  mapper-locations: classpath:mapper/*.xml
  #目的是为了省略resultType里的代码量
  type-aliases-package: com.congge.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-handlers-package: com.congge.handler

4.1.3 xml配置字段引用自定义TypeHandler

然后再在上述的UserMapper.xml文件中,在resultMap中的createTime字段映射上引用上述的TypeHandler

<resultMap id="baseMap" type="com.congge.entity.TbUser">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="user_name" jdbcType="VARCHAR" property="userName" />
        <result column="pwd" jdbcType="VARCHAR" property="pwd" />
        <result column="user_image" jdbcType="BLOB" property="userImage" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
        <result property="createTime" column="create_time" typeHandler="com.congge.handler.Timestamp2LongHandler"/>
    </resultMap>

4.1.4 接口效果测试

以上配置完成后,再次启动工程调用接口,此时可以看到返回的时间可以正确的被转换为Long类型

4.1.5 新增数据

如果是新增数据时,由于在自定义TypeHandler已经对传入数据库的时间字段进行了转换处理,所以按照下面的操作即可。

public int save(TbUser tbUser) {
        Integer count = tbUserDao.selectCount();
        Integer id = count+1;
        tbUser.setId(count+1);
        tbUser.setCreateTime(System.currentTimeMillis());
        tbUserDao.save(tbUser);
        return id;
}

再在xml文件的sql里面,针对createTime字段添加自定义的TypeHandler

<insert id="save">
        insert into tb_user(id,user_name,pwd,user_image,create_time) values(
          #{id},#{userName},#{pwd},#{userImage},
          #{createTime,javaType=Long,jdbcType=TIMESTAMP,typeHandler=com.congge.handler.Timestamp2LongHandler}
        )
    </insert>

然后调用数据插入接口时,就能正常的被解析为数据库的TIMESTAMP类型

4.2 常用的TypeHandler

在真实的业务场景中,涉及到需要进行转换的字段类型可能非常多,下面总结了一些常用的开发中使用到的数据类型转换的自定义TypeHandler。

4.2.1 List<String>类型的处理

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

比如在一个用户表中,关联了多个角色,这个角色字段relate_roles,存储多个数据时以","分割,而在用户BizUser对象中,包含了一个List<String>类型的属性与relate_roles对应,我们就可以自定义一个handler来实现入库时和查询映射到对象属性时的转换。

mysql建表语句

CREATE TABLE `biz_user` (
  `id` int(12) NOT NULL,
  `user_name` varchar(32) DEFAULT NULL,
  `relate_roles` varchar(132) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

自定义以TypeHandler类,代码如下

import org.apache.ibatis.type.BaseTypeHandler;

import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.ibatis.type.*;

import java.sql.*;

//@MappedTypes(value = Long.class)
//@MappedJdbcTypes(value = {JdbcType.DATE, JdbcType.TIME, JdbcType.TIMESTAMP})

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 = String.join(DELIMITER, parameter);
        ps.setString(i, rolesString);
    }

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

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

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

    private List<String> parseRolesString(String rolesString) {
        if (rolesString == null || rolesString.isEmpty()) {
            return Collections.emptyList();
        }
        return Arrays.asList(rolesString.split(DELIMITER));
    }
}

最后在mybatis的配置文件中做一下配置即可

<resultMap id="baseMap" type="com.congge.entity.BizUser">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="user_name" jdbcType="VARCHAR" property="userName" />
        <result column="relate_roles" jdbcType="VARCHAR" property="relateRoles" typeHandler="com.congge.handler.StringListTypeHandler" />
    </resultMap>

接口测试,提前在数据库中准备一条数据,然后通过接口调用,可以看到下面的效果,说明成功转换

4.2.2 枚举类型的处理

假设我们有一个用户的实体类对象 BizUser,其中包含一个 SexEnum枚举类型的状态字段,在数据库中,我们将状态字段存储为一个字符串类型的列,例如 gender

@Getter
public enum SexEnum {
    MALE(1, "男"),
    FEMALE(0, "女");

    private int id;
    private String name;

    SexEnum(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static SexEnum getSexById(int id) {
        for (SexEnum sex : SexEnum.values()) {
            if (sex.getId() == id) {
                return sex;
            }
        }
        return null;
    }

}

实体类

@Getter
@Setter
public class BizUser {

    private int id;
    private String userName;
    private List<String>  relateRoles;

    private SexEnum sex;

}

可以使用自定义 TypeHandler 来处理 UserStatus枚举类型与数据库列值之间的转换,参考如下示例的 TypeHandler 实现

public class SexEnumTypeHandler extends BaseTypeHandler<SexEnum> {

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

    @Override
    public SexEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int data = rs.getInt(columnIndex);
        return SexEnum.getSexById(data);
    }

    @Override
    public SexEnum getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        int data = callableStatement.getInt(columnIndex);
        return SexEnum.getSexById(data);
    }

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

4.2.3 json类型的处理

假如在biz_user表中有一个描述的信息json,是BizUser对象关联的一个对象存储进来的,如下:BizUser对象中持有一个Content的对象引用

@Getter
@Setter
public class BizUser {

    private int id;
    private String userName;
    private List<String>  relateRoles;

    private SexEnum sex;

    private Content content;
}

Content对象

@Getter
@Setter
public class Content {

    private String desc;
    private String level;
    private String hit;
}

对应的数据表如下

CREATE TABLE `biz_user` (
  `id` int(12) NOT NULL,
  `user_name` varchar(32) DEFAULT NULL,
  `relate_roles` varchar(132) DEFAULT NULL,
  `gender` int(1) DEFAULT NULL,
  `content` json DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

要完成这样一个场景下字段到实体对象的映射,可以参考下面的做法,自定义一个ObjectJSONTypeHandler

import com.congge.entity.Content;
import com.google.gson.Gson;
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 ObjectJSONTypeHandler extends BaseTypeHandler<Content> {

    private Gson gson = new Gson();

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Content parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, gson.toJson(parameter));
    }

    @Override
    public Content getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return gson.fromJson(rs.getString(columnName), Content.class);
    }

    @Override
    public Content getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return gson.fromJson(rs.getString(columnIndex), Content.class);
    }

    @Override
    public Content getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return gson.fromJson(cs.getString(columnIndex), Content.class);
    }
}

最后再在mybatis的映射文件中将自定义TypeHandler加进去即可

<resultMap id="baseMap" type="com.congge.entity.BizUser">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="user_name" jdbcType="VARCHAR" property="userName" />
        <result column="relate_roles" jdbcType="VARCHAR" property="relateRoles" typeHandler="com.congge.handler.StringListTypeHandler" />
        <result column="gender" jdbcType="INTEGER" property="sex" typeHandler="com.congge.handler.SexEnumTypeHandler" />
        <result column="content" property="content" typeHandler="com.congge.handler.ObjectJSONTypeHandler" />
    </resultMap>

测试一下接口可以看到下面的效果

4.3 注意事项

4.3.1 自定义TypeHandler步骤

通过上面的案例介绍了几种常用的TypeHandler类型处理方式,如果还有其他的特殊场景也可以参考进行处理,总结一下自定义的TypeHandler使用流程主要分为下面几个步骤:

  1. 确定数据库字段的类型,以及要转换为JavaBean中的属性类型;

  2. 自定义一个TypeHandler类,继承这个BaseTypeHandler抽象类;

  3. 注册TypeHandler(ssm项目中,如果有mybatis的全局配置文件的话需要注册一下);

  4. 再在mybatis的业务xml文件中,属性与字段映射的位置,将需要转换的字段和属性使用这个自定义TypeHandler即可;

通常来说,通过上面的几步,就可以完成插入数据时,数据库字段是预期类型的值,而查询数据时,接口返回的是java中预期类型的值,但是在某些情况下,比如像上面的enum这种枚举类型的,还需要再返回值中进一步处理;

4.3.2 自定义TypeHandler使用注意事项

在 MyBatis 中,如果你使用了自定义 TypeHandler 进行类型转换,下面有一些注意事项和建议:

数据库字段与 Java 对象属性类型的匹配

如果数据库字段的类型与自定义 TypeHandler 处理的 Java 对象类型不一致,可能会导致转换错误或数据丢失。

处理 NULL 值

在自定义 TypeHandler 中, NULL 值的处理很重要,可通过判断传入的参数或结果集中的值是否为 NULL,并在处理过程中进行相应的处理。例如,可以将 NULL 值转换为 Java 对象的默认值,或者通过特定的标识值来表示 NULL。

注册 TypeHandler

确保在 MyBatis 的配置文件中正确注册自定义的 TypeHandler,如果没有正确注册,MyBatis 将无法找到并使用该 TypeHandler而导致类型转换失败。

测试和验证

在使用自定义 TypeHandler 进行类型转换之前,建议进行充分的测试和验证。确保自定义 TypeHandler 能够正确地将数据库类型转换为相应的 Java 对象类型,并且在查询和更新操作中能够正常工作。

了解官方提供的默认 TypeHandler

MyBatis 提供了一些默认的 TypeHandler,用于常见的数据类型转换,如字符串、整数、日期等。在自定义 TypeHandler 之前,建议先查看官方文档,了解是否已经有现成的 TypeHandler 可以满足你的需求。

可重用性和扩展性

在设计自定义 TypeHandler 时,考虑其可重用性和扩展性。如果有多个字段需要进行相同的类型转换,可以将转换逻辑封装在一个通用的 TypeHandler 中,并在映射文件中多次引用它。另外,可以通过继承或组合的方式扩展已有的 TypeHandler,以满足不同的业务需求。

注意线程安全性

自定义 TypeHandler 应该是线程安全的,因为 MyBatis 在多线程环境下可能会同时使用同一个 TypeHandler 实例进行类型转换。确保在实现自定义 TypeHandler 时考虑线程安全性,并采取相应的线程安全措施,如使用线程局部变量或同步机制。

五、写在文末

自定义TypeHandler在实际的开发中可以说是一个很常见的问题,虽然并不难,但是如果不了解其底层原理得话,使用起来很容易陷入忙乱中,有必要深入学习和掌握,本篇到此结束,感谢观看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/409034.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java面向对象高级

一、静态 static读作静态&#xff0c;可以用来修饰成员变量&#xff0c;也能修饰成员方法。我们先来学习static修饰成员变量。 1.1 static修饰成员变量 Java中的成员变量按照有无static修饰分为两种&#xff1a;类变量、实例变量。它们的区别如下图所示&#xff1a; 由于静态…

通过底层原理理解Java是值传递还是引用传递?

本文学习目标或者巩固的知识点 参数传递方式 值传递引用传递指针传递 彻底理解Java的值传递和引用传递 从底层的角度分析值传递会发生复制行为 Java的参数传递例子 快手的一面面试曾经问到过此类题目&#xff0c;所以记下此篇加深印象。 问&#xff1a;求下面main方法中的输…

常用状态码

状态码 用于响应中的&#xff0c;表示响应的结果如何 1、200 OK 运行成功 2、404 Not Found 访问的资源没有找到&#xff08;url的路径&#xff09; 3、403 Forbidden 请求资源没有权限访问 4、405 Method Not Allowed 你的服务器只支持GET请求&#xff0c;但是你发了个PO…

基于springboot+vue的校园社团信息管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

sizeof和strlen的对比及练习题(超详细)

创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ sizeof和strlen的对比 sizeof举例说明 sizeof计算int类型数据 &#xff08;有关于数组&#xff09;sizeof计算 strlen举例说明 strlen是C语言库函数&#xff0c;求字符串长度 函数原型&#xff1a; size_t s…

前端项目打包体积分析与优化

一、安装依赖分析工具 npm install webpack-bundle-analyz 二、修改webpack.config.js文件 1、导入上面下载的包 2、在plugins里创建实例 三、启动打包命令 npm run build 会弹出如下界面&#xff1a; 四、优化 1、通过CDN导入react-dom文件 修改webpack.config.js文件里…

掌握3个Mock工具,轻松玩转单元测试

公司要求提升单元测试的质量&#xff0c;提高代码的分支覆盖率和行覆盖率&#xff0c;安排我研究单元测试&#xff0c;指定方案分享并在开发部普及开。 单元测试中的Mock的目的 Mock的主要目的是让单元测试Write Once, Run Everywhere. 即编写一次后&#xff0c;可以在任意时…

LeetCode--代码详解 236. 二叉树的最近公共祖先

236. 二叉树的最近公共祖先 题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&am…

中国农业无人机行业市场现状分析与投资前景预测研究报告

全版价格&#xff1a;壹捌零零 报告版本&#xff1a;下单后会更新至最新版本 交货时间&#xff1a;1-2天 第一章农业无人机行业发展综述 第一节农业无人机行业定义及分类 一、农业无人机行业的定义 农业无人机是一种无人驾驶的飞行器来帮助优化农业经营&#xff0c;增加作…

找游戏 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 小扇和小船今天又玩起来了数字游戏&#xff0c; 小船给小扇一个正整数 n&#xff08;1 ≤ n ≤ 1e9&#xff09;&#xff0c;小扇需要找到一个比 n 大的数字 m&a…

C++ //练习 8.8 修改上一题的程序,将结果追加到给定的文件末尾。对同一个输出文件,运行程序至少两次,检验数据是否得以保留。

C Primer&#xff08;第5版&#xff09; 练习 8.8 练习 8.8 修改上一题的程序&#xff0c;将结果追加到给定的文件末尾。对同一个输出文件&#xff0c;运行程序至少两次&#xff0c;检验数据是否得以保留。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工…

dolphinscheduler伪集群部署教程

文章目录 前言一、配置免密登录1. 配置root用户免密登录2. 创建用户2.1 创建dolphinscheduler用户2.2 配置dolphinscheduler用户免密登录2.3 退出dolphinscheduler用户 二、安装准备1. 安装条件2. 安装jdk3. 安装MySQL4. 安装zookeeper4.1 zookeeper单机部署4.2 启动zookeeper4…

14-ATF中对多核的支持

讨论一个系统、一个软件或ATF对多核的支持,其实就是看这个软件,在启动阶段如何区分主核、从核的? 在runtime阶段,是否能把不同核的CPU Data加以区分?是否能区分出cpuid? runtime阶段:主核和从核的区分 在启动阶段,会读取平台函数plat_is_my_cpu_primary来判单,当前是…

java 面向对象-上:类的结构之二

类的设计中&#xff0c;两个重要结构之二&#xff1a;方法 方法 描述类应该具的功能。 比如&#xff1a;Math类&#xff1a;sqrt()\random() \... Scanner类&#xff1a;nextXxx() ... Arrays类&#xff1a;sort() \ binarySearch() \ toString() \ equals() \ ... 1.举例 p…

哈希-数组中两数之和

文章目录 题目解题思路具体步骤 题目 在编程和算法学习中&#xff0c;"两数之和"问题是一个非常经典的问题&#xff0c;它不仅测试了基本的编程能力&#xff0c;还涉及到数据结构的有效使用&#xff0c;特别是哈希表。问题的描述是这样的&#xff1a; 题目&#xff…

1110. 删点成林

1110. 删点成林 关键要点 通过O(1)时间复杂度确认节点是否需要删除 Set to_deleteSet new HashSet<>(); Arrays.stream(to_delete).forEach(to_deleteSet::add); 使用深度优先搜索&#xff08;DFS&#xff09;遍历树 node.left dfs(node.left, s, ans); node.right …

Orange3数据预处理(列选择组件)数据角色及类型描述

在Orange3的文件组件中&#xff0c;datetime、categorical、numeric以及text代表不同种类的数据类型&#xff0c;具体如下&#xff1a; datetime&#xff1a;代表日期和时间类型的数据。通常用于时间序列分析、生存分析和其他需要考虑时间因素的机器学习任务中。例如&#xff0…

英语连读技巧15

1. first one – 第一个 连读听起来就像是&#xff1a;【佛斯湾】 连读的音标为&#xff1a; 例句&#xff1a;I don’t want to be the first one there agin. 发音指导&#xff1a;在“first one”的连读中&#xff0c;"t"和"o"之间的连接几乎消失&a…

17.材质和外观

1.图形学中的材质 在图形学中&#xff0c;材质&#xff08;Material&#xff09;是用来描述物体外观和表面特性的属性集合。它包含了控制光的反射、折射、吸收以及其他光学效果的信息&#xff0c;从而决定了物体在渲染过程中的外观。 渲染方程中那一项和材质有关&#xff1f; …

RDMA内核态函数ib_post_recv()源码分析

接上文&#xff0c;上文分析了内核rdma向发送队列添加发送请求的函数ib_post_send&#xff0c;本文分析一下向接收队列添加接收请求的函数ib_post_recv。其实函数调用流程与上文类似&#xff0c;不再重复说明&#xff0c;可参考链接。 函数调用过程 最终会调用到这个函数 下面…