MyBatis处理CLOB/BLOB必须避免默认TypeHandler全量加载,应映射为java.sql.Clob/Blob并流式读取:实体字段声明对应SQL类型,Mapper显式配置jdbcType与javaType,业务层调用getCharacterStream()或getBinaryStream()按需处理。

MyBatis 处理 BLOB/CLOB 不能靠默认映射,否则极易触发 OOM。核心思路是:避开全量加载型 TypeHandler,改用 JDBC 原生 Blob/Clob 对象,再通过流式读写控制内存占用。
避免默认 TypeHandler 导致的内存爆炸
MyBatis 默认用 ClobTypeHandler 读 CLOB 时,会调用 clob.getSubString(1, (int) clob.length()),把整个字段一次性转成 String;ByteArrayTypeHandler 读 BLOB 同样调用 blob.getBytes(1, (int) blob.length()),全部加载进 byte[]。哪怕数据库存了 50MB 文本或图片,JVM 堆内存不足就直接崩溃。
- 这不是数据本身太大,而是 TypeHandler 的“默认行为”在吃内存
- 只要不需全文检索、前端直出或字符串处理,就别让 MyBatis 自动转成 String 或 byte[]
- Spring Boot 3.x + MyBatis 3.5+ 仍默认启用这些 Handler,不会自动降级
正确映射:返回原生 Blob/Clob 对象
目标是让 MyBatis 返回 java.sql.Blob 或 java.sql.Clob,由业务代码按需流式消费。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 实体类字段声明为
java.sql.Clob或java.sql.Blob(不是 String / byte[]) - Mapper XML 中显式指定
jdbcType="CLOB"或jdbcType="BLOB",且不配typeHandler - 示例:
<result column="content" property="clobContent" jdbcType="CLOB" javaType="java.sql.Clob"/> - SQL 查询中不能用
#{xxx}写入大字段,插入时需用setClob()/setBlob()方法
流式读取:按需拉取,不占堆内存
拿到原生对象后,用标准 JDBC 流接口读取,驱动只在真正调用时建立连接,不预加载。
立即学习“Java免费学习笔记(深入)”;
- CLOB:调用
clob.getCharacterStream()获取Reader,可配合IOUtils.copy()或逐行读取 - BLOB:调用
blob.getBinaryStream()获取InputStream,适合写入文件、上传或分块解析 - 若必须转成 byte[] 或 String,务必先校验长度:
if (blob.length() > 5 * 1024 * 1024) throw new IllegalArgumentException("Too large")
安全写入:避免 PreparedStatement 直接传大内容
插入时不要把大内容塞进 #{xxx},应利用 JDBC 的流式设值能力。
- 使用
PreparedStatement.setClob(int, Reader)或setClob(int, Reader, long) - 同理,
setBlob(int, InputStream)或setBlob(int, InputStream, long) - MyBatis 中可通过自定义 TypeHandler 实现,但更推荐在 Service 层直接操作 PreparedStatement(如用 SqlSession.getConnection() 获取原生连接)
- 注意:MySQL 的
load_file()、Oracle 的存储过程等方式属于 SQL 层绕过,不适用于通用 ORM 场景

















