【解决方案】基于数据库驱动的自定义 TypeHandler 处理器

news2024/10/9 16:04:36

前言

笔者在最近的项目开发中,频繁地遇到了 Java 类型与 JDBC 类型之间的2个转换问题:

  • 数据库的 varchar 类型字段,需要存储 Java 实体中的 JSON 字符串

  • 数据库的 int 类型字段,需要存储 Java 实体中的 Enum 枚举

其实要处理也不麻烦,可以在每次入库地方的手动将 Java Bean 调用 JSON.toJSONString() 即可,取出数据库数据的时候再 JSON.parseObject()解析。再说处理枚举类型也并不难,无非就是手动将枚举的 int 型属性取出后 set 到数据库的int中去。

而本文要介绍的自定义 TypeHandler 处理器的作用,就是自动处理 Java Bean 与数据库类型的转换,提高编码效率,通过全局的统一处理省去繁琐的手动转换。


一、TypeHandler 简介

如果我们使用的是 Mybatis 或者是 Mybatis Plus 的话,在 SQL 语句执行过程中,无论是设置参数还是获取结果集,都需要通过 TypeHandler 进行类型转换。

MyBatis 提供了丰富的内置 TypeHandler 实现,以支持常见的数据类型转换,如以下几种:

表1-1

1.1转换步骤

当 MyBatis 执行一个预编译的 SQL 语句(如 INSERT、UPDATE 等)时,它需要将 Java 对象中的属性值设置到 SQL 语句中对应的占位符上。这个过程就是通过TypeHandler 来实现的。

具体步骤如下:

  • MyBatis 会根据映射配置找到对应的 TypeHandle r实例,这个映射配置可以在 MyBatis 的配置文件或者 Mapper 的 XML 文件中定义;

  • TypeHandler 实例会接收到 Java 对象中的属性值,并将其转换为 JDBC 能够识别的类型,这个转换过程是根据两者之间的映射关系来实现的;

  • 转换后的值会被设置到 PreparedStatement 对象中对应的占位符上,以便数据库能够正确解析和执行 SQL 语句。

1.2转换规则

再次强调,TypeHandler 的核心功能是实现 Java 类型和 JDBC 类型之间的映射和转换,这个映射和转换规则是根据 Java 类型和 JDBC 类型的特性和语义来定义的。

  • 对于基本数据类型(如 int、long、float等),MyBatis 提供了内置的 TypeHandler 实现,这些实现能够直接将 Java 基本数据类型转换为对应的 JDBC 基本数据类型,反之亦然。

  • 对于复杂数据类型(如自定义对象、集合等),MyBatis 允许开发者自定义 TypeHandler 来实现复杂的类型转换逻辑。例如,开发者可以定义一个自定义的TypeHandler 来将数据库中的 JSON 字符串转换为 Java 中的对象,或者将 Java 对象转换为 JSON 字符串存储到数据库中。

下面两章就举两个例子来加以说明。


二、JSON 转换

应用的 .yml 配置文件新增以下:

mybatis-plus:
  type-handlers-package: #自定义 handler 类所在的包路径

/**
 * <p>作用:即 Java 实体属性可以直接使用 JSONObject 映射数据库的 varchar,方便入库、出库</p>
 * <p>注意:需要在 .yml 配置文件上加上 {@code mybatis:type-handlers-package: 本类所在包路径}</p>
 *
 * @param <T> 该泛型即为需要转换成 varchar 的 Java 对象
 * @MappedTypes 注解很关键,指定了映射的类型
 */
@MappedTypes({JSONObject.class, JSONArray.class})
public class JSONTypeHandler <T> extends BaseTypeHandler<T> {

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, T param, JdbcType jdbcType) throws SQLException {
        //将指定的参数设置为给定的 Java String 值,数据库驱动程序及其转换成 varchar 类型
        preparedStatement.setString(i, JSON.toJSONString(param));

    }

    @Override
    public T getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        //这里根据字段名去拿到之前放进来的 jsonStr 值
        String jsonStr = resultSet.getString(columnName);
        return StringUtils.isNotBlank(jsonStr) ? JSON.parseObject(jsonStr, getRawType()) : null;
    }

    @Override
    public T getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
        //这里是根据位置来确定字段,进而拿到该字段的值(之前放进来的 jsonStr)
        String jsonStr = resultSet.getString(columnIndex);
        return StringUtils.isNotBlank(jsonStr) ? JSON.parseObject(jsonStr, getRawType()) : null;
    }

    @Override
    public T getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        //这里是根据SQL存储过程里的字段位置来拿字段的值
        String jsonStr = callableStatement.getString(columnIndex);
        return StringUtils.isNotBlank(jsonStr) ? JSON.parseObject(jsonStr, getRawType()) : null;
    }

}

三、枚举转换

/**
 * <p>作用:将实体类中的枚举 code 映射为数据库的 int</p>
 * <p>注意:需要在 .yml 配置文件上加上 {@code mybatis:type-handlers-package: 本类所在包路径}</p>
 *
 * @param <E> 该泛型即为需要处理的枚举对象,使用上界通配符来保证类型安全
 */
@MappedTypes(MyEnum.class)
public class EnumCodeTypeHandler <E extends MyEnum> extends BaseTypeHandler<E> {

    private final Class<E> type;

    /**
     * 记录枚举值和枚举的对应关系
     */
    private final Map<Integer, E> enumMap = new ConcurrentHashMap<>();

    public EnumCodeTypeHandler(Class<E> type) {
        Assert.notNull(type, "argument cannot be null");
        this.type = type;
        E[] enums = type.getEnumConstants();
        if (Objects.nonNull(enums)) {
            //这里将枚举值和枚举类型存入 enumMap
            for (E e : enums) {
                this.enumMap.put(e.toCode(), e);
            }
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int index, E e, JdbcType jdbcType) throws SQLException {
        //这里将枚举的 code 转为数据库该字段的 int 类型
        preparedStatement.setInt(index, e.toCode());
    }

    @Override
    public E getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        //这里根据字段名来将数据库的 int 转为 Java 的 Integer
        Integer code = resultSet.getInt(columnName);
        if (resultSet.wasNull()){
            return null;
        }else {
            //取出对应的枚举值
            return enumMap.get(code);
        }
    }

    @Override
    public E getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
        //这里根据字段位置来将数据库的 int 转为 Java 的 Integer
        Integer code = resultSet.getInt(columnIndex);
        if (resultSet.wasNull()){
            return null;
        }else {
            //取出对应的枚举值
            return enumMap.get(code);
        }
    }

    @Override
    public E getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        //这里根据SQL存储过程里的字段位置将字段的 int 转为 Java 的 Integer
        Integer code = callableStatement.getInt(columnIndex);
        if (callableStatement.wasNull()){
            return null;
        }else {
            //取出对应的枚举值
            return enumMap.get(code);
        }
    }

}

/**
 * <p>作用:该接口包含了两个枚举操作的抽象方法</p>
 */
public interface MyEnum {

    /**
     * 根据 code 获取枚举实例
     * @param code
     */
    MyEnum fromCode(int code);

    /**
     * 获取枚举中的 code
     */
    int toCode();

}

@Getter
@RequiredArgsConstructor
public enum StudyStatusEnum implements MyEnum{
    ONE(1, "枚举1"),
    TWO(2, 枚举2"),
    THREE(3, "枚举3"),
    FOUR(4, "枚举4"),
    FIVE(5, "枚举5");

    private final Integer code;

    private final String desc;

    /**
     * 根据 code 获取枚举实例
     */
    @Override
    public MyEnum fromCode(int code) {
        return Arrays.stream(StudyStatusEnum.values())
                .filter(val -> val.getCode().equals(code))
                .findFirst().orElse(null);
    }

    /**
     * 获取枚举中的 code
     */
    @Override
    public int toCode() {
        return this.getCode();
    }

}

四、文章小结

通过内置和自定义的 TypeHandler,我们可以轻松处理各种数据类型转换需求,提高开发效率和代码可维护性。

在 Spring Boot 环境中使用自定义 TypeHandler 更是简化了配置和注册过程,使得我们能够更专注于业务逻辑的实现。

最后,文章如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!

文章转载自:CodeBlogMan

原文链接:https://www.cnblogs.com/CodeBlogMan/p/18353997

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库软题6.2-关系模式-范式

一、判断部分函数依赖&#xff08;1NF有部分函数依赖&#xff09; 题型&#xff1a;给出函数依赖集和属性&#xff0c;判断该关系模式属于第几范式。 求出候选码 若是候选码为属性的组合&#xff0c;则可能有部分函数依赖&#xff1b;&#xff08;存在部分函数依赖&#xff0…

苹果AI重磅升级!ChatGPT正式登陆iPhone,开启智能生活新时代

苹果AI重磅升级&#xff01;ChatGPT正式登陆iPhone&#xff0c;开启智能生活新时代 在今年的全球开发者大会&#xff08;WWDC 2024&#xff09;上&#xff0c;苹果公司引爆了科技圈——通过与OpenAI合作&#xff0c;苹果宣布将人工智能技术与iPhone深度融合。通过这个合作&…

自由学习记录(2)

Unity打包图集相关 Draw Call 实验设置&#xff1a; 我们将创建两个场景&#xff0c;一个场景有高 Draw Call&#xff0c;另一个场景通过优化减少 Draw Call。然后对比它们的帧率&#xff08;FPS&#xff09;。 场景 1&#xff1a;高 Draw Call 场景&#xff08;无优化&…

浙大数据结构:07-图5 Saving James Bond - Hard Version

这道题也是很有难度&#xff0c;我最开始尝试用Dijkstra来做&#xff0c;发现不是很好处理&#xff0c;用bfs还不错。 机翻&#xff1a; 1、条件准备 n为鳄鱼数量&#xff0c;jump为跳跃距离&#xff0c;headjump为第一次跳跃距离&#xff0c;包括了岛的半径。 isalive标识…

求职书与求职经历 - Chap01.

节前定点在智联投了几家&#xff0c;智联上之前的简历还在&#xff0c;稍稍维护了一下&#xff0c;现在有两三家再看看。然后节后&#xff0c;今天&#xff0c;注册了职友网的7天会员。正在整理简历。 专利证书&#xff0c;通过soopat查&#xff0c;很不友好。国家产权局后来直…

docker部署langfuse 本地

下载langfuse地址:https://api.github.com/repos/langfuse/langfuse/tarball/v2.43.2 tips&#xff1a;这里有的博主说需要clone到本地&#xff0c;但是我的docker clone langfuse到本地使用docker安装页面提示如下错误: 最后使用下载地址 上传到服务器后 解压再安装 反正没有…

PIFA天线工作原理:【图文讲解】

1&#xff1a;什么是PIFA天线 PIFA ( Planar Inverted F-shaped Antenna)天线即平面倒F形天线&#xff0c;因为整个天线的形状像个倒写的英文字母F而得名 2&#xff1a;PIFA天线的应用 PIFA常见于手机天线设计&#xff0c;占手机内置天线的60%-80% 3&#xff1a;PIFA天线结构…

5.错误处理在存储过程中的重要性(5/10)

错误处理在存储过程中的重要性 引言 在数据库编程中&#xff0c;存储过程是一种重要的组件&#xff0c;它允许用户将一系列SQL语句封装成一个单元&#xff0c;以便重用和简化数据库操作。然而&#xff0c;像任何编程任务一样&#xff0c;存储过程中的代码可能会遇到错误或异常…

封装vue-cropper,图片裁剪组件

组件基本使用: 这里的action同时也可以传相对路径&#xff0c;比如封装了axios&#xff0c;那么组件源码里就不需要引入原生axios&#xff0c;可以替换为封装的axios。传 action"/upload/file" 源代码&#xff1a; <script setup> import WuyuCropper from /c…

PyTorch单机多卡训练(无废话)

目前大家基本都在使用DistributedDataParallel&#xff08;简称DDP&#xff09;用来训练&#xff0c;该方法主要用于分布式训练&#xff0c;但也可以用在单机多卡。 第一步&#xff1a;初始化分布式环境&#xff0c;主要用来帮助进程间通信 torch.distributed.init_process_g…

Notepad-- 程序员日常用法

一、快速查找标记&#xff0c;提取指定的字符串 1.使用正则表达式查找并标记&#xff0c;将标记的内容复制到新文本中 标记中输入&#xff1a;.*"staffNo": "([^"])".* 这个正则表达式可以匹配整个行&#xff0c;并将 staffNo 后面的内容标记出来。…

修复WordPress .htaccess文件中的常见问题

在搭建WordPress网站时&#xff0c;.htaccess文件非常重要。它可以帮助我们管理网站的重定向、优化URL结构、提高网站安全性等。然而&#xff0c;有时可能会出现由.htaccess文件引起的问题&#xff0c;比如500内部服务器错误、重定向次数过多和文章返回404错误等。本文将详细介…

Hadoop---概念篇

一、Hadoop的组成 二、HDFS架构概述 **1.NameNode(nn): **存储文件的元数据,例如:文件名、文件目录结构、文件属性(生成时间、副本数、文件权限)以及每个文件的块列表和块所在的DateNode等。 **2.DateNode(dn): **在本地文件系统中存储的文件块数据,以及块数据的校验和。 **3.…

后端必备技巧之SQL优化

日常开发中&#xff0c;几乎都免不了和数据库打交道&#xff0c;说到数据库&#xff0c;我们也需要联想这几个问题&#xff1a;我们写的SQL性能怎么样&#xff1f;有没有优化的空间&#xff1f;如何优化&#xff1f;下面我们来介绍关于SQL性能的命令EXPLAIN 什么是EXPLAIN命令&…

智融SW3536DC/DC+快充协议二合一IC

描述 SW3536 是一款高集成度的多快充协议双口充电芯片&#xff0c;支持 AC 口任意口快充输出&#xff0c;支持双口独立限流。其集成了 7A 高效率同步降压变换器&#xff0c;支持 PPS/ PD/ QC/ AFC/ FCP/ SCP/ PE/ SFCP/TFCP 等多种快充协议&#xff0c;支持 140W 输出功率&…

SGDC复位约束找不到信号问题

在使用spyglass编写sgdc时&#xff0c;对一个内部复位信号约束的时候&#xff0c;报信号找不到。 reset -name TOP.BLOCK3.U_TOP2.BLOCK1.U_TOP1.rst -value 0 但是明明get_pins 可以找到此信号。 由于存在generate命令&#xff0c;导致生成的路径比较奇怪&#xff0c;甚至存…

.NET 一款提权工具:Sharp4PetitPotato

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

深入浅出MongoDB(六)

深入浅出MongoDB&#xff08;六&#xff09; 文章目录 深入浅出MongoDB&#xff08;六&#xff09;分析查询性能原子性和事务字段名称带句点和美元符号查询计划 分析查询性能 mongodb compass提供解释计划标签页&#xff0c;其中显示有关查询性能的统计信息。这些统计信息可用…

简历修订与求职经历 - Chap02.

最新的简历&#xff1a; 1.基本信息 姓名 ---- 学历 学位 本科 理学学士 专业 应用物理 智能仪器仪表 性别 男 出生年月 1976/7 电话 ---- 年龄 48 毕业时间 1998/6 电邮 ---- 籍贯 河南洛阳宜阳 居住地 河南郑州高新区 1.1 期望从事职业信息 机械仪器…

泡沫背后:人工智能的虚幻与现实

人工智能的盛世与泡沫 现今&#xff0c;人工智能热潮席卷科技行业&#xff0c;投资者、创业者和用户都被其光环吸引。然而&#xff0c;深入探讨这种现象&#xff0c;人工智能的泡沫正在形成&#xff0c;乃至具备崩溃的潜质。我们看到的&#xff0c;无非是一场由资本推动的狂欢…