【解决方案】基于数据库驱动的自定义 TypeHandler 处理器
前言
笔者在最近的项目开发中,频繁地遇到了 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 处理器的更多相关文章
- atitit. java queue 队列体系and自定义基于数据库的队列总结o7t
atitit. java queue 队列体系and自定义基于数据库的队列总结o7t 1. 阻塞队列和非阻塞队列 1 2. java.util.Queue接口, 1 3. ConcurrentLink ...
- ThinkCMF项目部署出现无法加载数据库驱动解决方案
最近有个TP项目刚从从本地部署到阿里云服务器上,出现了无法加载数据库驱动的错误,提示 :( 无法加载数据库驱动: Think\Db\Driver 这里分享一下出现该错误的解决步骤: 首先记得项目部署到 ...
- 在IDEA中已经配置postgis数据库驱动并且能在Java类中连接数据库,但在servlet中无法连接数据库且导致Tomcat自动断开连接的解决方案
最近在IDEA中用JDBC连接PostgreSQL数据库时遇到了这样一个奇怪的事情: 从PostgreSQL JDBC Driver官网下载好JDBC驱动之后,在IDEA的Project Struct ...
- MyBatis使用自定义TypeHandler转换类型的实现方法
From: http://www.manongjc.com/article/15577.html 这篇文章主要介绍了MyBatis使用自定义TypeHandler转换类型的实现方法,本文介绍使用Typ ...
- MyBatis使用自定义TypeHandler转换类型
MyBatis虽然有很好的SQL执行性能,但毕竟不是完整的ORM框架,不同的数据库之间SQL执行还是有差异. 笔者最近在升级 Oracle 驱动至 ojdbc 7 ,就发现了处理DATE类型存在问题. ...
- 【tornado】系列项目(二)基于领域驱动模型的区域后台管理+前端easyui实现
本项目是一个系列项目,最终的目的是开发出一个类似京东商城的网站.本文主要介绍后台管理中的区域管理,以及前端基于easyui插件的使用.本次增删改查因数据量少,因此采用模态对话框方式进行,关于数据量大采 ...
- 基于数据库的自动化生成工具,自动生成JavaBean、自动生成数据库文档等(v4.1.2版)
目录: 第1版:http://blog.csdn.net/vipbooks/article/details/51912143 第2版:htt ...
- SQL SERVER 基于数据库镜像的主从同步(数据库镜像实践汇总)
SQL SERVER 基于数据库镜像的主从同步 Author:chaoqun.guo createtime:2019-03-26 目录 SQL SERVER 基于数据库镜像的主从同步... 1 ...
- jdbc 加载数据库驱动如何破坏双亲委托模式
导读 通过jdbc链接数据库,是每个学习Java web 方向的人必然一开始会写的代码,虽然现在各路框架都帮大家封装好了jdbc,但是研究一下jdbc链接的套路还是很意义 术语以及相 ...
- 基于英特尔® 至强™ 处理器 E5 产品家族的多节点分布式内存系统上的 Caffe* 培训
原文链接 深度神经网络 (DNN) 培训属于计算密集型项目,需要在现代计算平台上花费数日或数周的时间方可完成. 在最近的一篇文章<基于英特尔® 至强™ E5 产品家族的单节点 Caffe 评分和 ...
随机推荐
- C# 推荐一种开机自启动的方式
概述(Overview) 网上多数搜索结果以注册表设置为优先,这个方法需要管理员权限,实际工作中可能并不适用.这个方法是直接写到用户开机自启动目录里,系统开机会带着一起启动.(Most search ...
- python raise异常处理
python raise异常处理 一般最简单的异常处理是try except: try: f = open('test.txt') except Exception as e: print(e) f ...
- kimchi – kvm虚拟机网页管理
参考: https://mangolassi.it/topic/15882/kimchi-kvm-updated-and-better-and-easy-guide-for-kvm-beginners ...
- 个人常用的 matplotlib 绘图模板
import numpy as np import matplotlib import matplotlib.pyplot as plt matplotlib.rcParams['mathtext.f ...
- 纯css美化滚动条样式
https://www.emperinter.info/2020/09/20/scrollbar-in-browser/ 自己完善自己博客再Ipad等移动端的菜单时选用了左右滑动的菜单模式!当然就会使 ...
- 神经网络之卷积篇:详解卷积步长(Strided convolutions)
详解卷积步长 卷积中的步幅是另一个构建卷积神经网络的基本操作,让向展示一个例子. 如果想用3×3的过滤器卷积这个7×7的图像,和之前不同的是,把步幅设置成了2.还和之前一样取左上方的3×3区域的元素的 ...
- 通过JMX监控weblogic服务
一.JMX简介 JMX是一种JAVA的正式规范,它主要目的是让程序有被管理的功能,那么怎么理解所谓的"被管理"呢?试想你开发了一个软件(如WEB网站),它是在24小时不间断运行的, ...
- Win32_GDI_五星红旗绘制
五星红旗画法 设置矩形长与高的比为3:2 把矩形分为四个相等的长方形 把左上角宽分为15份,高分为10份 定位大五角星圆心位置,x为宽的5份,y为高的5份 计算五角星五个点的坐标 void SetFi ...
- IP报文格式详解
下图为常见的IP报文格式表: 上面是IP的报文格式,接下来我们先说明各个字段的意义.然后,用Etheral软件转包分析IP的报文格式. 1.版本:ip报文中,版本占了4位,用来表示该协议采用的是那一个 ...
- AI的那些名词
AI 是什么? Artificial Intelligence,即人工智能,1956年于Dartmouth学会上提出,一种旨在以类似人类反应的方式对刺激做出反应并从中学习的技术,其理解和判断水平通常只 ...