使用 Spring JdbcTemplate 插入 BLOB
1. 概述
在使用關係型資料庫時,我們經常需要儲存二進位大型物件(BLOB),例如文件、影像和其他媒體。 Spring 的JdbcTemplate提供了一個輕量級、靈活的 API,用於執行這些操作,而無需使用複雜的 ORM 解決方案。
在本教學中,我們將探討使用JdbcTemplate.
2. 問題介紹
使用正確的 JDBC API,在關聯式資料庫中儲存二進位資料非常簡單。
然而,根據檔案大小、資料庫供應商和驅動程式的具體情況,插入策略可能會有所不同。對於小型或中型 BLOB,直接設定位元組數組就足夠了。對於大型內容,使用串流會更節省記憶體。在某些情況下,我們可能需要 Spring 的SqlLobValue和LobHandler 。此外,我們也可以使用 Spring 的SqlBinaryValue來解決這個問題。
像往常一樣,我們將透過範例來探討這些方法。首先,讓我們在 Spring Boot 應用程式中設定一個記憶體中的 H2 資料庫。然後,我們將使用簡單、重點突出的 JUnit 測試案例來逐一示範每種方案。
為了簡單起見,本教學將省略資料庫和 Spring 配置。
現在,讓我們建立一個create-document-table.sql SQL 腳本,其中包含建立表格的語句:
CREATE TABLE DOCUMENT
(
ID INT PRIMARY KEY,
FILENAME VARCHAR(255) NOT NULL,
DATA BLOB
);
如我們所見,在DOCUMENT表中, DATA's類型是BLOB 。我們將在整個範例中使用DOCUMENT表。
為了保持測試環境的整潔,我們也定義了一個對應的清理腳本drop-document-table.sql:
DROP TABLE DOCUMENT;
我們將在@Sql註解中使用這兩個腳本:
@Sql(value = "/com/baeldung/spring/jdbc/blob/create-document-table.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(value = "/com/baeldung/spring/jdbc/blob/drop-document-table.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@SpringBootTest(classes = InsertBlobUsingJdbcTemplateApplication.class)
@TestPropertySource(locations = { "classpath:com/baeldung/spring/jdbc/blob/application.properties" })
class InsertBlobUsingJdbcTemplateUnitTest {
@Autowired
private JdbcTemlate jdbcTemplate;
private static final String CONTENT = "I am a very very long content.";
}
如程式碼所示,我們在測試類別上新增了兩個@Sql註解。透過在每個測試方法之前執行建立表格腳本,並在之後執行刪除表腳本,我們確保每個測試都從一個可預測的、乾淨的資料庫模式開始。這使得測試環境完全隔離且可重複。
此外,我們創建了CONTENT常數來模擬文件內容。稍後,我們將使用不同的方法將CONTENT's值插入DOCUMENT表的DATA列中。
3. 使用byte數組
第一種也是最直接的方法是使用PreparedStatement.setBytes().這種方法會將內容轉換為byte[]並直接儲存到資料庫中。當 BLOB 的大小足夠小,可以輕鬆放入記憶體時,這種方法非常理想。
接下來,我們來看看它是如何運作的:
byte[] bytes = CONTENT.getBytes(StandardCharsets.UTF_8);
jdbcTemplate.update(
"INSERT INTO DOCUMENT (ID, FILENAME, DATA) VALUES (?, ?, ?)",
1,
"bigfile.txt",
bytes
);
byte[] stored = jdbcTemplate.queryForObject("SELECT DATA FROM DOCUMENT WHERE ID = 1", (rs, rowNum) -> rs.getBytes("data"));
assertEquals(CONTENT, new String(stored, StandardCharsets.UTF_8));
由於我們一次傳遞整個位元組數組,資料庫驅動程式無需串流傳輸資料。它只需將整個數組寫入 BLOB 列即可。這樣可以最大限度地減少樣板程式碼,並且能夠在所有主流 JDBC 驅動程式上可靠地運作。
如我們所見,這種方法簡單明了,可讀性強,適用於許多使用場景,例如短文字檔案、小圖像或幾兆位元組以下的任何二進位資料。
4. 使用InputStream
第二種方法是使用二進位流而不是將所有位元組載入到記憶體中。透過PreparedStatement.setBinaryStream(),我們將一個InputStream傳遞給 JDBC 驅動程序,然後驅動程式會將資料直接串流到 BLOB 欄位中。
這種技巧在以下情況尤其有用:
- 文件很大(例如,幾十或幾百兆位元組)。
- 內容源自流(例如,文件上傳、網路流、記憶體高效能管道)
- 我們希望避免過高的記憶體消耗。
接下來,讓我們來看看這種方法是如何應用的:
InputStream stream = new ByteArrayInputStream(CONTENT.getBytes(StandardCharsets.UTF_8));
jdbcTemplate.update(
"INSERT INTO DOCUMENT (ID, FILENAME, DATA) VALUES (?, ?, ?)",
2,
"bigfile.txt",
stream
);
byte[] stored = jdbcTemplate.queryForObject("SELECT DATA FROM DOCUMENT WHERE ID = 2", (rs, rowNum) -> rs.getBytes("data"));
assertEquals(CONTENT, new String(stored, StandardCharsets.UTF_8));
我們的測試透過建立一個包含內容的InputStream並將其傳遞給插入操作來模擬這種情況。
串流傳輸更具可擴展性,因為它避免了應用程式在記憶體中保存大型數組。相反,我們傳遞流,並讓資料庫驅動程式以增量方式管理讀寫操作。
當資料來源本身已經是流時,例如透過 REST 端點上傳的文件,此選項非常方便。
5. 使用 Spring 的SqlLobValue和LobHandler (Spring 6.2 中已棄用)
在 Spring 6.2 之前,將 BLOB 資料插入資料庫的另一種方法是使用SqlLobValue和LobHandler提供了不同的LobHandler實現,其中最常用的是DefaultLobHandler,用於抽象化特定於供應商的 LOB 操作。
儘管這種方法仍然有效,但 Spring 6.2 中已棄用SqlLobValue和LobHandler ,取而代之的是更新的SqlBinaryValue API。不過,許多現有的 Spring 應用程式仍然使用這種模式,因此了解它仍然很有用。順便一提,我們稍後會探討SqlBinaryValue方法。
接下來,我們來看一個SqlLobValue和LobHandler如何完成這項工作的範例:
byte[] bytes = CONTENT.getBytes(StandardCharsets.UTF_8);
jdbcTemplate.update(
"INSERT INTO DOCUMENT (ID, FILENAME, DATA) VALUES (?, ?, ?)",
new Object[] { 3, "bigfile.txt", new SqlLobValue(bytes, new DefaultLobHandler()) },
new int[] { Types.INTEGER, Types.VARCHAR, Types.BLOB }
);
byte[] stored = jdbcTemplate.queryForObject("SELECT DATA FROM DOCUMENT WHERE ID = 3", (rs, rowNum) -> rs.getBytes("DATA"));
assertEquals(CONTENT, new String(stored, StandardCharsets.UTF_8));
在這個範例中,我們將SqlLobValue實例作為參數傳遞,並單獨提供 SQL 類型。
6. 使用 Spring 的SqlBinaryValue
自 Spring 6.2 起,建議使用SqlBinaryValue插入二進位數據,而不是使用已棄用的SqlLobValue / LobHandler API。
SqlBinaryValue直接與 Spring 的 JDBC 參數框架集成,這意味著我們將其包裝在SqlParameterValue中,並附上相應的 SQL 類型:
byte[] bytes = CONTENT.getBytes(StandardCharsets.UTF_8);
jdbcTemplate.update(
"INSERT INTO DOCUMENT (ID, FILENAME, DATA) VALUES (?, ?, ?)",
4,
"bigfile.txt",
new SqlParameterValue(Types.BLOB, new SqlBinaryValue(bytes))
);
byte[] stored = jdbcTemplate.queryForObject("SELECT DATA FROM DOCUMENT WHERE ID = 4", (rs, rowNum) -> rs.getBytes("DATA"));
assertEquals(CONTENT, new String(stored, StandardCharsets.UTF_8));
在這個例子中, SqlBinaryValue封裝了原始的byte[]數組,並為驅動程式提供了一個乾淨的二進位表示法。此外,我們還將其封裝在SqlParameterValue,以便指定 JDBC 類型( Types.BLOB )。
7. 結論
本文探討了使用 Spring 的JdbcTemplate.透過一系列 JUnit 測試案例,我們演示了每種方法的獨立工作原理。
透過了解這些選項,我們可以為我們特定的資料庫和應用程式需求選擇合適的策略。
與往常一樣,範例的完整原始程式碼可在 GitHub 上找到。