前言

笔者在最近的项目开发中,频繁地遇到了 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 更是简化了配置和注册过程,使得我们能够更专注于业务逻辑的实现。

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

【解决方案】基于数据库驱动的自定义 TypeHandler 处理器的更多相关文章

  1. atitit. java queue 队列体系and自定义基于数据库的队列总结o7t

    atitit. java queue 队列体系and自定义基于数据库的队列总结o7t 1. 阻塞队列和非阻塞队列 1 2. java.util.Queue接口, 1 3. ConcurrentLink ...

  2. ThinkCMF项目部署出现无法加载数据库驱动解决方案

    最近有个TP项目刚从从本地部署到阿里云服务器上,出现了无法加载数据库驱动的错误,提示 :( 无法加载数据库驱动: Think\Db\Driver 这里分享一下出现该错误的解决步骤: 首先记得项目部署到 ...

  3. 在IDEA中已经配置postgis数据库驱动并且能在Java类中连接数据库,但在servlet中无法连接数据库且导致Tomcat自动断开连接的解决方案

    最近在IDEA中用JDBC连接PostgreSQL数据库时遇到了这样一个奇怪的事情: 从PostgreSQL JDBC Driver官网下载好JDBC驱动之后,在IDEA的Project Struct ...

  4. MyBatis使用自定义TypeHandler转换类型的实现方法

    From: http://www.manongjc.com/article/15577.html 这篇文章主要介绍了MyBatis使用自定义TypeHandler转换类型的实现方法,本文介绍使用Typ ...

  5. MyBatis使用自定义TypeHandler转换类型

    MyBatis虽然有很好的SQL执行性能,但毕竟不是完整的ORM框架,不同的数据库之间SQL执行还是有差异. 笔者最近在升级 Oracle 驱动至 ojdbc 7 ,就发现了处理DATE类型存在问题. ...

  6. 【tornado】系列项目(二)基于领域驱动模型的区域后台管理+前端easyui实现

    本项目是一个系列项目,最终的目的是开发出一个类似京东商城的网站.本文主要介绍后台管理中的区域管理,以及前端基于easyui插件的使用.本次增删改查因数据量少,因此采用模态对话框方式进行,关于数据量大采 ...

  7. 基于数据库的自动化生成工具,自动生成JavaBean、自动生成数据库文档等(v4.1.2版)

            目录:            第1版:http://blog.csdn.net/vipbooks/article/details/51912143            第2版:htt ...

  8. SQL SERVER 基于数据库镜像的主从同步(数据库镜像实践汇总)

    SQL SERVER 基于数据库镜像的主从同步 Author:chaoqun.guo    createtime:2019-03-26 目录 SQL SERVER 基于数据库镜像的主从同步... 1 ...

  9. jdbc 加载数据库驱动如何破坏双亲委托模式

    导读      通过jdbc链接数据库,是每个学习Java web 方向的人必然一开始会写的代码,虽然现在各路框架都帮大家封装好了jdbc,但是研究一下jdbc链接的套路还是很意义     术语以及相 ...

  10. 基于英特尔® 至强™ 处理器 E5 产品家族的多节点分布式内存系统上的 Caffe* 培训

    原文链接 深度神经网络 (DNN) 培训属于计算密集型项目,需要在现代计算平台上花费数日或数周的时间方可完成. 在最近的一篇文章<基于英特尔® 至强™ E5 产品家族的单节点 Caffe 评分和 ...

随机推荐

  1. window系统使用经验:新买的window11初始化时最好要选择用Microsoft账户激活,而不要用local账户激活

    Windows系统初始化时有两种类型的账户可以选择,一种时Microsoft账户,一种时local账户,Microsoft账户需要联网初始化,而local账户则和传统的初始化方式一致,即账号信息保存在 ...

  2. 一群伪专家讨论“motherland”和“fatherland”,说说个人的观点

    看了一个视频: 中国的文化里在找妈,美国的文化里在找爸!如何真正教育子女? ============================================= ================ ...

  3. 2024年Apache DolphinScheduler RoadMap:引领开源调度系统的未来

    非常欢迎大家来到Apache DolphinScheduler社区!随着开源技术在全球范围内的快速发展,社区的贡献者 "同仁" 一直致力于构建一个强大而活跃的开源调度系统社区,为用 ...

  4. 必看!S3File Sink Connector 使用文档

    S3File 是一个用于管理 Amazon S3(Simple Storage Service)的 Python 模块.当前,Apache SeaTunnel 已经支持 S3File Sink Con ...

  5. .Net Aspire次体验

    上次用上了在微软MVP的带领下用上了Aspire,在开发阶段隐藏了细节,什么都不用做,点个调试按钮就跑起来了,可是部署时出现了难题, 因为发布时只能选择Azure环境,为此注册了Azure,开了科网. ...

  6. SMU 2024 spring 天梯赛2

    SMU 2024 spring 天梯赛2 7-1 计算指数 - SMU 2024 spring 天梯赛2 (pintia.cn) #include <bits/stdc++.h> usin ...

  7. quartz监控日志(一)

    最近几个月,现网总是出现定时器不执行的情况,或者定时器卡死的情况,而又不方便排查,只能依靠quartz的debug日志以及错误日志来监控定时器的执行情况,并且随着我们系统中job越来越多,而使得job ...

  8. Python 开发中,使用bcrypt 或 Passlib 对系统用户密码进行哈希和验证处理

    在设计一个系统的时候,肯定都有会有用户身份认证的问题,一般对用户校验的时候,都是对用户存在数据库总的密码哈希值进行判断,从而避免密码泄露和反向解密,那么在Python 开发中,我们可以引入bcrypt ...

  9. yum下载包保存到本地

    1.使用yumdownloadonly下载RPM包及依赖包 #下载yumdownloadonly插件 yum install yum-plugin-downloadonly # yum 下载rpm包到 ...

  10. 关于Protobuf在使用中的一些注意点

    Protobuf是谷歌旗下的一款二进制序列化协议 协议的编写 在项目中新建一个xxx.proto文件 文件的格式 第一行写protobuf的版本 syntax = "proto3" ...