所以我对 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 和 hibernate 在内部执行:
原始查询:
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
编辑2:
我从代码中删除了
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
我现在甚至尝试使用
com.ibm.db2:jcc:11.5.9.0
作为依赖项来升级到最新的 JDBC 驱动程序。但这现在产生了一个新问题:
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 驱动程序,但问题仍然相同。
查看休眠代码,我最终看到
DB2SqlAstTranslator
,它也是使用 Dialect
方法从 getSqlAstTranslatorFactory
返回的。 DB2SqlAstTranslator
是最终创建查询的内容。这可能是由于 Hibernate 6+ 中整个 AST 处理的重构所致。
DB2SqlAstTranslator
有一个 supportsOffsetClause
方法,仅检查版本并返回 true
。当返回 true
时,它将进入生成带有 offset 0 ...
的查询的路径,如果它返回 false
,它将返回较旧的查询。
您可以重写该方法以返回
false
,然后在您的 Db2zOsDialect
中返回您的自定义 AstTranslatorFactory
。
但奇怪的是,在普通工具中查询执行成功,但在 JPA 中执行失败。错误代码表明准备语句时存在一些问题(因为我怀疑这是一些虚拟 SQL,而不是原始 SQL,这很难确定)。您可能还想向 Hibernate 注册问题。