Мы используем JOOQ для вставки записей в таблицу postgresql со столбцом bytea.
Отправляемый нами массив байтов имеет размер около 150 МБ, и мы получаем ошибку от сервера Postgresql:
ОШИБКА: неверный размер запроса на выделение памяти 1073741824.
Покопавшись в коде и запросе, который генерирует JOOQ, мы заметили, что byteArray преобразуется в восьмеричный (org.jooq.util.postgres.PostgresUtils#toPGString(byte[]))
И в итоге запрос будет выглядеть примерно так:
INSERT INTO test_table (my_data) VALUES (E'\\042\\145\\171...\\042'::bytea);
Тестирование с меньшими массивами байтов показывает, что после вставки данных их размер действительно равен размеру массива байтов, а не размеру восьмеричной строки (которая намного длиннее).
Проблема, по-видимому, заключается в том, что восьмеричная строка настолько длинная, что сервер postgresql не может преобразовать ее в bytea даже до попытки сохранить ее в столбце.
Есть ли способ настроить JOOQ для передачи строки hex
в качестве значения вместо octal
?
Мы используем версию JOOQ 3.16.4
и postgresql 13.9
JOOQ настроен по умолчанию DefaultConfiguration
. Никаких существенных изменений не происходит.
Приведенный выше sql взят из созданного исключения:
org.jooq.impl.Tools#translate(java.lang.String, java.lang.RuntimeException)
И sql, который он печатает, выглядит так:
org.jooq.exception.DataAccessException: SQL [insert into "public"."table_name" ("id", "name", "version", "description", "archived", "other_id", "jws_data") values (829720463911690240, 'v-0', 'rb-0-6HW7', null, 0, 829720463899107328, E'\\042\\145\\171\\112\\162\\141\\127\\....\135\\175'::bytea) returning "public"."table_name"."id"]; Unspecified RuntimeException
Мы не генерируем вставку SQL, вместо этого мы используем org.jooq.impl.DAOImpl#insert(P)
и передаем сгенерированный JOOQ объект Record, который содержит byteArray в качестве одного из членов (jws_data
). это имя также соответствует имени столбца в таблице.
Примечание. Я немного изменил SQL, чтобы скрыть точные имена столбцов, поэтому игнорируйте имена и несоответствия с другими SQL.
toPGString:631, PostgresUtils (org.jooq.util.postgres)
sqlInline0:2044, DefaultBinding$DefaultBytesBinding (org.jooq.impl)
sqlInline0:1941, DefaultBinding$DefaultBytesBinding (org.jooq.impl)
sql:937, DefaultBinding$AbstractBinding (org.jooq.impl)
sql:929, DefaultBinding$AbstractBinding (org.jooq.impl)
accept:186, Val (org.jooq.impl)
visit0:720, DefaultRenderContext (org.jooq.impl)
visit:295, AbstractContext (org.jooq.impl)
toSQL92Values:326, FieldMapsForInsert (org.jooq.impl)
toSQL92Values:278, FieldMapsForInsert (org.jooq.impl)
accept:137, FieldMapsForInsert (org.jooq.impl)
visit0:720, DefaultRenderContext (org.jooq.impl)
visit:295, AbstractContext (org.jooq.impl)
toSQLInsert:739, InsertQueryImpl (org.jooq.impl)
accept0:624, InsertQueryImpl (org.jooq.impl)
accept:642, AbstractDMLQuery (org.jooq.impl)
visit0:720, DefaultRenderContext (org.jooq.impl)
visit:295, AbstractContext (org.jooq.impl)
getSQL0:484, AbstractQuery (org.jooq.impl)
execute:287, AbstractQuery (org.jooq.impl)
storeInsert0:191, TableRecordImpl (org.jooq.impl)
lambda$storeInsert$0:157, TableRecordImpl (org.jooq.impl)
apply:-1, TableRecordImpl$$Lambda$2120/0x0000000801a5bef8 (org.jooq.impl)
operate:143, RecordDelegate (org.jooq.impl)
storeInsert:156, TableRecordImpl (org.jooq.impl)
insert:144, TableRecordImpl (org.jooq.impl)
insert:139, TableRecordImpl (org.jooq.impl)
insert:180, DAOImpl (org.jooq.impl)
insert:156, DAOImpl (org.jooq.impl)
save:80, RDBDao (org.jfrog.bintray.distribution.rdb)
То же поведение можно воспроизвести, вызвав:
dslContext.insertInto(TEST_TABLE)
.set(MY_DATA, jws_data_byte_array)
.execute();
Или
dslContext.execute("insert into test_table (my_data) values (?)", jws_data_byte_array);
Срабатывает тот же toPGString:631, PostgresUtils (org.jooq.util.postgres)
.
Наверняка вам стоит использовать PreparedStatement::setBinaryStream
?
Предоставленный мной запрос является лишь примером того, как выглядит запрос. Мы используем DAOImpl.insert() с сгенерированными объектами Record.
Отправленный «настоящий» запрос немного длиннее и имеет гораздо больше параметров: insert into "public"."our_table" ("id", "name", "version", "description", "archived", "other_id", "jws_data") values (829720463911690240, 'v-0', 'rb-0-6HW7', null, 0, 829720463899107328, E'\\042\\145\\171\\112\\162\\141\\127\\121\\151\\117\\1...)
@EliSkoran: jOOQ всегда генерирует PreparedStatement
со значениями привязки по умолчанию. Почему ваше значение привязки становится встроенным? Как вы настроили jOOQ и почему?
@LukasEder Спасибо за ответ, пожалуйста, ознакомьтесь с поясняющей поправкой к сообщению.
@EliSkoran: но мой вопрос все еще остается: почему ваши значения привязки встраиваются? Можете ли вы опубликовать стек вызовов, который ведет к (org.jooq.util.postgres.PostgresUtils#toPGString(byte[]))
?
@LukasEder Добавлена трассировка стека.
Кажется, вы используете старую версию jOOQ, но это, вероятно, не имеет значения. Ваш dslContext
, скорее всего, настроен на встроенные значения привязки. Пожалуйста, расследуйте это. Нет веской причины, по которой этот стек вызовов был бы выбран в противном случае (от DefaultBinding$AbstractBinding.sql()
до DefaultBinding$DefaultBytesBinding.sqlInline0()
). Вы можете установить там точку останова или подняться вверх по стеку вызовов, чтобы увидеть, где, например, Settings.statementType
установлено в STATIC_STATEMENT
или Settings.paramType
установлено в INLINED
(там может быть могут быть и другие причины). Возможно, это делает ваша третья сторона RDBDao
?
Как упомянул @Lukas Eder,
Мы нашли место, где параметр StatementType был изменен на STATIC_STATEMENT
вместо значения по умолчанию PREPARED_STATEMENT
.
После удаления этой конфигурации JOOQ перестал создавать длинные восьмеричные строки, и вставка работает отлично.
Почему вы встраиваете значения привязки?