MyBatis Plus:自定义typeHandler类型处理器

目录

引言:关于TypeHandler

PostGreSQL:JSON数据类型

PostGreSQL数据库驱动:PGobject类

TypeHandler类型处理器

自定义类型处理器

类型处理器实现:PGJsonTypeHandler

注册类型处理器


引言:关于TypeHandler

MyBatis Plus提供了丰富的类型处理器TypeHandler,用于实现用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值。

但是,内置的类型处理器在某些特殊情况难以满足实际开发需求。以下,以PostGreSQL数据库中的JSON类型为例,叙述自定义类型处理器,实现java.util.Map与PostGreSQL中JSON类型之间的转换。

PostGreSQL:JSON数据类型

使用SQL语句构建JSON类型字段值是十分简单的,

sql 复制代码
-- 简单标量/基本值
-- 基本值可以是数字、带引号的字符串、true、false或者null
SELECT '5'::json;

-- 有零个或者更多元素的数组(元素不需要为同一类型)
SELECT '[1, 2, "foo", null]'::json;

-- 包含键值对的对象
-- 注意对象键必须总是带引号的字符串
SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json;

-- 数组和对象可以被任意嵌套
SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json;

问题在于:如何将java中的Map类型转换为PG数据库中的JSON字段值。

PostGreSQL数据库驱动:PGobject类

在PostGreSQL数据库驱动依赖包的org.postgresql.util工具包下,挖出来了一个名称为:PGobject的类,

该类(PGobject)的源码描述信息如下:

PGobject is a class used to describe unknown types An unknown type is any type that is unknown by JDBC Standards.

概略含义:PGobject是一个用于描述一种非JDBC标准的未知类型。

可以看到,它内置了很多子类,用于描述PostGreSQL中的Line、Polygon、Money等数据类型。

一通分析之后,发现PGobject类的type、value属性是比较重要的(毕竟只提供了这两个属性),

查看源码之后,解读type和value字段的含义:

type:object对象的类型,

value:object对象的值。

再拿来和PGpolygon子类做一下对比,会发现:type属性其实就是一个String字符串,用于描述PostGreSQL内置数据类型的名称;value则是具体的值。那么,我们也可以借助这个PGobject类来描述自己的类型。

TypeHandler类型处理器

TypeHandler类型处理器是MyBatis/MyBatisPlus很重要的一部分内容,日常开发中,从数据表到JavaBean,从JavaBean到数据表,两者之间的转换全靠这些内置的TypeHandler来实现。

查看TypeHandler的源码,内容如下,

java 复制代码
/**
 * @author Clinton Begin
 */
public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @return the result
   * @throws SQLException
   *           the SQL exception
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

可以分为两类,其中:

①setParameter:是将JavaBean属性值转换为PostGreSQL支持的数据库字段值;

②3个getResult方法:熟悉原生JDBC编程的朋友应该都很眼熟,其实就是通过PreparedStatement执行完SQL之后,对ResultSet结果集进行按字段名columnName、按索引下标columnIndex,将PostGreSQL支持的数据库字段值,转换为JavaBean属性值的过程。

自定义类型处理器

了解了上述内容之后,我们要自定义类型处理器,核心的工作就是:对TypeHandler接口提供的三个方式进行实现。

类型处理器实现:PGJsonTypeHandler

以下给出一个Map集合类型到PostGreSQL-JSON数据类型之间的转换器实现类,相关部分可以直接查看代码注释,

java 复制代码
/**
 * TypeHandler:类型处理器,用于 JavaType 与 JdbcType 之间的转换
 *  [1] 用于 PreparedStatement 设置参数值
 *  [2] 用于从 ResultSet CallableStatement 中取出一个值
 * PGJsonTypeHandler:处理Map集合类型与postgresql中JSON类型之间的转换
 */

@MappedTypes(value = {Object.class})
public class PGJsonTypeHandler<T> extends BaseTypeHandler<T> {

    //PGobject:PGobject is a class used to describe unknown types An unknown type is any type that is unknown by JDBC Standards.
    private static final PGobject pgObject = new PGobject();

    /**
     * 插入时设置参数类型
     * @param preparedStatement SQL预编译对象
     * @param i 需要赋值的索引位置(相当于在JDBC中对占位符的位置进行赋值)
     * @param parameter 索引位置i需要赋的值(原本要给这个位置赋的值,在setNonNullParameter方法中主要解决的问题就是将这个自定义类型变成数据库认识的类型)
     * @param jdbcType jdbc的类型
     * @throws SQLException
     */
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (preparedStatement != null) {
            System.out.println("setNonNullParameter::"+parameter.toString());
            pgObject.setType("json");
            pgObject.setValue(JSON.toJSONString(parameter));
            preparedStatement.setObject(i, pgObject);
        }
    }

    /**
     * 转换为T类型实例
     * @param object 参数字符串({"name":"张三","age":18,"address":"北京市朝阳区"})或者null空值
     * @return T实例或者参数
     */
    private T parseAsTInstance(Object object){
        //判空
        if(object == null){
            return null;
        }
        //处理字符串{"name":"张三","age":18,"address":"北京市朝阳区"}-转换为Map类型
        Map map = JSON.parseObject(object.toString(), Map.class);
        return (T)map;
    }

    /**
     * 获取时转换回的自定义类型
     * @param resultSet 结果集
     * @param s 列名称
     * @return Bean对象
     */
    @Override
    public T getNullableResult(ResultSet resultSet, String s) throws SQLException {
        Object object = resultSet.getObject(s);
        return parseAsTInstance(object);
    }

    /**
     * 获取时转换回的自定义类型
     * @param resultSet 结果集
     * @param i 列索引
     */
    @Override
    public T getNullableResult(ResultSet resultSet, int i) throws SQLException {
        Object object = resultSet.getObject(i);
        return parseAsTInstance(object);
    }

    /**
     * 获取时转换回的自定义类型
     * @param callableStatement 结果集
     * @param i 列索引
     */
    @Override
    public T getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        Object object = callableStatement.getObject(i);
        return parseAsTInstance(object);
    }
}

注册类型处理器

要注册类型处理器,可以进行全局配置:typeHandlersPackage属性

也可以在局部直接使用,例如:

java 复制代码
package com.example.soil_backend.model.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;

import java.time.LocalDateTime;

import com.baomidou.mybatisplus.annotation.TableId;

import java.io.Serializable;
import java.util.Map;

import com.example.soil_backend.model.TbBase;
import com.example.soil_backend.typehandler.PGJsonbTypeHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 配置模板
 *
 * @author XiMumu
 * @since 2024-02-18
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName(value = "tb_template", autoResultMap = true)
public class TbTemplate extends TbBase implements Serializable {
    private static final long serialVersionUID = -2288822970791637243L;
    /**
     * 主键ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 调查模板名称
     */
    @TableField(value = "name")
    private String name;

    /**
     * 模板配置JSON
     */
    @TableField(value = "config",typeHandler = PGJsonbTypeHandler.class)
    private Map config;

    /**
     * 是否删除:1-使用中;2-已删除
     */
    @TableField(value = "del_flag")
    private Integer delFlag;


}
相关推荐
jokerest12312 小时前
web——sqliabs靶场——第十三关——报错注入+布尔盲注
mybatis
武子康12 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
WindFutrue15 小时前
使用Mybatis向Mysql中的插入Point类型的数据全方位解析
数据库·mysql·mybatis
AiFlutter16 小时前
Java实现简单的搜索引擎
java·搜索引擎·mybatis
天天扭码19 小时前
五天SpringCloud计划——DAY1之mybatis-plus的使用
java·spring cloud·mybatis
武子康2 天前
Java-05 深入浅出 MyBatis - 配置深入 动态 SQL 参数、循环、片段
java·sql·设计模式·架构·mybatis·代理模式
2的n次方_2 天前
MyBatis——#{} 和 ${} 的区别和动态 SQL
数据库·sql·mybatis
jokerest1232 天前
web——sqliabs靶场——第十二关——(基于错误的双引号 POST 型字符型变形的注入)
数据库·sql·mybatis
天蓝蓝235282 天前
Lucene数据写入流程
java·mybatis·lucene