Слишком сложная работа с Oracle в jdbc BLOB - PullRequest
31 голосов
/ 14 мая 2009

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

  1. вставка empty_blob() значение.
  2. выберите строку с помощью for update.
  3. введите реальное значение.

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

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, размер большого двоичного объекта, ...)? Первое решение лучше (скорость, потребление памяти, ...)? Какие-либо причины не использовать более простой второй подход?

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

Ответы [ 9 ]

11 голосов
/ 14 мая 2009

Подход к обновлению, который вы упоминаете в первом случае, может быть переписан с использованием чистого кода 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 + " = ? "
7 голосов
/ 15 марта 2012

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

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

  2. Вызов функции EMPTY_BLOB. INSERT INTO T1 (ИМЯ, ФАЙЛ) ЗНАЧЕНИЯ ("a.avi", EMPTY_BLOB ()) RETURNING FILE INTO?; В этом случае возвращаемые точки локатора в табличное пространство данных. Все записи / чтения против этого локатора будут отправлены через сеть на сервер БД. Все записи «охраняются» записями в повторные журналы. Ничего не хранится в куче JVM. Возвращаемое предложение не поддерживалось стандартами JDBC (1.0, 2.0), поэтому в Интернете можно найти много примеров, когда люди рекомендуют подход из двух шагов: «INSERT ...; SELECT ... FOR UPDATE;"

Ораторы Oracle должны быть связаны с каким-либо соединением с базой данных, их нельзя использовать, когда Соединение с БД потеряно / закрыто / (или «зафиксировано»). Они не могут быть переданы из одного соединения в другое.

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

5 голосов
/ 17 мая 2009

Одна интересная вещь с JDBC - вы можете довольно агрессивно обновиться до последних драйверов и работать с функциями JDBC 4.0. Драйверы oracle JDBC будут работать со старыми версиями баз данных, поэтому вы можете использовать драйвер JDBC под маркой 11g против базы данных 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 вы можете создавать Blob и Clobs из объекта подключения:

Blob aBlob = con.createBlob();
int numWritten = aBlob.setBytes(1, val);
5 голосов
/ 14 мая 2009

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

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

4 голосов
/ 18 августа 2010

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

blob.setBytes(1, inputBytes);

вызывает проблемы при использовании тонкого клиента oracle ojdbc14.jar, «Неподдерживаемые функции»

Итак, я должен был обойти:

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();
2 голосов
/ 01 октября 2012

Найдены некоторые предупреждения для второго решения

Я использую 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

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

2 голосов
/ 05 июля 2011

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

ps.setString(1, yourString);

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

1 голос
/ 25 июня 2012

Я нашел простой вызов 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();
0 голосов
/ 20 августа 2016

Если размер вставки BLOB больше, чем blob.getBufferSize () , транзакция фиксируется, как только первый блок записывается в БД как Значение по умолчанию свойства autoCommit соединения jdbc равно true и дальнейшие записи фрагментов завершаются неудачно, так как 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();
...