在許多應用系統中,需要將圖片、文檔等二進制文件直接存儲在資料庫中。MySQL 提供 BLOB(Binary Large Object)類型支援此需求。本文詳細說明 BLOB 類型選擇、Java 讀寫代碼實作、常見問題與最佳實踐。
四種 BLOB 類型僅最大儲存長度不同,請根據文件大小選擇:
| 類型 | 最大長度 | 適用場景 |
|---|---|---|
| TINYBLOB | 255 bytes | 極小圖示、縮圖 |
| BLOB | 64 KB | 小圖片、配置文件 |
| MEDIUMBLOB | 16 MB | 一般照片、文檔 |
| LONGBLOB | 4 GB | 高解析度影像、大型文件 |
CREATE TABLE phototest (
id INT PRIMARY KEY,
photo LONGBLOB NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
import java.io.*;
import java.sql.*;
public class ImageBlobDemo {
// 資料庫連線配置(實際專案請使用連線池與外部配置)
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "password";
public static void main(String[] args) {
String sourceImage = "source.png";
String targetImage = "retrieved.jpg";
int recordId = 1;
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {
// 1. 存入圖片
storeImage(conn, recordId, sourceImage);
System.out.println("圖片存入成功");
// 2. 讀取圖片
retrieveImage(conn, recordId, targetImage);
System.out.println("圖片讀取成功:" + targetImage);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 將本地圖片存入資料庫
*/
public static void storeImage(Connection conn, int id, String imagePath)
throws SQLException, IOException {
String sql = "INSERT INTO phototest (id, photo) VALUES (?, ?) " +
"ON DUPLICATE KEY UPDATE photo = ?";
try (PreparedStatement ps = conn.prepareStatement(sql);
FileInputStream fis = new FileInputStream(imagePath)) {
ps.setInt(1, id);
ps.setBinaryStream(2, fis, fis.available());
ps.setBinaryStream(3, fis, fis.available()); // 更新用
ps.executeUpdate();
}
}
/**
* 從資料庫讀取圖片至本地
*/
public static void retrieveImage(Connection conn, int id, String outputPath)
throws SQLException, IOException {
String sql = "SELECT photo FROM phototest WHERE id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql);
FileOutputStream fos = new FileOutputStream(outputPath)) {
ps.setInt(1, id);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
try (InputStream is = rs.getBinaryStream("photo")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
} else {
throw new SQLException("找不到 ID=" + id + " 的記錄");
}
}
}
}
}
避免一次性將大文件載入記憶體(setBytes()/getBytes()),流式處理支援任意大小文件,記憶體佔用恆定。
若遇到 Data truncation: Data too long for column,請檢查:
max_allowed_packet 是否足夠(預設 4MB,大文件需調大)生產環境必須使用 HikariCP、Druid 等連線池,並利用 try-with-resources 確保連線、流正確關閉。
| 策略 | 優點 | 缺點 | 建議場景 |
|---|---|---|---|
| 資料庫存 BLOB | 事務一致性、備份簡單、權限控管容易 | 資料庫膨脹、備份慢、讀取效能較差 | 小文件、強一致性需求、文件少 |
| 資料庫存路徑,文件存檔案系統/物件儲存 | 資料庫輕量、讀取快、擴展性好 | 需處理分散式一致性、備份複雜 | 大文件、高併發、雲原生架構 |
| 混合策略 | 平衡兩者優點 | 邏輯較複雜 | 核心業務文件存 DB,非核心存外部 |
getBytes() 載入大文件wait_timeout、net_read_timeout 參數