Spring Boot连接DB2 z/OS分页查询触发JpaSystemException问题
问题背景
应用配置
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 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);
无分页时代码运行正常,添加分页后立即报错。Spring Boot 2.X升级到3.X后,JPA/Hibernate生成的SQL发生变化:
旧版本(Spring Boot 2.X)生成的SQL
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;
新版本(Spring Boot 3.X)生成的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;
注:两条SQL通过SQL工具执行均正常,但Hibernate执行新版本SQL时报错
异常信息
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不使用自定义的LimitHandler,反而生成带offset的SQL?
- Hibernate 6的别名更新为何会引发问题?
- 这是否属于JPA/Hibernate/JDBC的内部问题?
补充信息1:已确认方言正确加载
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))
生成SQL:
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
降级驱动后原问题依旧。
问题解答
1. 为何不使用自定义LimitHandler?
Hibernate 6中LimitHandler的生效逻辑发生了变化:
- 即使重写了
getLimitHandler(),Hibernate可能会根据数据库版本检测结果强制替换LimitHandler。DB2 z/OS v12.015理论上支持offset语法,但你的JDBC驱动版本(11.5.8.0)对该语法的兼容性存在问题。 - 另外,Hibernate 6引入了
DialectResolutionInfo机制,会通过JDBC元数据自动检测数据库版本,可能覆盖自定义的LimitHandler配置。可以尝试在自定义方言中显式设置supportsOffsetFetch()为false,强制使用Legacy模式:@Override public boolean supportsOffsetFetch() { return false; }
2. Hibernate 6的别名更新影响?
Hibernate 6对SQL别名生成逻辑做了优化(比如从key0_改为k1_0),但这个本身不会直接导致报错。真正的问题是:
- 带
offset的SQL触发了JDBC驱动的兼容性bug,别名变化只是巧合,和报错无直接关联。SQL工具执行正常但Hibernate通过JDBC执行时报错,是因为驱动对offset语法的处理存在内部错误(SQLCODE=-4743通常是驱动与数据库版本不匹配导致的语法解析问题)。
3. 是否属于内部问题?
属于JDBC驱动与Hibernate版本的兼容性问题:
- DB2 z/OS v12.015支持
offset语法,但你的JCC驱动版本(11.5.8.0)在通过JDBC执行该语法时存在内部错误(SQLCODE=-4743、-516、-514都是驱动层面的执行异常)。 - Hibernate 6的LimitHandler自动适配逻辑加剧了这个问题,即使自定义了方言,仍可能被自动检测的数据库版本覆盖。
解决方案建议
- 修复自定义方言:在
Db2zOsDialect中增加supportsOffsetFetch()重写,强制禁用offset语法:@Override public boolean supportsOffsetFetch() { return false; } - 调整JDBC驱动参数:在数据库URL中添加
useFetchFirst=true,强制驱动使用fetch first而非offset语法:datasource: url: jdbc:db2://someserver:1234/DBNAME:sslConnection=true;currentSchema=MY_SCHEMA;useUnicode=yes;characterEncoding=UTF-8;useFetchFirst=true; - 排查驱动授权问题:升级驱动到11.5.9.0时出现的授权错误,需要检查用户名密码、SSL配置或数据库侧的权限设置,确保驱动版本与数据库的授权机制兼容。
内容的提问来源于stack exchange,提问作者Pwnstar
相关产品推荐
相关产品推荐

