Поддержка типов BLOB/CLOB в тонком клиенте JDBC#

Введение#

Начиная с версии DataGrid 17.0.0 появился доступ к новым возможностям работы с типами BLOB (Binary Large Object — двоичные большие объекты) и CLOB (Character Large Object — символьные большие объекты) в тонком клиенте JDBC.

В тонком клиенте JDBC доступны:

  • Базовая поддержка операций чтения, записи и модификации объектов данных типа BLOB/CLOB. Ранее поддержка типов BLOB/CLOB была доступна только для толстого клиента JDBC. Работа с BLOB/CLOB с помощью тонкого клиента характеризуется теми же особенностями функциональности и ограничениями по размеру данных, что и с помощью толстого клиента.

  • Поддержка Stream API для чтения и записи данных типов BLOB. В толстом клиенте JDBC поддержка Stream API для BLOB/CLOB отсутствует.

Особенности функциональности#

DataGrid — in-memory база данных, и ее решения оптимизированы для хранения данных в оперативной памяти (heap). Это ключевой аспект архитектуры DataGrid, который позволяет достигать высокой производительности за счет минимизации задержек доступа к данным.

Особенности функциональности для тонкого и толстого клиентов JDBC:

  • Глубокая оптимизация под BLOB/CLOB отсутствует — DataGrid хранит данные BLOB/CLOB внутри таблицы, что упрощает реализацию, но накладывает ограничение на размер передаваемых данных. Не рекомендуется использовать BLOB/CLOB больше нескольких МБ. Использование BLOB/CLOB невозможно для работы с информацией более 2 ГБ, так как будет превышен допустимый размер хранения в byte[] и сгенерируется исключение OutOfMemoryException.

  • При использовании API по потоковой передаче данных DataGrid не обработает объекты данных типа BLOB частями (in chunks). Соответственно, это не приведет к уменьшению нагрузки на память, так как оптимизации под хранение объектов данных типа BLOB/CLOB нет.

  • Отсутствует поддержка Stream API для CLOB (например, setCharacterStream() и setAsciiStream()).

  • Тонкий клиент JDBC не является потокобезопасным, поэтому следует избегать параллельного изменения данных.

  • После того, как экземпляр JdbcClob или JdbcBlob освобожден с помощью метода free(), любые дальнейшие попытки использовать этот экземпляр приведут к генерации исключения SQLException.

В связи с этим для работы с объектами JdbcBlob и JdbcClob рекомендуется рассмотреть альтернативные методы:

  • Разделение больших файлов или данных на меньшие фрагменты (например, по 1 МБ каждый) и их хранение отдельными строками в таблице DataGrid.

  • Использование внешнего хранилища и сохранение только ссылки (например, пути к файлу, URL или уникального идентификатора) в таблице DataGrid.

Поддерживаемые операции#

Поддерживаемые операции в тонком клиенте JDBC#

Тип

Функциональность

Методы

Описание

CLOB

Базовая поддержка для хранения и извлечения символьных данных

JdbcClob(String chars)

Метод-конструктор.

Параметр: chars — последовательность символов, которые составляют CLOB

length()

Возвращает количество символов в значении CLOB, которое определено данным объектом CLOB

getSubString(long pos, int len)

Извлекает копию подстроки из значения CLOB, которая определена данным объектом CLOB. Подстрока начинается с позиции pos и включает до length последовательных символов

getCharacterStream()

Возвращает значение CLOB, которое определено данным объектом CLOB, в виде объекта java.io.Reader или потока символов

getCharacterStream(long pos, long len)

Возвращает объект Reader, который содержит частичное значение CLOB, начиная с символа pos и длиной length символов

getAsciiStream()

Возвращает значение CLOB, которое обозначено данным объектом CLOB, как поток ASCII

position(String searchStr, long start)

Находит позицию, на которой указанная подстрока searchstr появляется в SQL CLOB, который представлен текущим объектом CLOB. Поиск начинается с позиции start

position(Clob searchStr, long start)

Ищет положение, на котором строка searchstr встречается в данном объекте CLOB. Поиск начинается с позиции start

setString(long pos, String str)

Записывает указанную Java-строку в CLOB-значение, которое представляет данный объект CLOB, начиная с позиции pos. Строка перезапишет уже имеющиеся символы в CLOB, начиная с позиции pos. Если конец CLOB достигнут при записи строки, длина CLOB увеличится, чтобы вместить дополнительные символы.

Примечание: если значение pos превышает длину CLOB на единицу, поведение не определено. Некоторые JDBC-драйверы могут сгенерировать исключение SQLException, другие могут поддерживать такую операцию

setString(long pos, String str, int off, int len)

Записывает len символов строки str, начиная с позиции offset, в CLOB, который представляет данный объект CLOB. Строка перезапишет уже существующие символы в CLOB, начиная с позиции offset. Если конец CLOB достигается при записи строки, длина CLOB увеличится, чтобы вместить дополнительные символы.

Примечание: если значение offset больше длины CLOB на единицу, поведение не определено. Некоторые JDBC-драйверы могут сгенерировать исключение SQLException, другие могут поддерживать такую операцию

truncate(long len)

Урезает значение CLOB, которое представляет данный объект CLOB, до длины len символов.

Примечание: если указанное значение len превышает длину CLOB на единицу, поведение не определенно. Некоторые JDBC-драйверы могут сгенерировать исключение SQLException, другие могут поддерживать такую операцию

free()

Метод освобождает ресурсы, которыми владеет объект CLOB. После вызова метода free() объект становится недействительным, и любая попытка вызвать другой метод, кроме free(), приведет к генерации исключения SQLException.

После вызова метода free() повторные вызовы метода free() игнорируются и не меняют состояние объекта

read()

Метод считывает следующий байт данных из входного потока. Возвращаемое значение байта будет представлено целым числом в диапазоне от 0 до 255. Если байт недоступен, потому что достигнут конец потока, возвращается значение -1. Этот метод блокируется до появления данных в потоке, обнаружения конца потока или генерации исключения.

Этот метод должен реализовывать подкласс

read(byte[] b, int off, int len)

Метод read(byte[] b, int off, int len) считывает до len байтов данных из входного потока в массив байтов b. Количество прочитанных байтов может быть меньше, чем len, но предпримется попытка прочитать максимально возможное количество байтов. Байты записываются в элементы массива b, начиная с индекса off и продолжая до индекса off + len - 1. Оставшиеся байты (если их меньше, чем len) остаются неизменными.

Если поток находится в конце файла, возвращается значение -1. Если байты отсутствуют, потому что поток закончился, возвращается значение -1. В противном случае байты считываются, и их количество сохраняется в переменной k. Прочитанные байты сохраняются в элементах массива b с индексами off до off + k - 1, оставляя остальные байты неизменными.

Байты, которые были успешно прочитаны, сохраняются в массиве b, начиная с индекса off и далее по индексам off + 1 до off + len - 1. Байты, которые не прочитали, остаются неизменными.

Метод read(b, off, len) для класса InputStream многократно вызывает метод read(). Если первый вызов read() генерирует исключение IOException, оно передается обратно методу read(b, off, len). Если последующие вызовы read() приводят к IOException, это интерпретируется как конец файла, и байты, собранные до этого момента, сохраняются в массиве b. Байты, полученные до генерации исключения, возвращаются, а количество байтов, собранных до исключения, возвращается как результат

createClob()

Создает объект, который реализует интерфейс Clob. Возвращаемый объект изначально не содержит данных. Методы setAsciiStream, setCharacterStream и setString интерфейса Clob могут быть использованы для добавления данных в объект CLOB

setClob(int paramIdx, Clob x)

Устанавливает указанный параметр равным переданному объекту java.sql.Clob. Драйвер преобразует это значение в типе SQL CLOB при отправке в базу данных

getClob(int colIdx)

Возвращает значение указанного столбца в текущей строке данного объекта ResultSet в виде объекта CLOB в языке программирования Java

getClob(String colLb)

Возвращает значение указанного столбца в текущей строке данного объекта ResultSet в виде объекта CLOB в языке программирования Java

BLOB

Базовая поддержка для хранения и извлечения бинарных данных

JdbcBlob()

Создает пустой объект данных типа BLOB. Предполагается, что этот метод будет вызван клиентом при создании объекта BLOB с помощью метода java.sql.Connection.createBlob

JdbcBlob(JdbcBinaryBuffer buf)

Создает объект данных типа BLOB, который оборачивает данные, хранящиеся в буфере buf. Предполагается, что метод будет использоваться для создания BLOB в результате выполнения запроса в java.sql.ResultSet.

Параметр: buf — существующий буфер с данными

JdbcBlob(byte[] arr)

Создает объект данных типа BLOB, который оборачивает существующий массив байтов arr

length()

Возвращает количество байтов в значении BLOB, которые определены объектом данных типа BLOB

getBytes(long pos, int len)

Метод возвращает все или часть значений BLOB, которые представлены данным объектом BLOB, в виде массива байтов. Массив байтов состоит из length последовательных байтов, начиная с позиции pos

position(byte[] ptrn, long start)

Метод находит позицию байтового массива ptrn в значении BLOB, которое управляется данным объектом BLOB. Поиск начинается с позиции start

position(Blob ptrn, long start)

Метод определяет позицию байтового массива ptrn в значении BLOB, которое управляется данным объектом BLOB. Поиск начинается с позиции start

setBytes(long pos, byte[] bytes)

Метод записывает указанный массив байтов в значение BLOB, который управляется данным объектом BLOB, начиная с позиции pos. Байты будут перезаписывать существующие байты в BLOB, начиная с позиции pos. Если конец BLOB достигнут при записи байтов, длина BLOB увеличится, чтобы вместить дополнительные байты.

Примечание: если значение pos больше длины BLOB на единицу, поведение не определено. Некоторые JDBC-драйверы могут сгенерировать исключение SQLException, другие могут поддерживать такую операцию

setBytes(long pos, byte[] bytes, int off, int len)

Метод записывает указанные байты в значение BLOB, которое управляется данным объектом BLOB, начиная с позиции pos и заканчивая на len байтов дальше. Байты будут перезаписывать существующие байты в BLOB, начиная с позиции pos. Если конец BLOB достигнут при записи байтов, длина BLOB увеличится, чтобы вместить дополнительные байты.

Примечание: если значение pos больше длины BLOB на единицу, поведение не определено. Некоторые JDBC-драйверы могут сгенерировать исключение SQLException, другие могут поддерживать такую операцию

truncate(long len)

Метод truncate(long len) сокращает значение BLOB, которым управляет данный объект BLOB, до длины len байтов.

Примечание: если значение len больше длины BLOB на единицу, поведение не определено. Некоторые JDBC-драйверы могут сгенерировать исключение SQLException, другие могут поддерживать такую операцию

free()

Метод освобождает ресурсы, которые удерживает объект типа данных BLOB, и делает его недействительным.

После вызова метода free() объект BLOB становится недействительным, и любые попытки использовать его снова, кроме метода free(), приведут к генерации SQLException. После вызова метода free() повторные вызовы метода free() игнорируются и не меняют состояние объекта

Поддержка потоков для данных типа BLOB

createBlob()

Создает объект, который реализует интерфейс Blob. Изначально возвращаемый объект не содержит никаких данных. Методы setBinaryStream и setBytes интерфейса Blob могут использоваться для добавления данных в BLOB

getBytes(int colIdx)

Возвращает значение указанного столбца в текущей строке данного объекта ResultSet в виде массива байтов в языке программирования Java. Байты представляют собой сырые значения, которые возвращает драйвер

getObject(int colIdx)

Возвращает значение указанного столбца в текущей строке данного объекта ResultSet в виде объекта в языке программирования Java. Этот метод вернет значение данного столбца как объект Java. Тип объекта Java будет соответствовать типу SQL, который отображается как встроенный тип, согласно спецификации JDBC. Если значение равно SQL NULL, драйвер вернет null в Java.

Данный метод также может использоваться для чтения абстрактных типов данных, специфичных для конкретной базы данных. В API JDBC 2.0 поведение метода getObject расширено для работы с пользовательскими типами данных SQL.

Если метод Connection.getTypeMap не генерирует исключение SQLFeatureNotSupportedException, когда столбец содержит структурированное или уникальное значение, поведение данного метода такое же, как при вызове метода getObject(columnIndex, this.getStatement().getConnection().getTypeMap()). Если метод Connection.getTypeMap генерирует исключение SQLFeatureNotSupportedException, структурированные значения не поддерживаются, а уникальные значения отображаются как стандартные классы Java, которые определяются типом SQL уникального типа

getObject(String colLb)

Возвращает значение указанного столбца в текущей строке данного объекта ResultSet в виде объекта в языке программирования Java. Этот метод вернет значение данного столбца как объект Java. Тип объекта Java будет соответствовать типу SQL, который отображается как встроенный тип, согласно спецификации JDBC. Если значение равно SQL NULL, драйвер вернет null в Java.

Данный метод также может использоваться для чтения абстрактных типов данных, специфичных для конкретной базы данных. В API JDBC 2.0 поведение метода getObject расширено для работы с пользовательскими типами данных SQL. Когда столбец содержит структурированное или уникальное значение, поведение данного метода аналогично вызову метода getObject(columnIndex, this.getStatement().getConnection().getTypeMap())

setBinaryStream(long pos)

Метод setBinaryStream(long pos) возвращает поток, который можно использовать для записи данных в BLOB. Поток начинает запись в BLOB с позиции pos. Байты, которые записаны в поток, заменяют существующие байты в BLOB, начиная с позиции pos. Если конец BLOB достигнут при записи байтов, длина BLOB увеличится, чтобы вместить дополнительные байты.

Примечание: если значение pos больше длины BLOB на единицу, поведение не определено. Некоторые JDBC-драйверы могут сгенерировать исключение SQLException, другие могут поддерживать такую операцию

void setBlob(int paramIdx, InputStream inputStream)

Метод устанавливает параметр в объект InputStream. Отличие от метода setBinaryStream: драйвер получает указание, что параметр должен быть передан серверу как объект BLOB. В методе setBinaryStream драйверу могут потребоваться дополнительные действия для определения, является ли параметр значением LONGVARBINARY или BLOB.

Примечание: документация JDBC-драйвера может предоставить дополнительную информацию о том, как эффективнее передавать данные, если требуется выбор между передачей данных как LONGVARBINARY и как BLOB. Сверьтесь с документацией драйвера для выбора подходящего метода передачи данных

void setBlob(int paramIdx, InputStream inputStream, long length)

Устанавливает указанный параметр в объект InputStream. Поток ввода-вывода должен содержать количество символов, которое указано длиной, иначе будет сгенерировано исключение SQLException при выполнении PreparedStatement. Этот метод отличается от метода setBinaryStream (int, InputStream, int), так как он сообщает драйверу, что значение параметра должно быть отправлено на сервер как BLOB. Когда используется метод setBinaryStream, драйверу могут потребоваться дополнительные действия для определения, следует ли отправлять данные параметра на сервер как LONGVARBINARY или как BLOB

Blob getBlob(int colIdx)

Возвращает значение указанного столбца в текущей строке данного объекта ResultSet в виде объекта BLOB в языке программирования Java

Blob getBlob(String colLb)

Возвращает значение указанного столбца в текущей строке данного объекта ResultSet в виде объекта BLOB в языке программирования Java

void setBinaryStream(int paramIdx, InputStream x, int len)

Устанавливает указанный параметр в заданный входной поток, у которого будет указанное количество байтов. Если нужно ввести очень большое двоичное значение в параметр типа LONGVARBINARY, может быть удобнее отправить его с помощью объекта java.io.InputStream. Данные будут считываться из потока по мере необходимости до достижения конца файла.

Примечание: объект потока может быть стандартным объектом потока Java или пользовательским подклассом, который реализует стандартный интерфейс

void setBinaryStream(int paramIdx, InputStream x, long len)

Устанавливает указанный параметр в заданный входной поток, у которого будет определенное количество байтов. Если нужно ввести очень большое двоичное значение в параметр типа LONGVARBINARY, может быть удобнее отправить его с помощью объекта java.io.InputStream. Данные будут считываться из потока по мере необходимости до достижения конца файла.

Примечание: объект потока может быть стандартным объектом потока Java или пользовательским подклассом, который реализует стандартный интерфейс

void setBinaryStream(int paramIdx, InputStream x)

Устанавливает указанный параметр в заданный входной поток. Если нужно ввести очень большое двоичное значение в параметр типа LONGVARBINARY, может быть удобнее отправить его с помощью объекта java.io.InputStream. Данные будут считываться из потока по мере необходимости до достижения конца файла.

Примечание: объект потока может быть стандартным объектом потока Java или пользовательским подклассом, который реализует стандартный интерфейс

Примечание: ознакомьтесь с документацией JDBC-драйвера, чтобы определить, стоит ли использовать версию метода setBinaryStream, которая принимает параметр длины

InputStream getBinaryStream(int colIdx)

Извлекает значение указанного столбца в текущей строке данного объекта ResultSet в виде потока неинтерпретируемых байтов. Затем значение может быть прочитано частями (in chunks) из потока. Данный метод особенно подходит для извлечения больших значений типа LONGVARBINARY.

Примечание: все данные в возвращаемом потоке должны быть прочитаны перед получением значения любого другого столбца. Следующий вызов метода получения данных автоматически закрывает поток. Также поток может вернуть 0 при вызове метода InputStream.available независимо от наличия доступных данных

InputStream getBinaryStream(String colLb)

Извлекает значение указанного столбца в текущей строке данного объекта ResultSet в виде потока неинтерпретируемых байтов. Затем значение может быть прочитано частями (in chunks) из потока. Данный метод особенно подходит для извлечения больших значений типа LONGVARBINARY.

Примечание: все данные в возвращаемом потоке должны быть прочитаны перед получением значения любого другого столбца. Следующий вызов метода получения данных автоматически закрывает поток. Также поток может вернуть 0 при вызове метода available независимо от наличия доступных данных

Примеры использования#

Работа с объектами JdbcBlob с использованием потока InputStream:

Java#
/** Запись объекта `JdbcBlob` с использованием потока `InputStream`. */
try (InputStream is = new FileInputStream("largefile.bin")) {
    PreparedStatement stmt = conn.prepareStatement("INSERT INTO my_table (blob_column) VALUES (?)");
    stmt.setBinaryStream(1, is);
    stmt.executeUpdate();
}

/** Чтение объекта `JdbcBlob` с использованием потока `InputStream`. */
try (ResultSet rs = stmt.executeQuery("SELECT blob_column FROM my_table WHERE id = 1")) {
    if (rs.next()) {
        InputStream is = rs.getBinaryStream("blob_column");
        // Обработка потока `InputStream`.
    }
}

Используйте методы setClob() и getClob() для работы с CLOB. setClob() используется для передачи больших текстовых данных в базу данных, а метод getClob() — для получения этих данных из базы:

Java#
// Установка значения CLOB.
try (Connection conn = DriverManager.getConnection(url, user, password)) {
    String clobData = "Большой текст...";

    PreparedStatement pstmt = conn.prepareStatement(
        "INSERT INTO my_table (clob_column) VALUES (?)"
    );

    pstmt.setClob(1, new StringReader(clobData));
    pstmt.executeUpdate();
}

// Получение значения CLOB.
try (Connection conn = DriverManager.getConnection(url, user, password)) {
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT clob_column FROM my_table WHERE id = 1");

    if (rs.next()) {
        Reader reader = rs.getClob(1).getCharacterStream();

        char[] buffer = new char[1024];
        StringBuilder sb = new StringBuilder();

        int charsRead;
        while ((charsRead = reader.read(buffer)) > 0) {
            sb.append(buffer, 0, charsRead);
        }

        System.out.println(sb.toString());
    }
}

Используйте методы setBlob() и getBlob() для работы с BLOB. setBlob() используется для передачи больших бинарных данных в базу данных, а getBlob() — для получения этих данных из базы:

Java#
// Установка значения BLOB.
try (Connection conn = DriverManager.getConnection(url, user, password)) {
    byte[] blobData = { /* Бинарные данные. */ };

    PreparedStatement pstmt = conn.prepareStatement(
        "INSERT INTO my_table (blob_column) VALUES (?)"
    );

    pstmt.setBlob(1, new ByteArrayInputStream(blobData));
    pstmt.executeUpdate();
}

// Получение значения BLOB.
try (Connection conn = DriverManager.getConnection(url, user, password)) {
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT blob_column FROM my_table WHERE id = 1");

    if (rs.next()) {
        Blob blob = rs.getBlob(1);
        InputStream is = blob.getBinaryStream();

        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = is.read(buffer)) > 0) {
            // Обработка данных.
        }
    }
}