CLOB 大对象
本文你会学到:
- CLOB 和 BLOB 的区别及各自适用场景
- 通过
createClob + setCharacterStream 流式写入大文本
- 通过
getClob + getSubString 读取 CLOB 数据
- 大对象资源释放的重要性
📝 文章太长,VARCHAR 存不下怎么办?
当你需要存储文章内容、JSON 文档、XML 或日志等大文本数据时,VARCHAR(255) 显然不够用。这时就需要 CLOB(Character Large Object)——JDBC 中专门处理大文本的类型。与之对应的 BLOB(Binary Large Object)用于存储二进制数据(图片、视频、音频)。
在 MySQL 中,CLOB 对应的类型是 TEXT / LONGTEXT;在 Oracle 中则直接使用 CLOB 类型。JDBC 提供了统一的 API 来操作这些大对象,屏蔽了不同数据库的差异。
✍️ 写入 CLOB 数据
createClob + setCharacterStream 方式
Connection.createClob() 创建一个空的 Clob 对象,再通过 setCharacterStream(1) 获取 Writer 进行流式写入。这种方式适合大文本场景(MB 级别),数据通过流逐步写入,不会一次性占用内存。
| createClob() + setCharacterStream() 写入 CLOB |
|---|
| /**
* CLOB 写入:使用 Connection.createClob() + setCharacterStream 写入大文本
*/
@Test
@DisplayName("CLOB 写入:createClob + setCharacterStream")
void testClobWrite() throws SQLException {
String longText = "JDBC(Java Database Connectivity)是 Java 访问数据库的标准 API。"
+ "它提供了一套统一的接口,使 Java 程序能够以数据库无关的方式执行 SQL 语句、"
+ "检索结果集,并管理数据库连接。JDBC API 由 java.sql 和 javax.sql 包组成,"
+ "是 Java SE 和 Java EE 平台的核心组成部分。通过 JDBC,开发者可以连接各种关系型数据库,"
+ "包括 MySQL、PostgreSQL、Oracle、SQL Server 等。";
// 通过 Connection.createClob() 创建空的 Clob 对象
Clob clob = conn.createClob();
// 获取 Writer,从位置 1 开始写入(CLOB 位置从 1 开始)
try (Writer writer = clob.setCharacterStream(1)) {
writer.write(longText);
} catch (IOException e) {
throw new SQLException("写入 CLOB 内容失败", e);
}
// 使用 PreparedStatement.setClob() 绑定并插入
String sql = "INSERT INTO documents (name, content) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "JDBC 简介");
ps.setClob(2, clob);
int affectedRows = ps.executeUpdate();
assertEquals(1, affectedRows, "应成功插入 1 条记录");
}
// 验证插入成功:查询记录数
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM documents")) {
assertTrue(rs.next(), "应能查询到记录");
assertEquals(1, rs.getInt(1), "documents 表应有 1 条记录");
}
}
|
setString 直接写入
PreparedStatement.setString() 可以直接将字符串写入 CLOB 列,这是最简单的方式。但数据量较大时(如超过 JVM 堆内存),会将整个文本加载到内存中,不适合超大文本。
| setString() 直接写入 CLOB |
|---|
| /**
* CLOB 写入:直接使用 setString() 插入文本(适合较小内容)
*/
@Test
@DisplayName("CLOB 写入:setString 直接方式")
void testClobWriteString() throws SQLException {
String content = "这是一段通过 setString() 直接写入的文本。"
+ "对于较小的文本内容,无需创建 Clob 对象,直接使用 setString() 即可。"
+ "H2 数据库的 CLOB 类型支持直接设置字符串值,简化了操作流程。";
String sql = "INSERT INTO documents (name, content) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "简短文档");
// 直接用 setString() 设置 CLOB 列值(H2 支持,Oracle 等对大文本需用 setClob)
ps.setString(2, content);
int affectedRows = ps.executeUpdate();
assertEquals(1, affectedRows, "应成功插入 1 条记录");
}
// 验证插入内容
try (PreparedStatement ps = conn.prepareStatement(
"SELECT content FROM documents WHERE name = ?")) {
ps.setString(1, "简短文档");
try (ResultSet rs = ps.executeQuery()) {
assertTrue(rs.next(), "应能查询到刚插入的记录");
assertEquals(content, rs.getString("content"), "读取的内容应与写入的一致");
}
}
}
|
📖 读取 CLOB 数据
读取 CLOB 数据有两种常见方式:
ResultSet.getClob() 返回 Clob 对象,通过 getSubString() 提取全部或部分内容,适合需要分段读取的场景
ResultSet.getString() 直接返回完整字符串,JDBC 驱动会自动处理 CLOB 到 String 的转换
| getClob() + getSubString() 读取 CLOB |
|---|
| /**
* CLOB 读取:使用 getClob() 和 getString() 两种方式读取大文本
*/
@Test
@DisplayName("CLOB 读取:getClob 和 getString")
void testClobRead() throws SQLException {
// 先插入一条测试数据
String expectedContent = "CLOB(Character Large Object)用于存储大文本数据,"
+ "最大可存储数 GB 的字符数据。与 VARCHAR 不同,CLOB 专门为海量文本设计,"
+ "适合存储文章内容、日志文件、XML/JSON 文档等大文本场景。"
+ "在 JDBC 中,通过 java.sql.Clob 接口操作 CLOB 数据,"
+ "支持流式读写以避免将全部内容加载到内存。";
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO documents (name, content) VALUES (?, ?)")) {
ps.setString(1, "CLOB 说明文档");
ps.setString(2, expectedContent);
ps.executeUpdate();
}
// 方式一:使用 ResultSet.getClob() 获取 Clob 对象,再通过 getSubString() 读取
try (PreparedStatement ps = conn.prepareStatement(
"SELECT name, content FROM documents WHERE name = ?")) {
ps.setString(1, "CLOB 说明文档");
try (ResultSet rs = ps.executeQuery()) {
assertTrue(rs.next(), "应能查询到记录");
// getClob() 返回 Clob 对象
Clob clob = rs.getClob("content");
assertNotNull(clob, "Clob 对象不应为 null");
// getSubString(pos, length): 从指定位置读取指定长度的子串(位置从 1 开始)
String contentFromClob = clob.getSubString(1, (int) clob.length());
assertEquals(expectedContent, contentFromClob,
"通过 Clob.getSubString() 读取的内容应与写入的一致");
// 方式二:直接使用 getString(),更简洁(适合较小的 CLOB)
String contentFromString = rs.getString("content");
assertEquals(expectedContent, contentFromString,
"通过 getString() 读取的内容应与写入的一致");
}
}
}
|
推荐使用 getString()
对于大多数场景,直接使用 ResultSet.getString() 读取 CLOB 列是最简单的方式。JDBC 驱动会自动将 CLOB 内容转换为 String。只有处理超大文本(如 100MB+)时才需要使用 getCharacterStream() 流式读取。
⚖️ BLOB vs CLOB 对比
| 维度 |
BLOB |
CLOB |
| 全称 |
Binary Large Object |
Character Large Object |
| 存储内容 |
二进制数据(图片、视频、音频) |
字符数据(文章、JSON、XML) |
| JDBC 类型 |
Blob |
Clob |
| 创建方法 |
Connection.createBlob() |
Connection.createClob() |
| 写入方式 |
setBinaryStream() |
setCharacterStream() |
| 读取方式 |
getBinaryStream() |
getCharacterStream() / getString() |
| 简单写入 |
setBytes() |
setString() |
| MySQL 类型 |
BLOB / LONGBLOB |
TEXT / LONGTEXT |
选择原则很简单:存储的是字符文本就用 CLOB,存储的是二进制数据就用 BLOB。
♻️ 资源释放
Clob 和 Blob 对象在创建它们的事务期间一直有效,占用数据库服务端的资源。如果长事务中持有大量大对象而不释放,可能导致资源耗尽。使用完毕后应调用 free() 主动释放。
| free() 释放 CLOB 资源 |
|---|
| /**
* CLOB 资源释放:演示 free() 方法的使用
* <p>
* 对于大型 CLOB 对象,在长事务中及时调用 free() 可以释放数据库端和驱动端的资源。
* H2 的内存数据库中 free() 效果不明显,但在 Oracle 等真实数据库中,
* 未释放的 CLOB 可能占用临时表空间,尤其在批量处理场景下。
*/
@Test
@DisplayName("CLOB 资源释放:free() 方法")
void testClobFree() throws SQLException {
// 通过 Connection.createClob() 创建 Clob 对象
Clob clob = conn.createClob();
assertNotNull(clob, "createClob() 应返回非 null 的 Clob 对象");
String expectedText = "用于演示 free() 的测试文本,在实际生产环境中,"
+ "处理完大文本后应及时释放资源。";
try (Writer writer = clob.setCharacterStream(1)) {
writer.write(expectedText);
} catch (IOException e) {
throw new SQLException("写入 CLOB 内容失败", e);
}
// 验证 Clob 对象可用
assertEquals(expectedText.length(), clob.length(), "Clob 长度应与写入的文本一致");
// 调用 free() 释放资源
clob.free();
// 释放后再次访问 Clob 会抛出 SQLException
assertThrows(SQLException.class, () -> clob.length(),
"free() 之后访问 Clob 应抛出 SQLException");
}
|
及时释放大对象资源
Clob、Blob 对象在创建它们的事务期间一直有效。长事务可能导致资源耗尽,应在使用完毕后调用 free() 主动释放。对于小文本使用 setString() / getString() 方式则无需手动管理。