Как писать / обновлять Oracle blob надежным способом? - PullRequest
25 голосов
/ 02 декабря 2011

Я пытаюсь написать и обновить PDF-документ в столбце BLOB-объектов, но я могу обновить только BLOB-файл, записывающий больше данных, чем предыдущие сохраненные данные. Если я пытаюсь обновить столбец BLOB-объектов меньшими данными документа, я получаю только поврежденный файл PDF.

Сначала столбец BLOB-объектов был инициализирован с помощью функции empty_blob (). Я написал пример класса Java ниже, чтобы проверить это поведение. Я запускаю его в первый раз, указав true в качестве первого параметра метода main , поэтому в первой строке хранится документ размером около 31 КБ, а во второй строке - документ размером 278 КБ. Затем я запускаю его с параметром «false», таким образом, две строки должны быть обновлены, меняя местами документы. В результате я получаю правильный результат только тогда, когда пишу больше данных, чем существующее.

Как можно написать метод, который надежно записывает и обновляет большой двоичный объект, не беспокоясь о размере двоичных данных?

import static org.apache.commons.io.IOUtils.copy;

import java.io.FileInputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import oracle.jdbc.OracleDriver;
import oracle.jdbc.OracleResultSet;
import oracle.sql.BLOB;

import org.apache.commons.lang.ArrayUtils;
/**
 * Prerequisites:
 * 1) a table named 'x' must exists [create table x (i number, j blob);] 
 * 2) that table should have two columns [insert into x (i, j) values (1, empty_blob()); insert into x (i, j) values (2, empty_blob()); commit;]
 * 3) download lsp.pdf from http://www.objectmentor.com/resources/articles/lsp.pdf
 * 4) download dotguide.pdf from http://www.graphviz.org/Documentation/dotguide.pdf
 */
public class UpdateBlob {
    public static void main(String[] args) throws Exception {
        processFiles(new String[]{"lsp.pdf", "dotguide.pdf"}, Boolean.valueOf(args[0]));
    }

    public static void processFiles(String [] fileNames, boolean forward) throws Exception {
      if(!forward){
        ArrayUtils.reverse(a);
      }
      int idx = 1;
      for(String fname : fileNames){
        insert(idx++, fname);
      }
  }

    private static void insert(int idx, String fname) throws Exception{
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            DriverManager.registerDriver(new OracleDriver());
            conn = DriverManager.getConnection("jdbc:oracle:thin:@"+db+":"+port+":"+sid, user, pwd);
            ps = conn.prepareStatement("select j from x where i = ? for update");
            ps.setLong(1, idx);

            rs = ps.executeQuery();

            if (rs.next()) {
                FileInputStream instream = new FileInputStream(fname);
                BLOB blob = ((OracleResultSet)rs).getBLOB(1);
                OutputStream outstream = blob.setBinaryStream(1L);
                copy(instream, outstream);
                instream.close();
                outstream.close();
            }
            rs.close();
            ps.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new Exception(e);
        }
    }
}

Версия Oracle: 11.1.0.7.0 - 64 бита

Я даже попробовал стандартный JDBC API без использования специфического Oracle (как в примере выше), но безуспешно.

Ответы [ 3 ]

30 голосов
/ 02 декабря 2011

Это намного проще:

PreparedStatement pstmt =
  conn.prepareStatement("update blob_table set blob = ? where id = ?");
File blob = new File("/path/to/picture.png");
FileInputStream in = new FileInputStream(blob);

// the cast to int is necessary because with JDBC 4 there is 
// also a version of this method with a (int, long) 
// but that is not implemented by Oracle
pstmt.setBinaryStream(1, in, (int)blob.length()); 

pstmt.setInt(2, 42);  // set the PK value
pstmt.executeUpdate();
conn.commit();
pstmt.close();

То же самое работает при использовании оператора INSERT. Нет необходимости в empty_blob() и втором операторе обновления.

4 голосов
/ 15 мая 2017

В дополнение к a_horse_with_no_name s answer (который опирается на PreparedStatement.setBinaryStream (...) API), есть по крайней мере еще два опции для BLOB и еще 3 для CLOB и NCLOB:

  1. Явно создайте большой объект, напишите в него и используйте PreparedStatement.setBlob(int, Blob):

    int insertBlobViaSetBlob(final Connection conn, final String tableName, final int id, final byte value[])
    throws SQLException, IOException {
        try (final PreparedStatement pstmt = conn.prepareStatement(String.format("INSERT INTO %s (ID, VALUE) VALUES (?, ?)", tableName))) {
            final Blob blob = conn.createBlob();
            try (final OutputStream out = new BufferedOutputStream(blob.setBinaryStream(1L))) {
                out.write(value);
            }
    
            pstmt.setInt(1, id);
            pstmt.setBlob(2, blob);
            return pstmt.executeUpdate();
        }
    }
    
  2. Обновление пустого большого объекта (вставляется через DBMS_LOB.EMPTY_BLOB() или DBMS_LOB.EMPTY_CLOB()) через SELECT ... FOR UPDATE. Это специфично для Oracle и требует выполнения двух операторов вместо одного. Кроме того, это то, что вы пытались достичь в первую очередь:

    void insertBlobViaSelectForUpdate(final Connection conn, final String tableName, final int id, final byte value[])
    throws SQLException, IOException {
        try (final PreparedStatement pstmt = conn.prepareStatement(String.format("INSERT INTO %s (ID, VALUE) VALUES (?, EMPTY_BLOB())", tableName))) {
            pstmt.setInt(1, id);
            pstmt.executeUpdate();
        }
    
        try (final PreparedStatement pstmt = conn.prepareStatement(String.format("SELECT VALUE FROM %s WHERE ID = ? FOR UPDATE", tableName))) {
            pstmt.setInt(1, id);
            try (final ResultSet rset = pstmt.executeQuery()) {
                while (rset.next()) {
                    final Blob blob = rset.getBlob(1);
                    try (final OutputStream out = new BufferedOutputStream(blob.setBinaryStream(1L))) {
                        out.write(value);
                    }
                }
            }
        }
    }
    
  3. Для CLOB и NCLOB можно дополнительно использовать PreparedStatement.setString() и setNString() соответственно.

0 голосов
/ 16 января 2016

FWIW, для чего-то, что умещается в памяти, я обнаружил, что могу просто передать байтовый массив в качестве подготовленного параметра оператора, а не проходить через "строгую" мораль строгости (или, что хуже, специфичные / предложенные Oracle)

Используя упаковку Spring "JDBC template" (org.springframework.jdbc.core.JdbcTemplate), чтобы поместить содержимое "большой" (или нет) строки в столбец BLOB, код выглядит примерно так:

jdbc.update( "insert into a_table ( clob_col ) values ( ? )", largeStr.getBytes() );

Нет шага 2.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...