早期 SQL Server 2000 中存儲二進制文件(圖片、文檔)需使用 IMAGE 類型(TEXT 用於大文本,但與二進制流不兼容)。本文提供基於 JDBC-ODBC 橋接器的完整存取示例,雖技術棧較老,但核心 PreparedStatement.setBinaryStream/getBinaryStream 模式在現代 JDBC 中依然通用。
CREATE TABLE FileStorage (
id CHAR(10) PRIMARY KEY, -- 業務主鍵
data IMAGE -- 二進制數據,最大 2GB
-- 注意:不可使用 TEXT 類型存二進制,TEXT 為字符流類型
);
import java.io.*;
import java.sql.*;
public class SqlServerBlobHandler {
private static final String URL = "jdbc:odbc:LW"; // 系統 DSN 名稱
private static final String INSERT_SQL = "INSERT INTO FileStorage (id, data) VALUES (?, ?)";
private static final String SELECT_SQL = "SELECT data FROM FileStorage WHERE id = ?";
/**
* 文件寫入數據庫
* @param filePath 本地文件路徑
* @param id 業務主鍵
*/
public void insertFile(String filePath, String id) {
File file = new File(filePath);
try (Connection conn = DriverManager.getConnection(URL);
PreparedStatement ps = conn.prepareStatement(INSERT_SQL);
FileInputStream fis = new FileInputStream(file)) {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // JDK 8 前可用
ps.setString(1, id);
// 關鍵:使用 setBinaryStream 流式寫入,避免大文件 OOM
ps.setBinaryStream(2, fis, (int) file.length());
ps.executeUpdate();
System.out.println("文件存入成功: " + id);
} catch (Exception e) {
throw new RuntimeException("文件存入失敗: " + id, e);
}
}
/**
* 從數據庫讀取文件到本地
* @param id 業務主鍵
* @param destPath 目標保存路徑
*/
public void extractFile(String id, String destPath) {
try (Connection conn = DriverManager.getConnection(URL);
PreparedStatement ps = conn.prepareStatement(SELECT_SQL);
ResultSet rs = ps.executeQuery()) {
ps.setString(1, id);
if (rs.next()) {
try (InputStream bis = rs.getBinaryStream("data");
FileOutputStream fos = new FileOutputStream(destPath)) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("文件提取成功: " + destPath);
}
} else {
System.out.println("未找到記錄: " + id);
}
} catch (Exception e) {
throw new RuntimeException("文件提取失敗: " + id, e);
}
}
public static void main(String[] args) {
SqlServerBlobHandler handler = new SqlServerBlobHandler();
// 測試存入
handler.insertFile("D:\\test1.txt", "0004");
// 測試提取
handler.extractFile("0004", "D:\\restored_test1.txt");
}
}
ps.setBinaryStream(index, inputStream, length) 分塊傳輸,不將整文件載入內存rs.getBinaryStream(column) 獲取輸入流,緩衝區循環寫出8KB 平衡內存與系統調用開銷LW) 需與代碼 jdbc:odbc:LW 一致警告: sun.jdbc.odbc.JdbcOdbcDriver 在 JDK 8 中被移除,現代項目請使用 Microsoft 官方 mssql-jdbc 驅動:
// 現代驅動配置
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
String url = "jdbc:sqlserver://localhost:1433;databaseName=TestDB;user=sa;password=123456";
// 寫入 CLOB
ps.setCharacterStream(2, new FileReader(file), (int) file.length());
// 讀取 CLOB
Reader reader = rs.getCharacterStream("data");
// 或 rs.getClob("data").getCharacterStream()
原文採用線程封裝讀取文件再寫入,現代寫法建議直接流式處理。若需異步,可使用 CompletableFuture 或線程池:
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> handler.insertFile("large_video.mp4", "VID_001"));
| 錯誤現象 | 原因 | 解決 |
|---|---|---|
| Data type mismatch | 用 TEXT 列存二進制 / 用 IMAGE 存文本流 | 類型匹配:IMAGE↔BinaryStream, TEXT↔CharacterStream |
| Stream closed | ResultSet/Connection 關閉前讀取流 | 在 try-with-resources 內完成流讀寫 |
| Heap Space OOM | 用 getBytes()/String 載入大文件 | 堅持使用 Stream API 分塊處理 |
| ClassNotFound: JdbcOdbcDriver | JDK 8+ 移除橋接器 | 遷移至 mssql-jdbc 驅動 |
掌握 setBinaryStream/getBinaryStream 流式模式,即可應對任意版本 SQL Server 及主流數據庫的大對象存取需求。