Подтвердить что ты не робот

Сложная обработка BLOB-памяти oracle jdbc

Когда я просматриваю веб-страницы для вставки BLOB файлов в базу данных Oracle с помощью тонкого драйвера jdbc, большинство веб-страниц предлагают трехэтапный подход:

  • Вставить empty_blob() значение.
  • выберите строку с for update.
  • введите реальное значение.

Это отлично работает для меня, вот пример:

Connection oracleConnection = ...

byte[] testArray = ...

PreparedStatement ps = oracleConnection.prepareStatement(
    "insert into test (id, blobfield) values(?, empty_blob())");
ps.setInt(1, 100);
ps.executeUpdate();
ps.close();
ps = oracleConnection.prepareStatement(
    "select blobfield from test where id = ? for update");
ps.setInt(1, 100);
OracleResultSet rs = (OracleResultSet) ps.executeQuery();
if (rs.next()) {
    BLOB blob = (BLOB) rs.getBLOB(1);
    OutputStream outputStream = blob.setBinaryStream(0L);
    InputStream inputStream = new ByteArrayInputStream(testArray);
    byte[] buffer = new byte[blob.getBufferSize()];
    int byteread = 0;
    while ((byteread = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, byteread);
    }
    outputStream.close();
    inputStream.close();
}

Есть несколько веб-страниц, в которых авторы предлагают использовать более простое одношаговое решение. Предыдущий пример с этим решением:

Connection oracleConnection = ...

byte[] testArray = ...

PreparedStatement ps = oracleConnection.prepareStatement(
    "insert into test(id, blobfield) values(?, ?)");
BLOB blob = BLOB.createTemporary(oracleConnection, false, BLOB.DURATION_SESSION);
OutputStream outputStream = blob.setBinaryStream(0L);
InputStream inputStream = new ByteArrayInputStream(testArray);
byte[] buffer = new byte[blob.getBufferSize()];
int byteread = 0;
while ((byteread = inputStream.read(buffer)) != -1) {
    outputStream.write(buffer, 0, byteread);
}
outputStream.close();
inputStream.close();

ps.setInt(1, 100);
ps.setBlob(2, blob);
ps.executeUpdate();
ps.close();

Второй код намного проще, поэтому мой вопрос: в чем смысл первого (популярного) решения? Есть ли (было) какое-то ограничение для второго решения (номер версии сервера Oracle, версия драйвера jdbc, размер blob,...)? Лучше ли первое решение (скорость, потребление памяти,...)? Любые причины не использования более простого второго подхода?

Точно такой же вопрос применяется для полей CLOB.

4b9b3361

Ответ 1

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

public static void updateBlobColumn(Connection con, String table, String blobColumn, byte[] inputBytes, String idColumn, Long id) throws SQLException {
  PreparedStatement pStmt = null;
  ResultSet rs = null;
  try {
    String sql = 
      " SELECT " + blobColumn + 
      " FROM " + table + 
      " WHERE " + idColumn + " = ? " +
      " FOR UPDATE";
    pStmt = con.prepareStatement(sql, 
      ResultSet.TYPE_FORWARD_ONLY, 
      ResultSet.CONCUR_UPDATABLE);
    pStmt.setLong(1, id);
    rs = pStmt.executeQuery();
    if (rs.next()) {
      Blob blob = rs.getBlob(blobColumn);
      blob.truncate(0);
      blob.setBytes(1, inputBytes);
      rs.updateBlob(blobColumn, blob);
      rs.updateRow();
    }
  }
  finally {
    if(rs != null) rs.close();
    if(pStmt != null) pStmt.close();
  }
}

Для MSSQL я понимаю, что синтаксис блокировки отличается:

String sql = 
  " SELECT " + blobColumn + 
  " FROM " + table + " WITH (rowlock, updlock) " + 
  " WHERE " + idColumn + " = ? "

Ответ 2

Еще одна точка зрения от Oracle DBA. Солнечные ребята очень плохо работали, когда разрабатывали стандарты JDBC (1.0, 2.0, 3.0, 4.0). BLOB означает большой объект, и поэтому он может быть очень большим. Это то, что нельзя хранить в куче JVM. Oracle думает о BLOB как о чем-то вроде дескрипторов файлов (это факт, что они называются "локаторы локализации" ). LOBS нельзя создавать с помощью конструктора и не являются объектами Java. Кроме того, LOB-локаторы (oracle.sql.BLOB) не могут быть созданы с помощью конструктора - они ДОЛЖНЫ быть созданы на стороне БД. В Oracle существует два способа создания LOB.

  • DBMS_LOB.CREATETEMPORATY - возвращенный локатор в этом случае указывает на временное табличное пространство. Все записи/чтения против этого локатора будут отправляться через сеть на сервер БД. Ничего не хранится в куче JVM.

  • Вызов функции EMPTY_BLOB. INSERT INTO T1 (NAME, FILE) VALUES ( "a.avi", EMPTY_BLOB()) ВОЗВРАТ ФАЙЛ В? В этом случае возвращенные точки локатора лоб попадают в табличное пространство данных. Все записи/чтения против этого локатора будут отправляться через сеть на сервер БД. Все записи "охраняются" путем записи в повторные журналы. В куче JVM ничего не хранится. Предложение возврата не поддерживалось стандартами JDBC (1.0, 2.0), поэтому вы можете найти много примеров в Интернете, где люди рекомендуют использовать два шага: "INSERT...; SELECT... FOR UPDATE;"

Oracle lobs должны быть связаны с некоторым подключением к базе данных, они не могут использоваться, когда Соединение DB теряется/закрывается/(или "совершается" ). Они не могут быть переданы из одного соединения в другое.

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

Ответ 3

Обработка LOB-сервера Oracle довольно плохая и может привести к серьезным проблемам с производительностью (например, массовое чрезмерное использование журнала повтора), поэтому первое решение может быть способом их устранения.

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

Ответ 4

Интересная вещь с JDBC заключается в том, что вы можете агрессивно обновляться до последних драйверов и работать с функциями JDBC 4.0. Драйверы oracle JDBC будут работать со старыми версиями баз данных, поэтому вы можете использовать 11-граммовый JDBC-драйвер для базы данных 10g. База данных Oracle 11g JDBC поставляется в двух вариантах: ojdbc5.jar для Java 5 (т.е. JDK 1.5) и ojdbc6.jar для Java 6 (т.е. JDK 1.6). Ojdbc6.jar поддерживает новую спецификацию JDBC 4.0.

С помощью новых драйверов /jdbc 4.0 вы можете создавать Blobs и Clobs с объекта соединения:

Blob aBlob = con.createBlob();
int numWritten = aBlob.setBytes(1, val);

Ответ 5

Это утверждение:

blob.setBytes(1, inputBytes);

дает проблемы, когда я использую тонкий клиент oracle ojdbc14.jar, "Unsupported Features"

Итак, мне пришлось обходить:

rset.next();
Blob bobj = rset.getBlob(1);
BLOB object = (BLOB) bobj;
int chunkSize = object.getChunkSize();
byte[] binaryBuffer = new byte[chunkSize];
int position = 1;
int bytesRead = 0;
int bytesWritten = 0, totbytesRead = 0, totbytesWritten = 0;
InputStream is = fileItem.getInputStream();
while ((bytesRead = is.read(binaryBuffer)) != -1) {
bytesWritten = object.putBytes(position, binaryBuffer, bytesRead);
position += bytesRead;
totbytesRead += bytesRead;
totbytesWritten += bytesWritten;
is.close();

Ответ 6

Если данные CLOB достаточно малы, чтобы вписаться в вашу память без взрыва, вы можете просто создать подготовленный оператор и просто вызвать

ps.setString(1, yourString);

Там могут быть другие ограничения по размеру, но, похоже, они работают с теми размерами, с которыми мы имеем дело (максимум 500 кБ).

Ответ 7

Некоторые наблюдатели, найденные для второго решения

Я использую ojdbc6.jar - последнюю версию и для утверждения из "второго решения":

BLOB blob = BLOB.createTemporary(oracleConnection, false, BLOB.DURATION_SESSION);

Мне нужно освободить blob после завершения инструкции - иначе blob закрывается, когда сеанс закрыт (что может занять много времени с пулом соединений).

blob.freeTemporary();

В противном случае вы можете увидеть заблокированные ресурсы:

select * from v$temporary_lobs

Другой проблемой временного BLOB является необходимость выделения временного табличного пространства: согласно документации http://docs.oracle.com/cd/E11882_01/appdev.112/e18294.pdf

Управление временным табличным пространством для временных LOB Временное табличное пространство используется для хранения временных данных LOB

Ответ 8

Я нашел простой вызов setObject(pos, byte[]) для моего дела. От программирования базы данных с JDBC и Java Джорджем Риз,

        byte[] data = null;
        stmt = con.prepareStatement("INSERT INTO BlobTest(fileName, "
            + "blobData) VALUES(?, ?)");
        stmt.setString(1, "some-file.txt");
        stmt.setObject(2, data, Types.BLOB);
        stmt.executeUpdate();

Ответ 9

Если размер вставки BLOB больше, чем blob.getBufferSize(), транзакция завершается, как только первый фрагмент записывается в db, поскольку значение по умолчанию для свойства autoCommit соединения jdbc истинно, а дальнейшие фрагменты записываются с ошибкой, поскольку db рассматривает их как новые транзакции. Предлагается следующее:
a) Установите для свойства autoCommit подключения jdbc значение false.

conn.setAutoCommit(false);

b) Явно объявить транзакцию после загрузки всего BLOB.

while ((bytesRead = messageInputStream.read(buffer)) != -1) {
     cumBytes += bytesRead;
     blobOutputStream.write(buffer, 0, bytesRead);
    }
conn.commit();