JpaSystemException с использованием критериев, DB2zDialect и нумерации страниц

Итак, у меня есть следующая настройка приложения Spring-Boot:

org.springframework.boot:spring-boot-starter:3.2.4
com.ibm.db2:jcc:11.5.8.0
java.version=21
org.hibernate.orm:hibernate-core:6.4.4.Final

подключение к базе данных DB2 z/OS (DSN12015) v12.015.

У меня следующий диалект:

import org.hibernate.dialect.DB2zDialect;
import org.hibernate.dialect.pagination.LegacyDB2LimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;

public class Db2zOsDialect extends DB2zDialect {
  @Override
  public SequenceInformationExtractor getSequenceInformationExtractor() {
    return LegacyDb2zOsSequenceInformationExtractor.INSTANCE;
  }

  @Override
  public LimitHandler getLimitHandler() {
    return LegacyDB2LimitHandler.INSTANCE;
  }
}

У меня есть следующая сущность:

@Table(name = "\"KEY\"")
public class Key {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID", unique = true, nullable = false)
  private Long id;

  @Column(name = "\"VALUE\"", nullable = false, length = 100)
  private String value;

  ...
}

И следующий репозиторий Repository:

@Repository
interface KeyRepository extends JpaRepository<Key, Long>, JpaSpecificationExecutor<Key> {}

Теперь я хочу прочитать его, используя спецификацию:

final String toSearchFor = "%foo%"
Specification<User> spec = (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("value"), toSearchFor);
final PageRequest pageRequest = PageRequest.of(0, 100, Sort.by(Direction.DESC, "id"));
repo.findAll(specification, pageRequest)

Использование точно такого же кода без PageRequest работает отлично. Но как только нумерация страниц появляется, она терпит неудачу. Я обнаружил, что после обновления с Spring-Boot 2.X запрос изменился, внутренне выполняемый JPA и переведенный в спящий режим:

Исходный запрос:

select key0_.ID                        as id1_0_,
       key0_."VALUE"                   as value_2_0_,
       ...
from MY_SCHEMA."KEY" key0_
where key0_."VALUE" like '%foo%'
order by key0_.ID desc
fetch first 100 rows only;

Новый запрос:

select k1_0.ID,
       k1_0."VALUE",
       ...
from MY_SCHEMA."KEY" k1_0
where k1_0."VALUE" like '%foo%'
order by k1_0.ID desc
offset 0 rows fetch first 100 rows only;

Оба запроса выполняются, если я выполняю их с помощью инструмента SQL к базе данных.

На данный момент я узнал, что он не собирается использовать Db2zOsDialect, а вместо этого делает что-то еще, включая offset. Это происходит в конструкторе DeferredResultSetAccess:

Исключение:

org.hibernate.exception.GenericJDBCException: JDBC exception executing SQL [select k1_0.ID, k1_0."VALUE", ... from MY_SCHEMA."KEY" k1_0 where k1_0."VALUE" like '%foo%' order by k1_0.ID desc offset 0 rows fetch first 100 rows only] [DB2 SQL Error: SQLCODE=-4743, SQLSTATE=56038, SQLERRMC=null, DRIVER=4.32.28] [n/a]

Журналы ошибок:

2024-04-04T09:36:41.769+02:00 ERROR 22710 --- [nio-9080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper   : DB2 SQL Error: SQLCODE=-4743, SQLSTATE=56038, SQLERRMC=null, DRIVER=4.32.28
2024-04-04T09:36:41.769+02:00 ERROR 22710 --- [nio-9080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper   : DB2 SQL Error: SQLCODE=-516, SQLSTATE=26501, SQLERRMC=null, DRIVER=4.32.28
2024-04-04T09:36:41.769+02:00 ERROR 22710 --- [nio-9080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper   : DB2 SQL Error: SQLCODE=-514, SQLSTATE=26501, SQLERRMC=SQL_CURLH200C1, DRIVER=4.32.28

Вопросы:

  • Почему используется «смещение», а не мой LimitHandler
  • Я знаю, что в hibernate 6 были обновления «псевдонимов», но почему это имело эффект?
  • Это внутренняя проблема JPA/Hibernate/JDBC?

Обновлено:

Диалект подхвачен, тем не менее Hibernate делает кое-какие сомнительные вещи:

Конфигурация Spring-Boot application.yml:

spring:
  jpa:
    hibernate:
      ddl-auto: none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        dialect: com.my.package.Db2zOsDialect
        default_schema: MY_SCHEMA
        criteria:
          literal_handling_mode: inline
        hbm2ddl:
          halt_on_error: true
    open-in-view: false
  datasource:
    url: jdbc:db2://someserver:1234/DBNAME:sslConnection=true;currentSchema=MY_SCHEMA;useUnicode=yes;characterEncoding=UTF-8;
    username: someuser
    password: sumepassword
    driver-class-name: com.ibm.db2.jcc.DB2Driver

Обновлено еще раз:

Я удалил Specification и Sort из кода, но все равно получил ошибку:

repo.findAll(PageRequest.of(0, 100))

также запрос стал намного проще:

select k1_0.ID, k1_0."VALUE" from MY_SCHEMA."KEY" k1_0 offset 0 rows fetch first 100 rows only

Теперь я даже попытался обновить драйвер JDBC до последней версии, используя com.ibm.db2:jcc:11.5.9.0 в качестве зависимости. Но теперь возникает новая проблема:

com.ibm.db2.jcc.am.SqlInvalidAuthorizationSpecException: [jcc][t4][2010][11246][4.33.31] Connection authorization failure occurred.  Reason: Local security service non-retryable error. ERRORCODE=-4214, SQLSTATE=28000

Я также перешел на более старую версию драйвера JDBC, но проблема остается той же.

Как вы настроили Hibernate, использует ли он ваш диалект (видимо, нет, что означает, что вы что-то настроили неправильно).

M. Deinum 04.04.2024 10:02

Это просто неправильно, что ты сказал. Он использует мой диалект, но LimitHandler заменен на LegacyDB2LimitHandler. Я не знаю, почему это произошло, но я все равно перешел на него сейчас, поскольку мой собственный LimitHandler делал то же самое (устаревший код 2012 года). Обновлено: попробуйте перезаписать getLimitHandler() и вернуть экземпляр AbstractLimitHandler и убедитесь сами.

Pwnstar 04.04.2024 10:37

Удалите spring.jpa.database из вашей конфигурации, которая будет переопределять database-platform. Используйте один или другой, а не оба. Поскольку вы также устанавливаете hibernate.dialect, вы также можете удалить spring.jpa.database-platform (или hibernate.dialect), так как в конечном итоге он устанавливает то же самое. Вы сами заявили, что здесь не используется ваш Db2zOsDialect, это был не мой вывод (ну, он был основан на том, что вы написали).

M. Deinum 04.04.2024 10:37

@M.Deinum spring.jpa.database не повлиял на проблему. Это всего лишь попытка с моей стороны решить проблему. Проблема была там, до этого даже добавлялась.

Pwnstar 04.04.2024 10:40
database-platform удалено, проблема не изменилась.
Pwnstar 04.04.2024 10:55

Глядя на код спящего режима, я получаю DB2SqlAstTranslator, который также возвращается из Dialect с использованием метода getSqlAstTranslatorFactory. В конечном итоге именно это и создает запрос. Там есть метод supportsOffsetClause, который проверяет только версию. Вы можете переопределить этот метод, чтобы вернуть false. Но странно, что выполнение запроса успешно выполняется с помощью обычного инструмента, но не выполняется через JPA. Коды ошибок указывают на некоторую проблему с подготовкой оператора (поскольку я подозреваю, что это какой-то фиктивный SQL, а не оригинал, это трудно определить).

M. Deinum 04.04.2024 11:11

@M.Deinum На самом деле я удалил Specification и просто использовал repo.findAll(PageRequest.of(0, 100)), что выдает ту же ошибку. Так что это не обязательно связано с предложением where. Как я и ожидал, выбор теперь оказался довольно простым select k1_0.ID, k1_0."VALUE" from MY_SCHEMA."KEY" k1_0 offset 0 rows fetch first 100 rows only. Как мне перезаписать supportOffsetClause?

Pwnstar 04.04.2024 12:08

@M.Deinum, пожалуйста, напишите ответ, чтобы заменить public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {} на диалекте. отключение решило проблему. Я сделал собственный: LegacyDb2SqlAstTranslator<T extends JdbcOperation> extends DB2zSqlAstTranslator<T> который supportOffsetClause возвращает false. И теперь все работает!

Pwnstar 04.04.2024 12:31

Какая версия JDBC-.driver используется вашим приложением? Какая версия у вашего SQL-инструмента? OFFSET был представлен только в DB2 V12, поэтому требуется достаточно высокий уровень APPLCOMPAT для пакетов, используемых вашим драйвером. Это может объяснить разницу между вашим приложением и инструментом SQL.

piet.t 05.04.2024 07:39
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
9
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Глядя на код гибернации, я получаю DB2SqlAstTranslator, который также возвращается из Dialect с использованием метода getSqlAstTranslatorFactory. DB2SqlAstTranslator — это то, что в конечном итоге создает запрос. Вероятно, это произошло из-за рефакторинга всей обработки AST в Hibernate 6+.

У DB2SqlAstTranslator есть метод supportsOffsetClause, который проверяет только версию и возвращает true. Когда true возвращается, он переходит по пути, который генерирует запрос, с offset 0 ..., если он возвращает false, он возвращает более старый запрос.

Вы можете переопределить этот метод, чтобы вернуть false, а затем в своем Db2zOsDialect вернуть свой собственный AstTranslatorFactory.

Но странно, что выполнение запроса успешно выполняется с помощью обычного инструмента, но не выполняется через JPA. Коды ошибок указывают на некоторую проблему с подготовкой оператора (поскольку я подозреваю, что это какой-то фиктивный SQL, а не оригинал, это трудно определить). Возможно, вы также захотите зарегистрировать проблему в Hibernate.

Другие вопросы по теме