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

JDBC 操作 SQL Server 大對象進階:二進制流完整讀寫與圖片驗證

     編輯:Java 程式設計入門

承接上文,本文提供一個完整可運行的單檔案程式,演示 SQL Server VARBINARY(MAX) 欄位的圖片寫入、讀取、二進制完整性驗證全流程。代碼採用 jTDS 驅動、ODBC 數據源配置,適合快速驗證、教學演示、原型開發。

完整程式碼:BlobImageDemo.java

import java.io.*;
import java.sql.*;

public class BlobImageDemo {
    // 配置區
    private static final String JDBC_URL = "jdbc:odbc:LW"; // 系統 ODBC DSN 名稱
    private static final String TABLE = "Ss";
    private static final String ID_COL = "id";
    private static final String DATA_COL = "data";
    private static final String TEST_ID = "0012";
    private static final File SOURCE_IMG = new File("D:\\test1.jpg");
    private static final File TARGET_IMG = new File("D:\\test2.jpg");
    private static final int BUFFER_SIZE = 50000; // 50KB
    
    public static void main(String[] args) {
        BlobImageDemo demo = new BlobImageDemo();
        demo.insertImage();
        demo.extractAndVerify();
    }
    
    // 寫入圖片
    public void insertImage() {
        String sql = "INSERT INTO " + TABLE + " (" + ID_COL + ", " + DATA_COL + ") VALUES (?, ?)";
        try (Connection conn = DriverManager.getConnection(JDBC_URL);
             PreparedStatement pstmt = conn.prepareStatement(sql);
             InputStream fin = new FileInputStream(SOURCE_IMG)) {
            
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // JDK 8 內建
            
            pstmt.setString(1, TEST_ID);
            pstmt.setBinaryStream(2, fin, (int) SOURCE_IMG.length());
            pstmt.executeUpdate();
            System.out.println("✅ 圖片寫入成功: " + SOURCE_IMG.length() + " bytes");
        } catch (Exception e) {
            System.err.println("❌ 寫入失敗: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 讀取並驗證
    public void extractAndVerify() {
        String sql = "SELECT " + DATA_COL + " FROM " + TABLE + " WHERE " + ID_COL + " = ?";
        try (Connection conn = DriverManager.getConnection(JDBC_URL);
             PreparedStatement pstmt = conn.prepareStatement(sql);
             OutputStream out = new BufferedOutputStream(new FileOutputStream(TARGET_IMG), BUFFER_SIZE)) {
            
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            pstmt.setString(1, TEST_ID);
            
            try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    try (InputStream in = rs.getBinaryStream(DATA_COL)) {
                        if (in == null) {
                            System.err.println("❌ 數據為 NULL");
                            return;
                        }
                        byte[] buffer = new byte[BUFFER_SIZE];
                        int len;
                        long total = 0;
                        while ((len = in.read(buffer)) != -1) {
                            out.write(buffer, 0, len);
                            total += len;
                        }
                        out.flush();
                        System.out.println("✅ 圖片讀取成功: " + total + " bytes");
                        
                        // 二進制完整性驗證
                        verifyBinaryEqual(SOURCE_IMG, TARGET_IMG);
                    }
                } else {
                    System.err.println("❌ 未找到記錄: " + TEST_ID);
                }
            }
        } catch (Exception e) {
            System.err.println("❌ 讀取失敗: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 位元組級比對驗證
    private void verifyBinaryEqual(File f1, File f2) throws IOException {
        if (f1.length() != f2.length()) {
            System.err.println("❌ 文件大小不一致: " + f1.length() + " vs " + f2.length());
            return;
        }
        try (InputStream in1 = new FileInputStream(f1);
             InputStream in2 = new FileInputStream(f2)) {
            int b1, b2;
            long pos = 0;
            while ((b1 = in1.read()) != -1) {
                b2 = in2.read();
                if (b1 != b2) {
                    System.err.println("❌ 位元組差異 at position " + pos + ": " + b1 + " vs " + b2);
                    return;
                }
                pos++;
            }
            System.out.println("✅ 二進制內容完全一致,驗證通過!");
        }
    }
}

ODBC 數據源配置步驟 (Windows)

  1. 控制台 → 管理工具 → ODBC 數據源 (32位/64位) (匹配 JDK 位數)
  2. 系統 DSN 頁籤 → 新增 → 選擇 SQL Server 或 SQL Server Native Client
  3. 名稱: LW (與代碼 jdbc:odbc:LW 對應)
  4. 伺服器: localhost 或 IP
  5. 認證: SQL Server 認證 (sa/密碼) 或 Windows 認證
  6. 預設資料庫: 選擇目標資料庫
  7. 完成 → 測試數據源 確認連通

資料表準備

CREATE TABLE Ss (
    id CHAR(4) PRIMARY KEY,
    data VARBINARY(MAX) NOT NULL
);

執行與驗證

  1. 確保 D:\test1.jpg 存在
  2. 編譯:javac BlobImageDemo.java
  3. 運行:java BlobImageDemo
  4. 觀察輸出:寫入位元組數、讀取位元組數、二進制比對結果
  5. 檢查 D:\test2.jpg 是否可正常開啟、與原圖一致

關鍵技術點回顧

環節關鍵 API作用
寫入pstmt.setBinaryStream(2, fin, length)流式傳輸,避免 byte[] 全量入堆
讀取rs.getBinaryStream("data")獲取資料庫游標流
緩衝BufferedOutputStream + byte[50000]減少系統調用,提升寫入吞吐
資源管理try-with-resources (Java 7+)自動關閉 Connection/Statement/ResultSet/Stream
驗證位元組級比對證明寫入/讀取無損、無截斷、無編碼轉換

常見問題排查

  • 寫入成功但讀取為空:檢查 executeUpdate() 返回值、事務是否提交 (ODBC 連線預設 auto-commit=true)
  • ClassNotFoundException: JdbcOdbcDriver:JDK 9+ 已移除,需 JDK 8 或遷移至 jTDS/JDBC 直連
  • 文件大小為 0:FileInputStream 路徑錯誤、文件鎖定、長度超過 int 範圍 (>2GB)
  • 圖片損壞無法開啟:緩衝區未 flush()、流關閉順序錯誤、資料庫欄位類型非 VARBINARY/IMAGE

生產環境遷移清單

  • [ ] 替換 JdbcOdbcDriver → jTDS / mssql-jdbc
  • [ ] 移除 ODBC DSN 依賴 → JDBC URL 直連
  • [ ] 引入 HikariCP 連線池
  • [ ] 封裝為 Spring JdbcTemplate / MyBatis @Insert / JPA @Lob
  • [ ] 加入事務邊界 @Transactional
  • [ ] 異常轉換為業務異常、統一日誌、監控指標

總結

本文提供了最小可驗證閉環:從 ODBC 配置、建表、寫入、讀取、到二進制完整性驗證。掌握 setBinaryStream/getBinaryStream + 緩衝區分塊 + try-with-resources 這套核心模式,即可應對生產環境 99% 的大對象讀寫場景。驗證通過後,按遷移清單逐步引入企業級基礎設施,實現從原型到生產的平滑演進。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved