首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 讀取資料庫 Blob 圖片並輸出顯示:JDBC 流式讀取、Base64 嵌入與 Spring MVC 響應式輸出

     編輯:Java 相關知識

資料庫 BLOB 型別存儲圖片二進位資料,Java 讀取後需高效轉為前端可用格式。本文示範三種主流方案:JDBC InputStream 直輸 Servlet 輸出流、Base64 編碼嵌入 HTML/JSON、Spring MVC ResponseEntity/StreamingResponseBody 響應式輸出,並解決大圖記憶體溢出、連接洩漏、快取頭部設置等生產問題。

資料表結構

CREATE TABLE product_image (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    product_id BIGINT NOT NULL,
    image_data LONGBLOB NOT NULL,      -- MySQL 大圖用 LONGBLOB (4GB)
    mime_type VARCHAR(50) NOT NULL,    -- image/jpeg, image/png, image/webp
    file_name VARCHAR(200),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

方案一:Servlet 原生流式輸出 (最省記憶體)


@WebServlet("/image/raw/*")
public class RawImageServlet extends HttpServlet {
    private final DataSource dataSource;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String pathInfo = req.getPathInfo(); // /123
        if (pathInfo == null || pathInfo.length() <= 1) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        Long id = Long.valueOf(pathInfo.substring(1));

        String sql = "SELECT image_data, mime_type FROM product_image WHERE id = ?";
        try (Connection conn = dataSource.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {

            ps.setLong(1, id);
            try (ResultSet rs = ps.executeQuery()) {
                if (!rs.next()) {
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
                    return;
                }
                // 關鍵:getBinaryStream() 返回 InputStream,避免 getBytes() 全量載入記憶體
                try (InputStream is = rs.getBinaryStream("image_data")) {
                    resp.setContentType(rs.getString("mime_type"));
                    resp.setHeader("Cache-Control", "public, max-age=31536000"); // 1 年快取
                    resp.setContentLengthLong(rs.getLong("content_length")); // 若表有長度欄位
                    // 零拷貝輸出 (Servlet 3.1+ 支援)
                    is.transferTo(resp.getOutputStream());
                }
            }
        } catch (SQLException e) {
            throw new UncheckedSQLException(e);
        }
    }
}

方案二:Base64 編碼嵌入 (適合小圖、API 回傳 JSON)


@RestController
@RequestMapping("/api/images")
public class ImageApiController {
    private final JdbcTemplate jdbcTemplate;

    @GetMapping("/{id}/base64")
    public Map getBase64(@PathVariable Long id) {
        String sql = "SELECT image_data, mime_type, file_name FROM product_image WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
            byte[] bytes = rs.getBytes("image_data"); // 小圖才用 getBytes
            String base64 = Base64.getEncoder().encodeToString(bytes);
            String dataUri = "data:" + rs.getString("mime_type") + ";base64," + base64;
            return Map.of(
                "id", id,
                "dataUri", dataUri,           // 直接用於 
                "fileName", rs.getString("file_name"),
                "size", bytes.length
            );
        }, id);
    }
}

⚠️ **限制**:Base64 體積膨脹 33%,大圖會導致 JSON 過大、記憶體飆升、傳輸緩慢。**僅建議 < 50KB 圖標/縮圖使用**。

方案三:Spring MVC 響應式流式輸出 (大圖、高併發首選)


@RestController
@RequestMapping("/images")
public class ReactiveImageController {
    private final DataSource dataSource;

    @GetMapping("/{id}")
    public ResponseEntity streamImage(@PathVariable Long id) {
        String sql = "SELECT image_data, mime_type FROM product_image WHERE id = ?";
        return jdbcTemplate.query(sql, ps -> ps.setLong(1, id), rs -> {
            if (!rs.next()) {
                return ResponseEntity.notFound().build();
            }
            final InputStream is = rs.getBinaryStream("image_data");
            final String mimeType = rs.getString("mime_type");

            StreamingResponseBody body = out -> {
                try (is; out) {
                    is.transferTo(out); // 非阻塞、背壓感知
                }
            };
            return ResponseEntity.ok()
                    .contentType(MediaType.parseMediaType(mimeType))
                    .header("Cache-Control", "public, max-age=31536000")
                    .body(body);
        });
    }
}

優勢:StreamingResponseBody 整合 Spring MVC 異步 Servlet 支援,配合 Tomcat NIO / Undertow 非阻塞 IO,單執行緒可支撐數萬併發圖片下載。

大圖分片上傳與斷點續傳 (補充)

  • 前端:spark-md5 計算分片哈希 → 並發上傳分片 → 合併通知
  • 後端:臨時目錄儲存分片 → 合併時 Files.copy(part, merged, StandardCopyOption.REPLACE_EXISTING) → 寫入 BLOB 或對象存儲 (S3/MinIO)
  • 資料庫僅存儲元數據 (ID、路徑、MD5),**生產環境強烈建議圖片存對象存儲,DB 存 URL**,避免 BLOB 膨脹備份/遷移痛苦

避坑清單

結語

讀取 Blob 圖片的核心鐵律:**流式讀取、流式輸出、零全量載入**。Servlet 3.1+ transferTo、Spring StreamingResponseBody、對象存儲分離是現代架構的三板斧。小圖才用 Base64,大圖堅決走流與雲存儲。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
坑點後果解決
用 getBytes() 讀大圖Heap OOM必須用 getBinaryStream() + transferTo()
未關閉 ResultSet/Connection連接池洩漏、資料庫連接耗盡try-with-resources 強制關閉
未設 Content-Type瀏覽器無法正確渲染/下載從 DB 讀 mime_type 動態設置
無快取頭部重複請求壓垮 DB/頻寬Cache-Control: public, max-age=31536000 + ETag/Last-Modified
BLOB 存超大圖 (>100MB)備份慢、遷移難、GC 壓力大遷移至對象存儲 (S3/MinIO/OSS),DB 存預簽名 URL