mybatis读clob/blob时oom主因是clobtypehandler和bytearraytypehandler默认全量加载,应映射为java.sql.clob/blob并流式处理;mapper需显式配置jdbctype="clob"/"blob"与javatype,实体字段用对应sql类型,业务层调用getcharacterstream()或getbinarystream()按需读取。

MyBatis读取CLOB时OOM:不是数据太大,是TypeHandler默认加载全量
MyBatis用ClobTypeHandler读CLOB字段,默认会调用Clob.getSubString(1, (int) clob.length())——把整个CLOB一次性转成String进堆内存。哪怕数据库里存了100MB文本,JVM没配-Xmx2g就直接OutOfMemoryError。
这不是SQL写错了,也不是数据库配置问题,是TypeHandler的“默认行为”在悄悄吃内存。
- 只在需要全文检索、前端展示或字符串处理时才用
ClobTypeHandler - 如果只是校验长度、提取前100字、或流式处理,不该走这个路径
- Oracle/MySQL/PostgreSQL的JDBC驱动对
Clob对象的底层实现不同,但ClobTypeHandler不区分,统一强转
用InputStream流式读CLOB:绕过ClobTypeHandler的关键三步
目标是让MyBatis返回java.sql.Clob原生对象,自己控制读取节奏。核心是禁用自动类型转换,靠resultMap显式指定jdbcType和javaType。
- Mapper XML中字段映射不能写
#{clobField},必须用<result column="content" property="clobContent" jdbctype="CLOB" javatype="java.sql.Clob"></result> - 实体类字段类型必须声明为
java.sql.Clob(不是String或byte[]) - 业务代码里用
clob.getCharacterStream()或clob.getAsciiStream()按需读,例如IOUtils.copy(clob.getCharacterStream(), writer)
这样JDBC驱动只在真正调用getCharacterStream()时才建立流连接,不会提前加载全部内容。
BLOB字段同样危险:别用ByteArrayTypeHandler读百MB文件
ByteArrayTypeHandler对BLOB的处理逻辑和ClobTypeHandler如出一辙:调用Blob.getBytes(1, (int) blob.length()),全量加载进byte[]。PDF、图片、压缩包等二进制大字段极易触发OOM。
- 替代方案是映射为
java.sql.Blob,再用blob.getBinaryStream()流式消费 - 如果必须转
byte[],务必加长度校验:if (blob.length() > 5 * 1024 * 1024) throw new IllegalArgumentException("BLOB too large") - Spring Boot 3.x + MyBatis 3.5+ 默认仍启用
ByteArrayTypeHandler,不会自动降级,得手动干预
自定义TypeHandler不是银弹:慎用setNonNullParameter中的流写入
有人想写个“安全”的ClobTypeHandler,在setNonNullParameter里用Writer分块写入。这反而容易出错:
- 多数JDBC驱动(尤其是Oracle thin driver)不支持对
Clob多次setCharacterStream,第二次调用直接抛SQLException: Invalid operation for read only resultset - MySQL Connector/J 8.0+ 对
Clob.setAsciiStream(0)有bug,偏移量传0会写失败 - 流未关闭、事务未提交时,
Clob临时句柄可能被GC回收,导致写入静默截断
真要写大CLOB/BLOB,优先走PreparedStatement.setClob(int, Reader)或setBlob(int, InputStream)原生API,别套MyBatis的TypeHandler封装层。










