news 2026/6/10 13:16:40

MyBatis基础入门《十一》TypeHandler 详解:自定义类型处理器,打通数据库与 Java 的“任督二脉”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis基础入门《十一》TypeHandler 详解:自定义类型处理器,打通数据库与 Java 的“任督二脉”

前情回顾
在 《MyBatis基础入门《十》Spring Boot 整合 MyBatis》 中,我们完成了企业级项目的基础搭建。
但现实业务中,数据库字段和 Java 对象往往不是简单的一一对应

  • MySQL 的JSON字段要映射为Map<String, Object>或自定义对象;
  • 枚举值(如OrderStatus.PAID)需存为数字或字符串;
  • 敏感字段(手机号、身份证)入库前加密,查询后解密。

如何统一、安全、高效地处理这些转换?

答案:使用MyBatis TypeHandler
本文将从原理到实战,手把手教你编写自定义类型处理器。


一、什么是 TypeHandler?

TypeHandler 是 MyBatis 提供的类型转换器,负责:

  • Java Type → JDBC Type(设置参数时,如PreparedStatement.setXXX()
  • JDBC Type → Java Type(获取结果时,如ResultSet.getXXX()

MyBatis 内置了大量 TypeHandler(如StringTypeHandler,IntegerTypeHandler),但面对复杂类型时,我们需要自定义。


二、实战场景一:MySQL JSON 字段 ↔ Java 对象

场景说明

用户表中有一个profile JSON字段,存储用户扩展信息:

CREATE TABLE tbl_user ( id INT PRIMARY KEY, username VARCHAR(50), profile JSON );

Java 实体:

public class User { private Integer id; private String username; private UserProfile profile; // 自定义对象 } public class UserProfile { private String avatar; private String city; // getter / setter }

目标:查询时自动将 JSON 字符串转为UserProfile;插入时反向转换。


步骤 1:引入 JSON 工具(如 Jackson)

<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>

步骤 2:编写自定义 TypeHandler

// JsonTypeHandler.java package com.charles.typehandler; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.*; public class JsonTypeHandler<T> extends BaseTypeHandler<T> { private static final ObjectMapper objectMapper = new ObjectMapper(); private Class<T> type; public JsonTypeHandler(Class<T> type) { if (type == null) throw new IllegalArgumentException("Type argument cannot be null"); this.type = type; } @Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { try { String json = objectMapper.writeValueAsString(parameter); ps.setString(i, json); // 存为 VARCHAR/TEXT } catch (Exception e) { throw new SQLException("Error converting " + type.getSimpleName() + " to JSON", e); } } @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { return parseJson(rs.getString(columnName)); } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return parseJson(rs.getString(columnIndex)); } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return parseJson(cs.getString(columnIndex)); } private T parseJson(String json) throws SQLException { if (json == null || json.isEmpty()) return null; try { return objectMapper.readValue(json, type); } catch (Exception e) { throw new SQLException("Error parsing JSON to " + type.getSimpleName(), e); } } }

✅ 继承BaseTypeHandler<T>,实现四个核心方法; ✅ 使用泛型支持任意 Java 对象; ✅ 异常统一包装为SQLException


步骤 3:在实体类中注册 TypeHandler

public class User { private Integer id; private String username; @Results({ @Result(property = "profile", column = "profile", typeHandler = JsonTypeHandler.class) }) private UserProfile profile; }

或更简洁地,在字段上使用@TypeHandler(MyBatis 3.4+):

public class User { // ... @TypeHandler(JsonTypeHandler.class) private UserProfile profile; }

🔔 注意:若使用 XML 映射,可在<result>标签中指定typeHandler


步骤 4:测试效果

@Test public void testJsonTypeHandler() { User user = new User(); user.setUsername("张三"); UserProfile profile = new UserProfile(); profile.setAvatar("avatar.jpg"); profile.setCity("深圳"); user.setProfile(profile); userMapper.insert(user); // 自动转为 JSON 字符串存入数据库 User saved = userMapper.selectById(user.getId()); System.out.println(saved.getProfile().getCity()); // 输出:深圳 }

✅ 成功!无需手动序列化/反序列化!


三、实战场景二:枚举存储(Enum ↔ Integer/String)

需求

订单状态:CREATED=0,PAID=1,SHIPPED=2

public enum OrderStatus { CREATED(0), PAID(1), SHIPPED(2); private final int code; OrderStatus(int code) { this.code = code; } public int getCode() { return code; } public static OrderStatus fromCode(int code) { for (OrderStatus s : values()) { if (s.code == code) return s; } throw new IllegalArgumentException("Invalid code: " + code); } }

自定义 EnumTypeHandler

public class OrderStatusTypeHandler extends BaseTypeHandler<OrderStatus> { @Override public void setNonNullParameter(PreparedStatement ps, int i, OrderStatus parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.getCode()); } @Override public OrderStatus getNullableResult(ResultSet rs, String columnName) throws SQLException { int code = rs.getInt(columnName); return rs.wasNull() ? null : OrderStatus.fromCode(code); } // ... 其他 getNullableResult 方法类似 }

Order实体中使用:

@TypeHandler(OrderStatusTypeHandler.class) private OrderStatus status;

💡 优势:数据库存整数,Java 用枚举,安全又语义清晰!


四、全局注册 TypeHandler(可选)

避免每个字段重复声明,可在mybatis-config.xml中全局注册:

<typeHandlers> <typeHandler handler="com.charles.typehandler.JsonTypeHandler" javaType="com.charles.entity.UserProfile" jdbcType="VARCHAR" /> </typeHandlers>

或在 Spring Boot 中通过配置类:

@Configuration public class MyBatisConfig { @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); // 注册全局 TypeHandler TypeHandlerRegistry registry = factory.getObject().getConfiguration().getTypeHandlerRegistry(); registry.register(UserProfile.class, new JsonTypeHandler<>(UserProfile.class)); return factory.getObject(); } }

五、注意事项 & 最佳实践

⚠️ 1. 线程安全

  • ObjectMapper是线程安全的(Jackson 2.8+),可共享;
  • 避免在 TypeHandler 中使用非线程安全的成员变量。

⚠️ 2. 异常处理

  • 必须捕获内部异常并转为SQLException,否则 MyBatis 无法正确回滚。

✅ 3. 性能

  • TypeHandler 在每次 SQL 执行时调用,避免做耗时操作;
  • 可缓存反射结果(如枚举 code 映射)。

🔄 4. 与 JSON 数据库类型兼容

  • MySQL 5.7+ 支持JSON类型,但 JDBC 驱动仍将其视为VARCHAR
  • 因此setString/getString完全适用。

六、总结:TypeHandler 应用场景速查

场景解决方案
JSON 字段 ↔ 对象自定义JsonTypeHandler
枚举 ↔ 数字/字符串自定义EnumTypeHandler
加密字段(如手机号)入库加密,出库解密
日期格式定制(如只存年月)自定义LocalDateTypeHandler
多值字段(如逗号分隔)转为List<String>

核心价值
“让数据库字段与 Java 对象自由对话,代码更干净,逻辑更内聚!”

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 11:17:38

【Java毕设全套源码+文档】基于springboot的某省公园管理系统设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/7 6:02:16

AI搜索时代,塑胶模具企业如何通过GEO精准获客?

开篇&#xff1a; “精工模具”的张总最近很焦虑。公司网站做了SEO&#xff0c;B2B平台也烧着钱&#xff0c;但来的询盘要么是“哪里能3D打印个小零件”的学生&#xff0c;要么是问“注塑机多少钱一台”的同行。真正的、有明确模具需求的大客户&#xff0c;仿佛消失在了互联网的…

作者头像 李华
网站建设 2026/6/10 13:15:40

我发现光纤测温数据乱,时序对齐后精准定位电缆过热!

目录电力运维界的"扫地僧"&#xff1a;当AI遇上电老虎 一、传统运维的痛&#xff1a;我差点被电成碳烤肉 二、智能运维的救赎&#xff1a;从"人肉雷达"到AI医生 三、那些年我们追过的"高科技" 四、冷知识与冷笑话时间 五、未来已来的那些事 六、…

作者头像 李华
网站建设 2026/6/10 8:07:41

舒尔补(Schur Complement)

一、背景与动机&#xff08;为什么要用舒尔补&#xff09; 在处理分块矩阵&#xff08;block matrix&#xff09;时&#xff0c;经常需要“消去”某个分块&#xff0c;从而把问题降为对较小矩阵的运算。舒尔补提供了把一个 222\times222 分块矩阵的逆、行列式、正定性等性质归结…

作者头像 李华
网站建设 2026/6/10 8:06:06

AR定位漂移严重,后来才知道优化视觉惯性里程计融合算法

&#x1f493; 博客主页&#xff1a;借口的CSDN主页 ⏩ 文章专栏&#xff1a;《热点资讯》 目录我和AI的相爱相杀日常&#xff1a;一个普通人的自白书 第一章&#xff1a;从北京胡同到硅谷的蝴蝶效应 第二章&#xff1a;当AI开始玩转视频编辑 第三章&#xff1a;DeepSeek的&quo…

作者头像 李华
网站建设 2026/6/10 8:06:07

利用改进遗传算法进行大地电磁视电阻率反演

1. 引言 大地电磁&#xff08;MT&#xff09;反演是一种重要的地球物理勘探方法&#xff0c;用于推断地下介质的电阻率结构。传统的反演方法&#xff08;如最小二乘法&#xff09;存在依赖初始模型、容易陷入局部最优解的问题。近年来&#xff0c;遗传算法&#xff08;GA&#…

作者头像 李华