杰瑞科技汇

Java如何高效读写Oracle CLOB字段?

Of course. Working with Oracle CLOBs from Java is a common but sometimes tricky task. A CLOB (Character Large Object) is designed to store large amounts of character data (up to several terabytes), making it ideal for text like XML, JSON, logs, or long descriptions.

Java如何高效读写Oracle CLOB字段?-图1
(图片来源网络,侵删)

Here’s a comprehensive guide covering the key aspects: connecting, reading, writing, and best practices.

Prerequisites: JDBC Driver

First, you need the Oracle JDBC driver (also known as the JDBC Thin Driver). The modern, recommended version is the ojdbc8.jar (for Java 8) or ojdbc11.jar (for Java 11+).

You can download it from the Oracle Maven Repository or directly from Oracle's website.

If you're using Maven, add this to your pom.xml:

Java如何高效读写Oracle CLOB字段?-图2
(图片来源网络,侵删)
<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>21.9.0.0</version> <!-- Use the latest version -->
</dependency>

Connecting to the Oracle Database

Establishing a connection is the first step. It's best practice to use a try-with-resources block to ensure your connection, statement, and result set are always closed.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class OracleConnectionUtil {
    private static final String DB_URL = "jdbc:oracle:thin:@your_host:your_port:your_service_name";
    private static final String USER = "your_username";
    private static final String PASS = "your_password";
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(DB_URL, USER, PASS);
    }
}

Reading Data from a CLOB

There are two primary methods for reading a CLOB, depending on the size of the data and your needs.

Method 1: Using Clob.getCharacterStream() (Recommended for Large CLOBs)

This is the most efficient method for large CLOBs because it streams the data character by character without loading the entire object into memory.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Clob;
public class ReadClobExample {
    public static void main(String[] args) {
        String sql = "SELECT id, document_content FROM documents WHERE id = ?";
        try (Connection conn = OracleConnectionUtil.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, 101); // The ID of the document to read
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                int id = rs.getInt("id");
                Clob documentClob = rs.getClob("document_content");
                if (documentClob != null) {
                    // Use getCharacterStream() to read the CLOB as a stream
                    try (Reader reader = documentClob.getCharacterStream();
                         BufferedReader br = new BufferedReader(reader)) {
                        StringBuilder content = new StringBuilder();
                        String line;
                        while ((line = br.readLine()) != null) {
                            content.append(line).append("\n");
                        }
                        System.out.println("Document ID: " + id);
                        System.out.println("Content:\n" + content.toString());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                System.out.println("No document found with ID 101.");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Method 2: Using Clob.getSubString() (Convenient for Small CLOBs)

This method is simpler but can cause memory issues if the CLOB is very large (e.g., hundreds of megabytes or gigabytes), as it loads the entire CLOB content into a single String.

// ... inside the if (rs.next()) block ...
Clob documentClob = rs.getClob("document_content");
if (documentClob != null) {
    // Be cautious with this method for large CLOBs!
    String content = documentClob.getSubString(1, (int) documentClob.length());
    System.out.println("Content (from getSubString):\n" + content);
}

Writing Data to a CLOB

Writing to a CLOB is a bit more complex than reading. You cannot simply do pstmt.setString(...). You must use a PreparedStatement and a special trick involving the empty_clob() function.

The standard procedure is:

  1. Insert a row with an empty CLOB (empty_clob()).
  2. Immediately retrieve the CLOB locator for that new row.
  3. Use the locator to get a java.sql.Clob object.
  4. Write your data to this Clob object.
  5. Update the row with the now-populated CLOB.
import java.io.IOException;
import java.io.Writer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Clob;
public class WriteClobExample {
    public static void main(String[] args) {
        String insertSql = "INSERT INTO documents (id, name, document_content) VALUES (doc_seq.NEXTVAL, 'My Report', EMPTY_CLOB())";
        String selectSql = "SELECT document_content FROM documents WHERE id = ? FOR UPDATE"; // FOR UPDATE is crucial
        String updateSql = "UPDATE documents SET document_content = ? WHERE id = ?";
        String largeText = "This is the first line of my large document.\n"
                + "This is the second line, and it contains some important data.\n"
                + "This process can handle thousands of lines of text...\n"
                + "...and so on, for as long as needed.";
        try (Connection conn = OracleConnectionUtil.getConnection()) {
            conn.setAutoCommit(false); // Start transaction
            // Step 1: Insert a row with an empty CLOB
            try (PreparedStatement pstmt = conn.prepareStatement(insertSql, new String[]{"id"})) {
                pstmt.executeUpdate();
                // Get the generated ID (optional, but good practice)
                try (ResultSet rs = pstmt.getGeneratedKeys()) {
                    if (rs.next()) {
                        int newId = rs.getInt(1);
                        System.out.println("Inserted new document with ID: " + newId);
                        // Step 2: Retrieve the CLOB locator for the new row
                        try (PreparedStatement selectPstmt = conn.prepareStatement(selectSql)) {
                            selectPstmt.setInt(1, newId);
                            ResultSet clobRs = selectPstmt.executeQuery();
                            if (clobRs.next()) {
                                // Step 3: Get the Clob object
                                Clob documentClob = clobRs.getClob("document_content");
                                if (documentClob != null) {
                                    // Step 4: Write data to the Clob object
                                    try (Writer writer = documentClob.setCharacterStream(1)) {
                                        writer.write(largeText);
                                    } catch (IOException e) {
                                        conn.rollback();
                                        e.printStackTrace();
                                        return;
                                    }
                                }
                            }
                        }
                        // Step 5: Update the row (this step is often implicit in some drivers,
                        // but it's good to be explicit. The write above usually locks the row).
                        // In many cases, the commit is all that's needed.
                    }
                }
            }
            // Commit the transaction
            conn.commit();
            System.out.println("CLOB successfully written and committed.");
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                if (conn != null) conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }
}

Note: The FOR UPDATE clause in the SELECT statement is essential. It locks the row, preventing other transactions from modifying it while you are writing the CLOB data.


Updating Data in a CLOB

Updating is very similar to writing. You use the same setCharacterStream method on the Clob object you retrieve from the SELECT ... FOR UPDATE statement.

// Assume you have an existing document ID
int documentIdToUpdate = 101;
String newContent = "This is the updated content for the document.";
String selectSql = "SELECT document_content FROM documents WHERE id = ? FOR UPDATE";
String updateSql = "UPDATE documents SET document_content = ? WHERE id = ?"; // May not be needed depending on driver
try (Connection conn = OracleConnectionUtil.getConnection()) {
    conn.setAutoCommit(false);
    try (PreparedStatement pstmt = conn.prepareStatement(selectSql)) {
        pstmt.setInt(1, documentIdToUpdate);
        ResultSet rs = pstmt.executeQuery();
        if (rs.next()) {
            Clob clob = rs.getClob("document_content");
            try (Writer writer = clob.setCharacterStream(1)) {
                writer.write(newContent);
            }
            // The driver may automatically update the database when the writer is closed.
            // If not, you might need an explicit UPDATE statement.
        }
    }
    conn.commit();
    System.out.println("CLOB updated successfully.");
} catch (SQLException | IOException e) {
    e.printStackTrace();
    // ... rollback logic ...
}

Best Practices and Key Considerations

  1. Resource Management: Always use try-with-resources for Connection, Statement, ResultSet, Reader, and Writer. This prevents resource leaks.
  2. Transactions: Wrap CLOB operations in a transaction (conn.setAutoCommit(false) and conn.commit()). This ensures data integrity, especially for the multi-step write process.
  3. Memory Usage: For large CLOBs, always use getCharacterStream() and setCharacterStream(). Avoid getSubString() and setString() as they can cause OutOfMemoryError.
  4. FOR UPDATE: When you need to read a CLOB for the purpose of updating it, always use SELECT ... FOR UPDATE. This locks the row and prevents other sessions from changing it while you're writing.
  5. Connection Pooling: In a real application, never create a new connection for every request. Use a connection pool (like HikariCP, Apache DBCP) to manage database connections efficiently.
  6. NCHAR/NCLOB: If your database uses national character sets (e.g., AL32UTF8), you might encounter NCLOB. The usage is identical to CLOB, just replace Clob with NClob. Most of the time, Clob is what you need.
分享:
扫描分享到社交APP
上一篇
下一篇