mybatis处理blob大字段需避免全量加载导致oom,核心是映射为java.sql.blob或inputstream/outputstream,显式配置jdbctype="blob"与javatype,业务层调用getbinarystream()或setbinarystream()流式读写。

MyBatis 处理 BLOB(Binary Large Object)这类大字段,核心在于类型映射、SQL 写法和流式读写控制。直接用 byte[] 读写小文件没问题,但对几百 MB 甚至更大的二进制数据,必须避免内存溢出,需借助流式接口(InputStream/OutputStream)配合 JDBC 的 setBinaryStream 和 getBinaryStream。
1. 数据库字段与 Java 类型映射要匹配
确保数据库中字段类型为 BLOB(MySQL)、BYTEA(PostgreSQL)或 BLOB/VARBINARY(Oracle/SQL Server),Java 实体类中对应字段声明为:
-
byte[]:适用于小文件(如头像、文档附件 ≤ 10MB),MyBatis 自动完成字节数组与 BLOB 的双向转换; -
java.io.InputStream:仅用于读取,配合resultMap中的jdbcType="BLOB"和javaType="java.io.InputStream"; -
java.io.OutputStream:仅用于写入,需在Mapper接口中用@Param传入,并在 XML 中用#{stream, jdbcType=BLOB}(注意:JDBC 驱动需支持,且 MyBatis 3.4+ 更稳定)。
2. 写入 BLOB:优先用 InputStream + PreparedStatement 流式写入
不推荐把整个文件读进内存再塞进 byte[]。正确做法是让 MyBatis 绑定一个 InputStream,由 JDBC 驱动分块写入:
<insert id="insertWithBlob">
INSERT INTO document (id, name, content)
VALUES (#{id}, #{name}, #{content, jdbcType=BLOB})
</insert>
Mapper 接口方法:
void insertWithBlob(@Param("id") Long id,
@Param("name") String name,
@Param("content") InputStream inputStream);
调用时传入文件输入流即可:
try (FileInputStream fis = new FileInputStream("/path/to/large.pdf")) {
mapper.insertWithBlob(1L, "report.pdf", fis);
}
✅ MyBatis 会自动调用 PreparedStatement.setBinaryStream(),底层按需读取流,不加载全量到内存。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
3. 读取 BLOB:按需获取 InputStream,避免一次性加载
查询大 BLOB 时,别用 byte[] 接收,改用 InputStream:
<resultmap id="DocumentResultMap" type="Document"><id property="id" column="id"></id><result property="name" column="name"></result><result property="content" column="content" jdbctype="BLOB" javatype="java.io.InputStream"></result></resultmap><select id="selectById" resultmap="DocumentResultMap">
SELECT id, name, content FROM document WHERE id = #{id}
</select>
Java 调用后,直接消费流:
Document doc = mapper.selectById(1L);
try (InputStream is = doc.getContent()) {
Files.copy(is, Paths.get("/tmp/downloaded.pdf"));
}
⚠️ 注意:InputStream 生命周期绑定 SQL 执行后的 ResultSet,必须在同一个数据库连接未关闭前使用,建议在 DAO 方法内完成流读取或及时复制到本地文件/缓存。
4. 高级场景:分块读写或自定义 TypeHandler
若需更精细控制(如加解密、压缩、断点续传),可实现 BaseTypeHandler<inputstream></inputstream>:
- 重写
setNonNullParameter():包装原始流,注入加密逻辑后再传给 JDBC; - 重写
getNullableResult():从ResultSet.getBinaryStream()获取流后,返回解密/解压后的InputStream; - 注册到全局配置或局部
<resultmap></resultmap>中,用typeHandler属性指定。
这种方式适合统一治理敏感 BLOB 的加解密策略,无需每个 Mapper 单独处理。
关键是根据数据规模选对载体:小文件用 byte[] 简单直接;大文件必须走流,靠 MyBatis 的 JDBC 流式绑定能力规避 OOM。只要 SQL、类型声明、参数传递三者一致,BLOB 操作就非常可靠。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










