#LOB数据处理最佳实践
# 适用范围
本文适用于Java应用通过YashanDB JDBC驱动处理CLOB和BLOB数据的场景,包括业务表创建、流式插入、分块读取、内容校验、事务内更新和资源释放。
示例验证环境如下:
| 组件 | 版本 |
|---|---|
| JDK | 17.0.19 |
| YashanDB JDBC驱动 | 1.10.11 |
| YashanDB Server | 23.4.10.100 |
# 预期效果
完成本实践后,应用应具备以下能力:
- 使用CLOB和BLOB存储长文本与二进制内容。
- 使用流式接口读写LOB。
- 分块读取LOB并做校验。
- 在同一事务中更新LOB和业务状态。
- 及时关闭相关资源。
# 实施步骤
# 1. 创建LOB业务表
由部署账号执行以下建表语句:
CREATE TABLE business_asset (
asset_id INTEGER PRIMARY KEY,
asset_code VARCHAR(32),
description CLOB,
attachment BLOB,
status VARCHAR(16)
);
业务账号只需要该表的SELECT、INSERT和UPDATE权限。应用不使用高权限账号,也不允许把用户输入直接拼接为表名或列名。
# 2. 选择LOB接口
| 数据类型 | 流式写入 | 流式读取 | 适用数据 |
|---|---|---|---|
| CLOB | setCharacterStream() | getCharacterStream() | 长文本、说明文档、文本配置 |
| BLOB | setBinaryStream() | getBinaryStream() | 图片、压缩包、协议文件和其他二进制内容 |
使用带长度参数的重载时,CLOB长度单位为字符,BLOB长度单位为字节。长度未知时可以使用不带长度参数的重载,但应先验证目标驱动版本。
# 3. 流式插入LOB
输入流由业务层创建,JDBC代码在流仍然有效时执行SQL。流、PreparedStatement和连接应位于清晰的资源作用域内。
try (PreparedStatement statement = connection.prepareStatement(
"INSERT INTO business_asset "
+ "(asset_id, asset_code, description, attachment, status) "
+ "VALUES (?, ?, ?, ?, ?)")) {
statement.setInt(1, 1001);
statement.setString(2, "ASSET_1001");
statement.setCharacterStream(3, textReader, characterLength);
statement.setBinaryStream(4, binaryInputStream, byteLength);
statement.setString(5, "DRAFT");
statement.executeUpdate();
}
真实大文件可以使用FileInputStream和Files.newBufferedReader()提供流。不要先使用readAllBytes()把整个文件加载到Java堆内存。
# 4. 分块读取并校验LOB
读取时使用固定大小缓冲区。BLOB可以计算SHA-256摘要,用于校验业务传输前后的内容一致性;摘要不是加密存储,也不能替代访问控制。
try (Reader reader = resultSet.getCharacterStream("description")) {
char[] buffer = new char[2048];
for (int count; (count = reader.read(buffer)) != -1;) {
textWriter.write(buffer, 0, count);
}
}
try (InputStream input = resultSet.getBinaryStream("attachment")) {
byte[] buffer = new byte[4096];
for (int count; (count = input.read(buffer)) != -1;) {
digest.update(buffer, 0, count);
}
}
如果业务只需要把BLOB发送到文件或网络响应,应直接把读取缓冲区写入目标流,不需要保存完整byte[]。
# 5. 在事务中更新LOB和业务状态
LOB内容和状态字段属于同一业务变更时,应在一个事务中更新。更新行数不为1表示业务对象不存在或条件不满足,程序应回滚而不是继续提交。
boolean initialAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(false);
try (PreparedStatement statement = connection.prepareStatement(
"UPDATE business_asset "
+ "SET description = ?, attachment = ?, status = ? "
+ "WHERE asset_id = ?")) {
statement.setCharacterStream(1, updatedTextReader, updatedCharacterLength);
statement.setBinaryStream(2, updatedBinaryStream, updatedByteLength);
statement.setString(3, "APPROVED");
statement.setInt(4, 1001);
if (statement.executeUpdate() != 1) {
throw new SQLException("LOB business update did not affect one row");
}
connection.commit();
} catch (Exception exception) {
connection.rollback();
throw exception;
} finally {
connection.setAutoCommit(initialAutoCommit);
}
# 6. 执行完整业务示例
以下程序使用jdbc_bp_lob_asset临时表完成CLOB和BLOB流式插入、读取校验、业务更新和再次校验。程序只删除本次成功创建的对象。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.security.MessageDigest;
public class LobBestPracticeExample {
private static final String TABLE = "jdbc_bp_lob_asset";
public static void main(String[] args) throws Exception {
String initialText = repeat("Initial asset description. ", 200);
byte[] initialBinary = bytes(8192, 7);
String updatedText = repeat("Approved asset description. ", 160);
byte[] updatedBinary = bytes(12288, 11);
boolean created = false;
try (Connection connection = open()) {
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + TABLE
+ " (asset_id INTEGER PRIMARY KEY, "
+ "asset_code VARCHAR(32), description CLOB, "
+ "attachment BLOB, status VARCHAR(16))");
created = true;
}
boolean initialAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(false);
try {
insert(connection, initialText, initialBinary);
LobValue inserted = read(connection);
print("Inserted", inserted);
require(initialText.equals(inserted.text),
"Inserted CLOB mismatch");
require(digest(initialBinary).equals(
inserted.binaryDigest),
"Inserted BLOB mismatch");
update(connection, updatedText, updatedBinary);
connection.commit();
System.out.println("LOB transaction committed");
} catch (Exception exception) {
connection.rollback();
throw exception;
} finally {
connection.setAutoCommit(initialAutoCommit);
}
LobValue updated = read(connection);
print("Updated", updated);
require(updatedText.equals(updated.text),
"Updated CLOB mismatch");
require(digest(updatedBinary).equals(updated.binaryDigest),
"Updated BLOB mismatch");
require("APPROVED".equals(updated.status), "Status mismatch");
} finally {
if (created) {
execute("DROP TABLE " + TABLE);
}
}
}
private static void insert(
Connection connection, String text, byte[] binary)
throws SQLException {
try (PreparedStatement statement = connection.prepareStatement(
"INSERT INTO " + TABLE
+ " (asset_id, asset_code, description, "
+ "attachment, status) VALUES (?, ?, ?, ?, ?)")) {
statement.setInt(1, 1001);
statement.setString(2, "ASSET_1001");
statement.setCharacterStream(
3, new StringReader(text), text.length());
statement.setBinaryStream(
4, new ByteArrayInputStream(binary), binary.length);
statement.setString(5, "DRAFT");
statement.executeUpdate();
}
}
private static void update(
Connection connection, String text, byte[] binary)
throws SQLException {
try (PreparedStatement statement = connection.prepareStatement(
"UPDATE " + TABLE
+ " SET description = ?, attachment = ?, status = ? "
+ "WHERE asset_id = ?")) {
statement.setCharacterStream(
1, new StringReader(text), text.length());
statement.setBinaryStream(
2, new ByteArrayInputStream(binary), binary.length);
statement.setString(3, "APPROVED");
statement.setInt(4, 1001);
if (statement.executeUpdate() != 1) {
throw new SQLException(
"LOB business update did not affect one row");
}
}
}
private static LobValue read(Connection connection) throws Exception {
try (PreparedStatement statement = connection.prepareStatement(
"SELECT description, attachment, status FROM " + TABLE
+ " WHERE asset_id = ?")) {
statement.setInt(1, 1001);
try (ResultSet resultSet = statement.executeQuery()) {
if (!resultSet.next()) {
throw new SQLException("LOB business row not found");
}
String text;
try (Reader reader = resultSet.getCharacterStream(1);
StringWriter writer = new StringWriter()) {
copy(reader, writer);
text = writer.toString();
}
byte[] binary;
try (InputStream input = resultSet.getBinaryStream(2);
ByteArrayOutputStream output =
new ByteArrayOutputStream()) {
copy(input, output);
binary = output.toByteArray();
}
return new LobValue(text, binary.length,
digest(binary), resultSet.getString(3));
}
}
}
private static void copy(Reader reader, StringWriter writer)
throws Exception {
char[] buffer = new char[2048];
for (int count; (count = reader.read(buffer)) != -1;) {
writer.write(buffer, 0, count);
}
}
private static void copy(
InputStream input, ByteArrayOutputStream output)
throws Exception {
byte[] buffer = new byte[4096];
for (int count; (count = input.read(buffer)) != -1;) {
output.write(buffer, 0, count);
}
}
private static String digest(byte[] value) throws Exception {
byte[] result = MessageDigest.getInstance("SHA-256")
.digest(value);
StringBuilder text = new StringBuilder();
for (byte item : result) {
text.append(String.format("%02x", item & 0xff));
}
return text.toString();
}
private static String repeat(String value, int count) {
StringBuilder result = new StringBuilder(value.length() * count);
for (int index = 0; index < count; index++) {
result.append(value);
}
return result.toString();
}
private static byte[] bytes(int length, int seed) {
byte[] result = new byte[length];
for (int index = 0; index < result.length; index++) {
result[index] = (byte) ((index * 31 + seed) & 0xff);
}
return result;
}
private static void print(String phase, LobValue value) {
System.out.println(phase + " CLOB chars: " + value.text.length());
System.out.println(phase + " BLOB bytes: " + value.binaryLength);
System.out.println(phase + " BLOB SHA-256: "
+ value.binaryDigest);
System.out.println(phase + " status: " + value.status);
}
private static void require(boolean condition, String message) {
if (!condition) {
throw new IllegalStateException(message);
}
}
private static Connection open() throws SQLException {
return DriverManager.getConnection(required("YASDB_URL"),
required("YASDB_USER"), required("YASDB_PASSWORD"));
}
private static void execute(String sql) throws SQLException {
try (Connection connection = open();
Statement statement = connection.createStatement()) {
statement.execute(sql);
}
}
private static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new IllegalStateException(
"Missing environment variable: " + name);
}
return value;
}
private static final class LobValue {
private final String text;
private final int binaryLength;
private final String binaryDigest;
private final String status;
private LobValue(String text, int binaryLength,
String binaryDigest, String status) {
this.text = text;
this.binaryLength = binaryLength;
this.binaryDigest = binaryDigest;
this.status = status;
}
}
}
# 7. 验证结果
关键输出如下:
Inserted CLOB chars: 5400
Inserted BLOB bytes: 8192
Inserted status: DRAFT
LOB transaction committed
Updated CLOB chars: 4480
Updated BLOB bytes: 12288
Updated status: APPROVED
程序还会输出插入前后BLOB的SHA-256摘要,并在摘要、CLOB文本或业务状态不一致时抛出异常。

