MyBatis自定义TypeHandler如何解决字段映射问题

 更新时间:2023年12月06日 09:01:38   作者:串一串cc  
这篇文章主要介绍了MyBatis自定义TypeHandler如何解决字段映射问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

MyBatis自定义TypeHandler字段映射

小林子:串哥

串一串:干哈啊,又来

小林子:如果MySQL一张表中一个字段存储的数据格式是"1,2,3,4,5",也就是逗号分隔的,我如何能让别的使用者在无感知的情况下,只用List<Integer>来传输和接收?持久层用的MyBatis。你滴明白我的意思吗?

串一串:不明白

小林子:…

串一串:你知道MyBatis中有一个类叫BaseTypeHandler吗?这个类可以满足你的需求。

小林子:具体要怎么做?我有点懵,没接触过这个类,它是干嘛的?

串一串:我们来看个例子

创建一张表待用

create table qfant_message.demo (
	id int auto_increment primary key,
	name varchar(10) null,
	hobbies varchar(100) null
);

然后新建一个SpringBoot工程,在工程中引入mybatis-generator,我们使用它来生成Mapper文件,如果不会的话,自行谷歌,这里不做详细讲解,下一篇再说。

在生成Mapper文件之前,我们先定义一个处理字段hobbiesTypeHandler,命名为ListTypeHandler,这里问个问题:为什么不叫HobbiesTypeHandler呢?这样应该和字段更加贴合啊。

原因是这个Handler不仅仅是能处理hobbies,它可以处理所有相同情况的任何表的任何字段。

这个类继承自org.apache.ibatis.type.BaseTypeHandler

看下简化后的内容:

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      ps.setNull(i, jdbcType.TYPE_CODE);
    } else {
      setNonNullParameter(ps, i, parameter, jdbcType);
    }
  }

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    return getNullableResult(rs, columnName);
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    return getNullableResult(rs, columnIndex);
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    return getNullableResult(cs, columnIndex);
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

我们可以看到,不论是通过哪一个getResult方法获取数据,都是去调用下面的几个抽象方法,MyBatis帮我们实现了很多常用的类型的Handler,都在org.apache.ibatis.type包里面,截图看下吧,免得以为在忽悠你

小林子:那这里面有没有能满足我这个需求的Handler?如果有的话我就直接用了

串一串:你去看看,这里我说一下怎么重复造轮子

根据上述内容,我们就可以来写ListTypeHandler了,在写之前先整理一下思路:

因为我们实体类中hobbies属性是java.util.List类型的,而数据库表中hobbies字段是varchar类型的,所以我们需要在更新(插入)之前和查询之后对数据进行一次转换

  • 插入之前:将List中的数据转换为以逗号分隔的字符串
  • 查询之后:将逗号分隔的字符串转换为List结构

思路理顺了

我们来看看具体的代码

package cc.kevinlu.handler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
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 com.qfant.sms.data.model.DemoDO;

@MappedJdbcTypes(value = { JdbcType.VARCHAR })
//① @MappedTypes(value = DemoDO.class)
public class ListTypeHandler extends BaseTypeHandler<List<Integer>> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<Integer> parameter, JdbcType jdbcType)
            throws SQLException {
        String d = parameter.stream().map(v -> String.valueOf(v)).collect(Collectors.joining(","));
        ps.setString(i, d);
    }

    @Override
    public List<Integer> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String values = rs.getString(columnName);
        return getResults(values);
    }

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

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

    private List<Integer> getResults(String values) {
        if (StringUtils.isNotBlank(values)) {
            String[] data = values.split(",");
            return Arrays.stream(data).mapToInt(v -> Integer.parseInt(v)).boxed().collect(Collectors.toList());
        }
        return new ArrayList<>();
    }
}

然后生成对应的Mapper和DO实体类,刚才说了我们使用的是mybatis-generator,这里直接贴上<table>的相关配置

<table tableName="demo" domainObjectName="DemoDO" mapperName="DemoMapper"
       enableCountByExample="true"
       enableDeleteByExample="true" enableInsert="true" enableSelectByExample="true"
       enableUpdateByExample="true"
       selectByExampleQueryId="true" enableSelectByPrimaryKey="true">
  <generatedKey column="id" sqlStatement="MySql" identity="true"/>
  <columnOverride column="hobbies" property="hobbies" jdbcType="VARCHAR" javaType="java.util.List"
                  typeHandler="cc.kevinlu.handler.ListTypeHandler"/>
</table>

注意这里我们使用标签<columnOverride>重写了column的定义,这里一定要指明javaTypetypeHandlerjavaType的目的是让生成的DemoDO的属性hobbies声明为java.util.List,如果不加该字段的话,默认会根据jdbcType="VARCHAR"生成java.lang.String类型,然后typeHandler指向我们刚创建的ListTypeHandler,这样在生成DemoMapper.xml的时候,会在对应的字段上加上typeHandler,否则需要我们挨个儿位置的去修改,xml中的内容如下:

<insert id="insert" parameterType="cc.kevinlu.data.model.DemoDO">
  <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
    SELECT LAST_INSERT_ID()
  </selectKey>
  insert into demo (name, hobbies)
  values (
  		#{name,jdbcType=VARCHAR},
  		#{hobbies,jdbcType=VARCHAR,typeHandler=com.qfant.sms.handler.ListTypeHandler}
  )
</insert>

小林子:是不是这样就可以直接使用了?

串一串:你有没有注意到ListTypeHandler上有一个被注释掉的注解,把那个注释打开,然后value指向DO实体类即可,这个注释的意思是指定该Handler映射的java类,value是一个数组,可以指定一组映射类,当然也可以不指定。即使指定了,也可以用于其他类型,然后@MappedJdbcTypes映射的是jdbc的类型

小林子:那现在是不是可以测试啦?走一波~

@Resource
private DemoMapper demoMapper;

@Test
public void index() {
  List<DemoDO> data = demoMapper.selectByExample(new DemoDOExample());
  data.forEach(System.out::println);
}

输出:

Demo1DO [Hash = 3112387, id=1, name=123, hobbies=[1, 2, 3], serialVersionUID=1]
Demo1DO [Hash = 3294608, id=2, name=456, hobbies=[4, 5, 6], serialVersionUID=1]

总结一下

1.MyBatis之所以能解决MySQL字段和Java属性之间的匹配,全都依赖于org.apache.ibatis.type.BaseTypeHandler<T>抽象类,在该类中定义了3个获取结果的方法、1个更新的方法和4个抽象方法,我们可以自定义该抽象类来实现这个4个抽象方法进行Java类的属性和表字段的映射,可以做一些相关的处理。

2.MyBatis在org.apache.ibatis.type包中定义了常用的字段映射Handler,并且在服务启动的时候会在TypeHandlerRegistry构造方法中将其注册到一个Map中,而TypeHandlerRegistry是在MyBatis的核心类Configuration中进行的实例化

3.自定义的Handler可以全局通用,不受限于某一个字段或某一个Java类

4.在生成Mapper时使用<columnOverride>重写column声明,然后需要指定jdbcTypetypeHandler

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 详解Java如何通过Socket实现查询IP

    详解Java如何通过Socket实现查询IP

    在本文中,我们来学习下如何找到连接到服务器的客户端计算机的IP地址。我们将创建一个简单的客户端-服务器场景,让我们探索用于TCP/IP通信的java.net API,感兴趣的可以了解一下
    2022-10-10
  • Mybatis-Plus查询投影与查询条件设置过程

    Mybatis-Plus查询投影与查询条件设置过程

    文章介绍了查询投影、聚合查询、分组查询、分页查询、排序查询和查询条件设置等内容,涵盖了如何使用MyBatis Plus(MP)进行各种查询操作,包括lambda表达式、范围匹配、模糊匹配、空判定、包含性匹配、分组、排序等
    2025-11-11
  • IDEA创建Spring Boot Web项目完整图文教程

    IDEA创建Spring Boot Web项目完整图文教程

    在软件开发的浩瀚海洋中,SpringBoot以其独特的魅力和强大的功能,为开发者开辟了一条通往高效、便捷开发之路,这篇文章主要介绍了IDEA创建Spring Boot Web项目的相关资料,需要的朋友可以参考下
    2026-04-04
  • java中List对象列表实现去重或取出及排序的方法

    java中List对象列表实现去重或取出及排序的方法

    这篇文章主要介绍了关于java中List对象列表实现去重或取出以及排序的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面跟着小编来一起学习学习吧。
    2017-08-08
  • SpringBoot面试突击之过滤器和拦截器区别详解

    SpringBoot面试突击之过滤器和拦截器区别详解

    过滤器(Filter)和拦截器(Interceptor)都是基于 AOP(Aspect Oriented Programming,面向切面编程)思想实现的,用来解决项目中某一类问题的两种“工具”,但二者有着明显的差距,接下来我们一起来看
    2022-10-10
  • Java中Json与List、Map、entity的互相转化

    Java中Json与List、Map、entity的互相转化

    在开发中,Json转换的场景往往也就是那么几个,本文主要介绍了Java中Json与List、Map、entity的互相转化,具有一定的参考价值,感兴趣的可以了解一下
    2022-07-07
  • Applet小应用程序开发简介

    Applet小应用程序开发简介

    Applet小应用程序开发简介 ,用java开发的小程序,需要的朋友可以参考下
    2012-09-09
  • 升级springboot3之自动配置导入失效问题及解决

    升级springboot3之自动配置导入失效问题及解决

    这篇文章主要介绍了升级springboot3之自动配置导入失效问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • mybatis中使用大于小于等于的正确方法

    mybatis中使用大于小于等于的正确方法

    在mybatis中sql是写在xml映射文件中的,如果sql中有一些特殊字符的话,在解析xml文件的时候就会被转义,下面我们就一起来看一下大于小于等于是怎么转义的
    2021-04-04
  • Java全面细致讲解==和equals的使用

    Java全面细致讲解==和equals的使用

    这篇文章主要介绍了Java中==和equals()的区别,,==可以使用在基本数据类型变量和引用数据类型变量中,equals()是方法,只能用于引用数据类型,需要的朋友可以参考下
    2022-05-05

最新评论