前言

笔者在最近的项目开发中,频繁地遇到了 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. CCF 无线网络

    题目原文 问题描述(题目链接登陆账号有问题,要从这个链接登陆,然后点击"模拟考试",进去找本题目) 试题编号: 201403-4 试题名称: 无线网络 时间限制: 1.0s 内存限 ...

  2. 【AppStore】IOS应用上架Appstore的一些小坑

    前言 上一篇文章写到如何上架IOS应用到Appstore,其中漏掉了些许期间遇到的小坑,现在补上 审核不通过原因 5.1.1 Guideline 5.1.1 - Legal - Privacy - D ...

  3. Jmeter参数化1-随机数设置

    背景:当新增接口的某个字段是唯一性,每次调用该新增接口都会需要单独传入这个字段,麻烦且繁琐. 解决:jmeter设置随机数参数,然后接口调用该参数就达到了自动性不再需要人工传入不同的值.方便调用接口, ...

  4. centos8配置网络环境及阿里云网络yum源

    一.centos8配置网络环境 1.修改配置网卡配置文件 [root@localhost ~]# cat /etc/sysconfig/network-scripts/ifcfg-ens18 TYPE ...

  5. 【C】Re09 结构体

    一.结构体 Struct 创建和基本使用 #include <stdio.h> #include <stdlib.h> #include <string.h> // ...

  6. 【Spring-Security】Re03 认证参数修改与跨域跳转处理

    一.请求参数名设置 之前的表单信息有一些要求: 1.action属性发送的地址是Security设置的URL 2.发送的请求方式是POST 3.请求的账户信息,也就是表单发送的参数,必须对应的是use ...

  7. 强化学习:经典测试环境Cart-pole的原始文献

    参考文献格式: A. G. Barto, R. S. Sutton, and C. W. Anderson. Neuronlike adaptive elements that can solve d ...

  8. 【转载】Typora 的 Markdown 语法

    原文地址: Typora 的 Markdown 语法 详细见原文: https://support.typoraio.cn/zh/Markdown-Reference/

  9. 结合实例看 maven 传递依赖与优先级,难顶也得上丫

    开心一刻 想买摩托车了,但是钱不够,想找老爸借点 我:老爸,我想买一辆摩托车,上下班也方便 老爸:你表哥上个月骑摩托车摔走了,你不知道?还要买摩托车? 我:对不起,我不买了 老板:就是啊,骑你表哥那辆 ...

  10. VUE learn

    Vue .js 的官方文档中是这样介绍它的. 简单小巧的核心,渐进式技术拢,足以应付任何规模的应用. 简单小巧是指 vue.js 压缩后大小仅有 17k .所谓渐进式(Progressive ),就是 ...