Итак, у меня есть следующая настройка приложения 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
Вопросы:
Обновлено:
Диалект подхвачен, тем не менее 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, но проблема остается той же.
Это просто неправильно, что ты сказал. Он использует мой диалект, но LimitHandler заменен на LegacyDB2LimitHandler. Я не знаю, почему это произошло, но я все равно перешел на него сейчас, поскольку мой собственный LimitHandler делал то же самое (устаревший код 2012 года). Обновлено: попробуйте перезаписать getLimitHandler() и вернуть экземпляр AbstractLimitHandler и убедитесь сами.
Удалите spring.jpa.database из вашей конфигурации, которая будет переопределять database-platform. Используйте один или другой, а не оба. Поскольку вы также устанавливаете hibernate.dialect, вы также можете удалить spring.jpa.database-platform (или hibernate.dialect), так как в конечном итоге он устанавливает то же самое. Вы сами заявили, что здесь не используется ваш Db2zOsDialect, это был не мой вывод (ну, он был основан на том, что вы написали).
@M.Deinum spring.jpa.database не повлиял на проблему. Это всего лишь попытка с моей стороны решить проблему. Проблема была там, до этого даже добавлялась.
database-platform удалено, проблема не изменилась.
Глядя на код спящего режима, я получаю DB2SqlAstTranslator, который также возвращается из Dialect с использованием метода getSqlAstTranslatorFactory. В конечном итоге именно это и создает запрос. Там есть метод supportsOffsetClause, который проверяет только версию. Вы можете переопределить этот метод, чтобы вернуть false. Но странно, что выполнение запроса успешно выполняется с помощью обычного инструмента, но не выполняется через JPA. Коды ошибок указывают на некоторую проблему с подготовкой оператора (поскольку я подозреваю, что это какой-то фиктивный SQL, а не оригинал, это трудно определить).
@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?
@M.Deinum, пожалуйста, напишите ответ, чтобы заменить public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {} на диалекте. отключение решило проблему. Я сделал собственный: LegacyDb2SqlAstTranslator<T extends JdbcOperation> extends DB2zSqlAstTranslator<T> который supportOffsetClause возвращает false. И теперь все работает!
Какая версия JDBC-.driver используется вашим приложением? Какая версия у вашего SQL-инструмента? OFFSET был представлен только в DB2 V12, поэтому требуется достаточно высокий уровень APPLCOMPAT для пакетов, используемых вашим драйвером. Это может объяснить разницу между вашим приложением и инструментом SQL.




Глядя на код гибернации, я получаю DB2SqlAstTranslator, который также возвращается из Dialect с использованием метода getSqlAstTranslatorFactory. DB2SqlAstTranslator — это то, что в конечном итоге создает запрос. Вероятно, это произошло из-за рефакторинга всей обработки AST в Hibernate 6+.
У DB2SqlAstTranslator есть метод supportsOffsetClause, который проверяет только версию и возвращает true. Когда true возвращается, он переходит по пути, который генерирует запрос, с offset 0 ..., если он возвращает false, он возвращает более старый запрос.
Вы можете переопределить этот метод, чтобы вернуть false, а затем в своем Db2zOsDialect вернуть свой собственный AstTranslatorFactory.
Но странно, что выполнение запроса успешно выполняется с помощью обычного инструмента, но не выполняется через JPA. Коды ошибок указывают на некоторую проблему с подготовкой оператора (поскольку я подозреваю, что это какой-то фиктивный SQL, а не оригинал, это трудно определить). Возможно, вы также захотите зарегистрировать проблему в Hibernate.
Как вы настроили Hibernate, использует ли он ваш диалект (видимо, нет, что означает, что вы что-то настроили неправильно).