1. 概述
图片、音频、视频、文档等文件可以作为二进制大对象(BLOB)存储在数据库中。BLOB是一种SQL数据类型,能将大型二进制数据作为单个实体存储。
本教程将学习如何使用Java数据库连接(JDBC)和H2内存数据库存储和检索BLOB数据。
2. 示例环境搭建
首先,在pom.xml
中添加H2依赖:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>
接着创建名为warehouses
的数据库表:
String sql = """
CREATE TABLE IF NOT EXISTS warehouses (
id INTEGER PRIMARY KEY,
name text NOT NULL,
capacity REAL,
picture BLOB
);""";
这里创建了一个包含四列的warehouses
表,其中第四列是BLOB类型,适合存储图片、PDF等二进制对象。
然后设置JDBC连接:
static Connection connect() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:h2:./test", "sa", "");
return connection;
}
此方法创建Connection
对象建立数据库连接。
最后执行建表操作:
try (Connection connection = connect(); Statement stmt = connection.createStatement()) {
stmt.execute(sql);
}
上述代码执行SQL创建表结构。
3. 将文件存储为BLOB
可通过两种方式存储文件内容:先转换为字节数组再插入,或分块流式传输文件内容。
3.1. 将文件转换为字节数组
对于小文件,转换为字节数组可能是高效选择。但此方法不适合超大文件。
首先编写文件转字节数组的方法:
static byte[] convertFileToByteArray(String filePath) throws IOException {
File file = new File(filePath);
try (FileInputStream fileInputStream = new FileInputStream(file);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
for (int len; (len = fileInputStream.read(buffer)) != -1; ) {
byteArrayOutputStream.write(buffer, 0, len);
}
return byteArrayOutputStream.toByteArray();
}
}
该方法接收文件路径参数,创建FileInputStream
和ByteArrayOutputStream
对象分别读取文件和存储字节。每次读取1024字节块,当fileInputStream
返回-1
时表示文件结束。
接下来向warehouses
表插入新记录:
boolean insertFile(int id, String name, int capacity, String picture) throws SQLException, IOException {
String insertSql = """
INSERT INTO warehouses(id,name,capacity,picture) VALUES(?,?,?,?)
""";
try (Connection conn = connect()) {
if (conn != null) {
PreparedStatement stmt = conn.prepareStatement(insertSql);
stmt.setInt(1, id);
stmt.setString(2, name);
stmt.setDouble(3, capacity);
stmt.setBytes(4, convertFileToByteArray(picture));
stmt.executeUpdate();
return true;
}
}
return false;
}
这里建立数据库连接,创建PreparedStatement
对象设置各列值。文件被转换为字节数组,通过setBytes()
方法设为BLOB数据。
单元测试演示插入操作:
@ParameterizedTest
@CsvSource({ "1, 'Liu', 3000", "2, 'Walmart', 5000" })
void givenBlobFile_whenInsertingTheBlobFileAsByteArray_thenSuccessful(
int id,
String name,
int capacity
) throws SQLException, IOException {
boolean result = jdbcConnection.insertFile(id, name, capacity, TEST_FILE_PATH);
assertTrue(result);
}
上述代码向warehouses
表添加两条记录,最后断言操作返回true。
3.2. 以流方式保存文件
处理大文件时,先转换为字节数组再保存可能效率低下。此时可采用流式处理分块保存文件。
以下示例分块保存文件到数据库:
boolean insertFileAsStream(
int id,
String name,
int capacity,
String filePath
) throws SQLException, IOException {
String insertSql = """
INSERT INTO warehouses(id,name,capacity,picture) VALUES(?,?,?,?)
""";
try (Connection conn = connect()) {
if (conn != null) {
PreparedStatement stmt = conn.prepareStatement(insertSql);
stmt.setInt(1, id);
stmt.setString(2, name);
stmt.setDouble(3, capacity);
File file = new File(filePath);
try (FileInputStream fis = new FileInputStream(file)) {
stmt.setBinaryStream(4, fis, file.length());
stmt.executeUpdate();
return true;
}
}
}
return false;
}
这里不转换图片文件为字节数组,而是通过FileInputStream
对象直接流式传输文件内容到数据库,避免全文件加载到内存。这对大文件更高效,可减少OutOfMemoryErrors
风险。
⚠️ 踩坑提示:使用流式传输时,确保在try-with-resources
中关闭文件流,避免资源泄漏。
4. 从数据库检索BLOB
接下来学习如何从数据库读取二进制数据作为输入流,并直接写入文件输出流。
以下方法从数据库检索含BLOB数据的记录:
static boolean writeBlobToFile(
String query,
int paramIndex,
int id,
String filePath
) throws IOException, SQLException {
try (
Connection connection = connect();
PreparedStatement statement = connection.prepareStatement(query)
) {
statement.setInt(paramIndex, id);
try (
ResultSet resultSet = statement.executeQuery();
FileOutputStream fileOutputStream = new FileOutputStream(new File(filePath))
) {
while (resultSet.next()) {
InputStream input = resultSet.getBinaryStream("picture");
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buffer)) > 0) {
fileOutputStream.write(buffer, 0, bytesRead);
}
}
return true;
}
}
}
此代码从数据库检索BLOB并写入文件。创建PreparedStatement
对象执行查询,从ResultSet
获取二进制流,每次读取1024字节块。注意读取操作返回实际读取字节数,可能小于缓冲区大小。
✅ 关键点:使用分块读写处理大文件,避免内存溢出。
5. 总结
本文介绍了如何将二进制文件以字节数组形式写入数据库,以及如何使用流处理大文件。还学习了从数据库检索BLOB文件并写入本地文件的方法。
完整示例代码可在GitHub获取。