You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

核心问题

  1. 为何Hibernate不使用自定义的LimitHandler,反而生成带offset的SQL?
  2. Hibernate 6的别名更新为何会引发问题?
  3. 这是否属于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自动适配逻辑加剧了这个问题,即使自定义了方言,仍可能被自动检测的数据库版本覆盖。

解决方案建议

  1. 修复自定义方言:在Db2zOsDialect中增加supportsOffsetFetch()重写,强制禁用offset语法:
    @Override
    public boolean supportsOffsetFetch() {
        return false;
    }
    
  2. 调整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;
    
  3. 排查驱动授权问题:升级驱动到11.5.9.0时出现的授权错误,需要检查用户名密码、SSL配置或数据库侧的权限设置,确保驱动版本与数据库的授权机制兼容。

内容的提问来源于stack exchange,提问作者Pwnstar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 11:11:00