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

Java 操作 Oracle BLOB 字段完整示例:文件讀寫數據庫

     編輯:Java 程式設計入門

前言

Oracle BLOB (Binary Large Object) 用於存儲二進制大對象(圖片、文檔、視頻等)。本文提供基於 JDBC 的完整工具類,實現文件寫入 BLOB 字段及從 BLOB 讀取還原文件,採用單例模式管理數據庫連接,支援事務控制。

數據庫表結構假設

CREATE TABLE PS_ZP_PRJ_WBS_BLOB (
    ZP_PRJ_ID    VARCHAR2(50),   -- 項目ID
    ZZ_SEQ_NUM   VARCHAR2(50),   -- 任務序號
    ZZ_FILE_NAME VARCHAR2(200),  -- 文件名
    ZZ_IMAGE_BLOB BLOB           -- 二進制數據
);

核心工具類實現

package com.example.blob;

import oracle.sql.BLOB;
import java.io.*;
import java.sql.*;
import java.util.Properties;

public class BlobUtil {

    // 單例模式
    private static BlobUtil instance;
    private String envProfile; // 環境標識:dev/test/prod
    
    private BlobUtil() {}
    
    public static synchronized BlobUtil getInstance(String env) {
        if (instance == null) instance = new BlobUtil();
        instance.envProfile = env;
        return instance;
    }

    /** 獲取數據庫連接(從配置文件讀取) */
    private Connection getConnection() throws Exception {
        Properties props = new Properties();
        try (InputStream is = getClass().getClassLoader().getResourceAsStream("config.properties")) {
            props.load(is);
        }
        
        String url = String.format("jdbc:oracle:thin:@%s:%s:%s",
            props.getProperty(envProfile + ".host"),
            props.getProperty(envProfile + ".port"),
            props.getProperty(envProfile + ".database"));
        
        return DriverManager.getConnection(url,
            props.getProperty(envProfile + ".username"),
            props.getProperty(envProfile + ".password"));
    }

    /**
     * 寫入文件到 BLOB 字段
     * @return true 成功,false 失敗
     */
    public boolean writeBlob(String projectId, String taskId, String fileName, String filePath) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        BLOB blob = null;
        
        try {
            conn = getConnection();
            conn.setAutoCommit(false); // 開啟事務
            
            // 1. 插入空 BLOB 佔位
            String sql = "INSERT INTO PS_ZP_PRJ_WBS_BLOB (ZP_PRJ_ID, ZZ_SEQ_NUM, ZZ_FILE_NAME, ZZ_IMAGE_BLOB) " +
                         "VALUES (?, ?, ?, empty_blob())";
            ps = conn.prepareStatement(sql);
            ps.setString(1, projectId);
            ps.setString(2, taskId);
            ps.setString(3, fileName);
            ps.executeUpdate();
            ps.close();
            
            // 2. 鎖定該行並獲取 BLOB 定位器
            sql = "SELECT ZZ_IMAGE_BLOB FROM PS_ZP_PRJ_WBS_BLOB " +
                  "WHERE ZP_PRJ_ID = ? AND ZZ_SEQ_NUM = ? AND ZZ_FILE_NAME = ? FOR UPDATE";
            ps = conn.prepareStatement(sql);
            ps.setString(1, projectId);
            ps.setString(2, taskId);
            ps.setString(3, fileName);
            rs = ps.executeQuery();
            
            if (rs.next()) {
                blob = (BLOB) rs.getBlob(1);
            } else {
                throw new SQLException("插入後未查詢到 BLOB 定位器");
            }
            rs.close();
            ps.close();
            
            // 3. 流式寫入二進制數據
            try (InputStream in = new FileInputStream(filePath);
                 OutputStream out = blob.setBinaryStream(1L)) { // 1-based 位置
                
                byte[] buffer = new byte[8192]; // 8KB 緩衝區
                int len;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
            }
            
            conn.commit();
            return true;
            
        } catch (Exception e) {
            rollbackQuietly(conn);
            e.printStackTrace();
            return false;
        } finally {
            closeQuietly(rs, ps, conn);
        }
    }

    /**
     * 從 BLOB 讀取文件到本地
     */
    public boolean readBlob(String projectId, String taskId, String fileName, String destPath) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        BLOB blob = null;
        
        try {
            conn = getConnection();
            
            String sql = "SELECT ZZ_IMAGE_BLOB FROM PS_ZP_PRJ_WBS_BLOB " +
                         "WHERE ZP_PRJ_ID = ? AND ZZ_SEQ_NUM = ? AND ZZ_FILE_NAME = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, projectId);
            ps.setString(2, taskId);
            ps.setString(3, fileName);
            rs = ps.executeQuery();
            
            if (rs.next()) {
                blob = (BLOB) rs.getBlob(1);
            } else {
                System.err.println("未找到對應記錄");
                return false;
            }
            
            try (InputStream in = blob.getBinaryStream();
                 FileOutputStream out = new FileOutputStream(destPath)) {
                
                byte[] buffer = new byte[8192];
                int len;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
            }
            return true;
            
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            closeQuietly(rs, ps, conn);
        }
    }

    // ========== 輔助方法 ==========
    private void rollbackQuietly(Connection conn) {
        if (conn != null) {
            try { conn.rollback(); } catch (SQLException ignored) {}
        }
    }
    
    private void closeQuietly(AutoCloseable... resources) {
        for (AutoCloseable r : resources) {
            if (r != null) {
                try { r.close(); } catch (Exception ignored) {}
            }
        }
    }

    // 測試主方法
    public static void main(String[] args) {
        BlobUtil util = BlobUtil.getInstance("dev");
        boolean writeOk = util.writeBlob("PRJ001", "TASK01", "logo.png", "D:/images/logo.png");
        System.out.println("寫入結果:" + writeOk);
        
        boolean readOk = util.readBlob("PRJ001", "TASK01", "logo.png", "D:/temp/logo_copy.png");
        System.out.println("讀取結果:" + readOk);
    }
}

配置文件 config.properties

dev.host=localhost
dev.port=1521
dev.database=ORCL
dev.username=scott
dev.password=tiger

prod.host=192.168.1.100
prod.port=1521
prod.database=PRODDB
prod.username=app_user
prod.password=******

關鍵技術點解析

1. empty_blob() 與 FOR UPDATE

Oracle 要求先插入 empty_blob() 創建 BLOB 定位器,然後通過 SELECT ... FOR UPDATE 鎖定行並獲取可寫的 BLOB 對象。直接插入數據會報錯 "ORA-22275: invalid LOB locator"。

2. 流式處理大文件

使用 blob.setBinaryStream(pos) 和 blob.getBinaryStream() 進行流式讀寫,配合 8KB 緩衝區,避免大文件載入內存導致 OOM。單次寫入支持 2GB 以上文件(受限於 Oracle BLOB 最大 4GB)。

3. 事務控制

寫入操作包含插入、查詢、流寫入三步,必須在同一事務中完成。設置 conn.setAutoCommit(false),成功後 commit(),異常時 rollback()。

4. Oracle 專用 BLOB 類型

代碼中強轉 (BLOB) rs.getBlob(1) 使用 oracle.sql.BLOB 而非標準 java.sql.Blob,是為了調用 setBinaryStream 等 Oracle 擴展方法。若追求可移植性,可用標準 JDBC 4.0 Blob.setBinaryStream(long)(JDK 1.6+ 支持)。

常見問題排查

  • ClassNotFoundException:確保引入 ojdbc8.jar(JDK 8+)或對應版本驅動包
  • ORA-01031 權限不足:用戶需具備 CREATE TABLE、INSERT、SELECT FOR UPDATE 權限
  • 流未關閉導致連接泄漏:務必在 finally 塊或 try-with-resources 中關閉所有流、Statement、Connection
  • 中文文件名亂碼:數據庫字符集設置為 AL32UTF8,JDBC URL 加入 oracle.jdbc.defaultNChar=true

總結

本示例提供了生產級的 Oracle BLOB 讀寫工具類,涵蓋連接池預留、事務管理、資源安全釋放、大文件流式處理等最佳實踐。實際項目中建議整合連接池(HikariCP/Druid)並添加重試機制、進度回調等增強功能。

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