当前位置:   article > 正文

【微服务】mybatis typehandler使用详解

【微服务】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方法实现的。

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

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

  1. String id=rs.getString("id");
  2. 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如下

  1. CREATE TABLE `tb_user` (
  2. `id` int(12) NOT NULL,
  3. `user_name` varchar(32) DEFAULT NULL,
  4. `pwd` varchar(64) DEFAULT NULL,
  5. `user_image` blob,
  6. `create_time` datetime DEFAULT NULL,
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.2 搭建一个springboot工程

技术栈,springboot+mybatis+mysql

3.2.1 基础依赖如下

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.5.5</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. <dependencies>
  8. <dependency>
  9. <groupId>com.alibaba.fastjson2</groupId>
  10. <artifactId>fastjson2</artifactId>
  11. <version>2.0.23</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-test</artifactId>
  20. <scope>test</scope>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.projectlombok</groupId>
  24. <artifactId>lombok</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.mybatis.spring.boot</groupId>
  28. <artifactId>mybatis-spring-boot-starter</artifactId>
  29. <version>2.2.2</version>
  30. </dependency>
  31. <dependency>
  32. <groupId>com.alibaba</groupId>
  33. <artifactId>druid-spring-boot-starter</artifactId>
  34. <version>1.1.17</version>
  35. </dependency>
  36. <dependency>
  37. <groupId>mysql</groupId>
  38. <artifactId>mysql-connector-java</artifactId>
  39. </dependency>
  40. </dependencies>
  41. <build>
  42. <finalName>hook-service</finalName>
  43. <plugins>
  44. <plugin>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-maven-plugin</artifactId>
  47. </plugin>
  48. </plugins>
  49. </build>

3.2.2 核心配置文件

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

  1. server:
  2. port: 8088
  3. spring:
  4. application:
  5. name: user-service
  6. datasource:
  7. url: jdbc:mysql://IP:3306/db-base
  8. driverClassName: com.mysql.jdbc.Driver
  9. username: root
  10. password: root
  11. mybatis:
  12. mapper-locations: classpath:mapper/*.xml
  13. #目的是为了省略resultType里的代码量
  14. type-aliases-package: com.congge.entity
  15. configuration:
  16. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.2.3 测试接口

  1. @RestController
  2. @RequestMapping("/user")
  3. public class TbUserController {
  4. @Autowired
  5. private TbUserService tbUserService;
  6. //http:localhost:8088/user/get?id=1
  7. @GetMapping("/get")
  8. public TbUser getById(@RequestParam int id){
  9. return tbUserService.getById(id);
  10. }
  11. }

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

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
  3. <mapper namespace="com.congge.dao.TbUserDao">
  4. <resultMap id="baseMap" type="com.congge.entity.TbUser">
  5. <id column="id" jdbcType="INTEGER" property="id" />
  6. <result column="user_name" jdbcType="VARCHAR" property="userName" />
  7. <result column="pwd" jdbcType="VARCHAR" property="pwd" />
  8. <result column="user_image" jdbcType="BLOB" property="userImage" />
  9. <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
  10. </resultMap>
  11. <select id="getById" resultMap="baseMap">
  12. select * from tb_user where id = #{id}
  13. </select>
  14. </mapper>

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

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

四、TypeHandler在项目中的使用

4.1 时间类型转换问题

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

4.1.1 添加自定义TypeHandler

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

  1. import org.apache.ibatis.type.*;
  2. import java.sql.*;
  3. /**
  4. * 自定义类型转换:将数据库中的日期类型,转换成long类型的时间戳
  5. *
  6. * 三种注册方式:
  7. * 1.直接在 result标签中,指定typeHandler,如@Result(property = "createTime", column = "create_time", jdbcType = JdbcType.TIMESTAMP, typeHandler = Timestamp2LongHandler.class)
  8. * 2.在SqlSessionFactory实例中,注册 在SqlSessionFactory实例中.setTypeHandlers(new Timestamp2LongHandler());
  9. * 3.mapper 的xml配置,<typeHandler handler="com.congge.handler.Timestamp2LongHandler"/>
  10. */
  11. @MappedTypes(value = Long.class)
  12. @MappedJdbcTypes(value = {JdbcType.DATE, JdbcType.TIME, JdbcType.TIMESTAMP})
  13. public class Timestamp2LongHandler extends BaseTypeHandler<Long> {
  14. /**
  15. * 将java类型,转换为jdbc类型
  16. *
  17. * @param preparedStatement
  18. * @param i
  19. * @param aLong 毫秒时间戳
  20. * @param jdbcType db字段类型
  21. * @throws SQLException
  22. */
  23. @Override
  24. public void setNonNullParameter(PreparedStatement preparedStatement, int i, Long aLong, JdbcType jdbcType) throws SQLException {
  25. if (jdbcType == JdbcType.DATE) {
  26. preparedStatement.setDate(i, new Date(aLong));
  27. } else if (jdbcType == JdbcType.TIME) {
  28. preparedStatement.setTime(i, new Time(aLong));
  29. } else if (jdbcType == JdbcType.TIMESTAMP) {
  30. preparedStatement.setTimestamp(i, new Timestamp(aLong));
  31. }
  32. }
  33. @Override
  34. public Long getNullableResult(ResultSet resultSet, String s) throws SQLException {
  35. return parse2time(resultSet.getObject(s));
  36. }
  37. @Override
  38. public Long getNullableResult(ResultSet resultSet, int i) throws SQLException {
  39. return parse2time(resultSet.getObject(i));
  40. }
  41. @Override
  42. public Long getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
  43. return parse2time(callableStatement.getObject(i));
  44. }
  45. private Long parse2time(Object value) {
  46. if (value instanceof Date) {
  47. return ((Date) value).getTime();
  48. } else if (value instanceof Time) {
  49. return ((Time) value).getTime();
  50. } else if (value instanceof Timestamp) {
  51. return ((Timestamp) value).getTime();
  52. }
  53. return null;
  54. }
  55. }

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

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

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

4.1.2 添加扫描

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

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

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

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

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

4.1.4 接口效果测试

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

4.1.5 新增数据

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

  1. public int save(TbUser tbUser) {
  2. Integer count = tbUserDao.selectCount();
  3. Integer id = count+1;
  4. tbUser.setId(count+1);
  5. tbUser.setCreateTime(System.currentTimeMillis());
  6. tbUserDao.save(tbUser);
  7. return id;
  8. }

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

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

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

4.2 常用的TypeHandler

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

4.2.1 List<String>类型的处理

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

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

mysql建表语句

  1. CREATE TABLE `biz_user` (
  2. `id` int(12) NOT NULL,
  3. `user_name` varchar(32) DEFAULT NULL,
  4. `relate_roles` varchar(132) DEFAULT NULL,
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

自定义以TypeHandler类,代码如下

  1. import org.apache.ibatis.type.BaseTypeHandler;
  2. import java.sql.PreparedStatement;
  3. import java.util.Arrays;
  4. import java.util.Collections;
  5. import java.util.List;
  6. import org.apache.ibatis.type.*;
  7. import java.sql.*;
  8. //@MappedTypes(value = Long.class)
  9. //@MappedJdbcTypes(value = {JdbcType.DATE, JdbcType.TIME, JdbcType.TIMESTAMP})
  10. public class StringListTypeHandler extends BaseTypeHandler<List<String>> {
  11. private static final String DELIMITER = ",";
  12. @Override
  13. public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
  14. // 将 List<String> 转换为以逗号分隔的字符串
  15. String rolesString = String.join(DELIMITER, parameter);
  16. ps.setString(i, rolesString);
  17. }
  18. @Override
  19. public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
  20. // 从 ResultSet 中获取字符串值
  21. String rolesString = rs.getString(columnName);
  22. // 转换为 List<String>
  23. return parseRolesString(rolesString);
  24. }
  25. @Override
  26. public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
  27. String rolesString = rs.getString(columnIndex);
  28. return parseRolesString(rolesString);
  29. }
  30. @Override
  31. public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
  32. String rolesString = cs.getString(columnIndex);
  33. return parseRolesString(rolesString);
  34. }
  35. private List<String> parseRolesString(String rolesString) {
  36. if (rolesString == null || rolesString.isEmpty()) {
  37. return Collections.emptyList();
  38. }
  39. return Arrays.asList(rolesString.split(DELIMITER));
  40. }
  41. }

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

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

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

4.2.2 枚举类型的处理

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

  1. @Getter
  2. public enum SexEnum {
  3. MALE(1, "男"),
  4. FEMALE(0, "女");
  5. private int id;
  6. private String name;
  7. SexEnum(int id, String name) {
  8. this.id = id;
  9. this.name = name;
  10. }
  11. public static SexEnum getSexById(int id) {
  12. for (SexEnum sex : SexEnum.values()) {
  13. if (sex.getId() == id) {
  14. return sex;
  15. }
  16. }
  17. return null;
  18. }
  19. }

实体类

  1. @Getter
  2. @Setter
  3. public class BizUser {
  4. private int id;
  5. private String userName;
  6. private List<String> relateRoles;
  7. private SexEnum sex;
  8. }

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

  1. public class SexEnumTypeHandler extends BaseTypeHandler<SexEnum> {
  2. @Override
  3. public SexEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
  4. // 从 ResultSet 中获取列值
  5. int data = rs.getInt(columnName);
  6. // 将列值转换为枚举对象
  7. return SexEnum.getSexById(data);
  8. }
  9. @Override
  10. public SexEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
  11. int data = rs.getInt(columnIndex);
  12. return SexEnum.getSexById(data);
  13. }
  14. @Override
  15. public SexEnum getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
  16. int data = callableStatement.getInt(columnIndex);
  17. return SexEnum.getSexById(data);
  18. }
  19. @Override
  20. public void setNonNullParameter(PreparedStatement preparedStatement, int i, SexEnum status, JdbcType jdbcType) throws SQLException {
  21. // 将枚举值的code存储到数据库中
  22. preparedStatement.setInt(i, status.getId());
  23. }
  24. }

4.2.3 json类型的处理

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

  1. @Getter
  2. @Setter
  3. public class BizUser {
  4. private int id;
  5. private String userName;
  6. private List<String> relateRoles;
  7. private SexEnum sex;
  8. private Content content;
  9. }

Content对象

  1. @Getter
  2. @Setter
  3. public class Content {
  4. private String desc;
  5. private String level;
  6. private String hit;
  7. }

对应的数据表如下

  1. CREATE TABLE `biz_user` (
  2. `id` int(12) NOT NULL,
  3. `user_name` varchar(32) DEFAULT NULL,
  4. `relate_roles` varchar(132) DEFAULT NULL,
  5. `gender` int(1) DEFAULT NULL,
  6. `content` json DEFAULT NULL,
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

  1. import com.congge.entity.Content;
  2. import com.google.gson.Gson;
  3. import org.apache.ibatis.type.BaseTypeHandler;
  4. import org.apache.ibatis.type.JdbcType;
  5. import java.sql.CallableStatement;
  6. import java.sql.PreparedStatement;
  7. import java.sql.ResultSet;
  8. import java.sql.SQLException;
  9. public class ObjectJSONTypeHandler extends BaseTypeHandler<Content> {
  10. private Gson gson = new Gson();
  11. @Override
  12. public void setNonNullParameter(PreparedStatement ps, int i, Content parameter, JdbcType jdbcType) throws SQLException {
  13. ps.setString(i, gson.toJson(parameter));
  14. }
  15. @Override
  16. public Content getNullableResult(ResultSet rs, String columnName) throws SQLException {
  17. return gson.fromJson(rs.getString(columnName), Content.class);
  18. }
  19. @Override
  20. public Content getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
  21. return gson.fromJson(rs.getString(columnIndex), Content.class);
  22. }
  23. @Override
  24. public Content getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
  25. return gson.fromJson(cs.getString(columnIndex), Content.class);
  26. }
  27. }

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

  1. <resultMap id="baseMap" type="com.congge.entity.BizUser">
  2. <id column="id" jdbcType="INTEGER" property="id" />
  3. <result column="user_name" jdbcType="VARCHAR" property="userName" />
  4. <result column="relate_roles" jdbcType="VARCHAR" property="relateRoles" typeHandler="com.congge.handler.StringListTypeHandler" />
  5. <result column="gender" jdbcType="INTEGER" property="sex" typeHandler="com.congge.handler.SexEnumTypeHandler" />
  6. <result column="content" property="content" typeHandler="com.congge.handler.ObjectJSONTypeHandler" />
  7. </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在实际的开发中可以说是一个很常见的问题,虽然并不难,但是如果不了解其底层原理得话,使用起来很容易陷入忙乱中,有必要深入学习和掌握,本篇到此结束,感谢观看。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/172185
推荐阅读
相关标签
  

闽ICP备14008679号