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

Hibernate 6升级后DB2 z/OS查询参数CAST问题求助

Hibernate 6升级后DB2 z/OS 12移除CAST语句的方法

代码与场景说明

Repository代码

@Repository
public interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> {

  @Query(
  """
      SELECT o FROM Foo o 
      JOIN Fee e on o.feeId = e.id 
      JOIN Fii i on e.fiiId = i.id 
      WHERE i.id = :fiiId 
      AND o.id IN (
        SELECT u.id 
        FROM Fuu u  
        JOIN Foo o2 on u.fooId = o2.id 
        WHERE u.something LIKE CONCAT('%', :param1, '_', :param2, '%')
      )""")
  List<OptionRule> findByFiiIdAndFuuLikeSomeComputed(
     Long fiiId, String param1, String param2); // 已将char改为String修复类型转换错误
}

字段定义

u.something字段的实体定义:

@Nullable
@Column(name = "SOMETHING", length = 25000)
private String something;

Hibernate版本差异

升级前(旧版本)

生成的SQL无CAST语句:

select foo0_.ID                 as id1_18_,
       ...
from FOO foo0_
         inner join FEE fee1_ on (foo0_.FEE_ID = fee1_.ID)
         inner join FII fii2_ on (fee1_.FII_ID = fii2_.ID)
where fii2_.ID = ?
and (foo0_.ID in (select foo4_.ID
       from FUU fuu3_
                inner join FOO foo4_
                           on (fuu3_.FOO_ID = fuu4_.ID)
       where foo3.SOMETHING like '%' || ? || '_' || ? || '%'))

升级后(Hibernate 6)

  1. 初始错误:"Could not convert 'java.lang.Character' to 'java.lang.String' using 'org.hibernate.type.descriptor.java.StringJavaType' to wrap",已通过将参数类型从char改为String修复。
  2. 新问题:生成的SQL包含CAST语句,但DB2 z/OS 12不支持该语法:
select fo1_0.ID,
       ...
from FOO fo1_0
     join FEE fe1_0 on fo1_0.FEE_ID = fe1_0.ID
     join FII fi1_0 on fe1_0.FII_ID = fi1_0.ID
where fi1_0.ID = ?
and fo1_0.ID in (
    select 
      fo2_0.ID
    from FUU fu1_0
      join FOO fo2_0 on fu1_0.FOO_ID = fo2_0.ID
    where fu1_0.SOMETHING like cast('%' || cast(? as varchar(25000)) || '_' || cast(? as varchar(25000)) || '%' as varchar(32672)))

当前尝试的自定义Dialect(未解决CAST问题)

LegacyDb2Dialect.java

import org.hibernate.dialect.DB2zDialect;
import org.hibernate.dialect.pagination.LegacyDB2LimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;

public class LegacyDb2Dialect extends DB2zDialect {
  
  @Override
  public LimitHandler getLimitHandler() {
    return LegacyDB2LimitHandler.INSTANCE;
  }

  @Override
  public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
    return new StandardSqlAstTranslatorFactory() {
      @Override
      protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
          SessionFactoryImplementor sessionFactory, Statement statement) {
        return new LegacyDb2SqlAstTranslator<>(sessionFactory, statement, getVersion());
      }
    };
  }
}

LegacyDb2SqlAstTranslator.java

import org.hibernate.dialect.DB2zSqlAstTranslator;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;

public class LegacyDb2SqlAstTranslator<T extends JdbcOperation> extends DB2zSqlAstTranslator<T> {

  public LegacyDb2SqlAstTranslator(
      SessionFactoryImplementor sessionFactory, Statement statement, DatabaseVersion version) {
    super(sessionFactory, statement, version);
  }

  @Override
  protected boolean supportsOffsetClause() {
    return false;
  }
}

补充发现

使用Specification/CriteriaBuilder处理bigint类型字段时,同样会生成CAST语句。


解决方案

方法1:重写Dialect中的字符串拼接函数逻辑

Hibernate 6生成CAST是为了保证字符串拼接后的类型匹配,针对DB2 z/OS特性,可在自定义Dialect中重写CONCAT函数,避免自动生成CAST:

import org.hibernate.dialect.DB2zDialect;
import org.hibernate.dialect.function.StandardSQLFunctionTemplate;
import org.hibernate.type.StringType;
import org.hibernate.dialect.pagination.LegacyDB2LimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;

public class LegacyDb2Dialect extends DB2zDialect {
    public LegacyDb2Dialect() {
        super();
        // 重写CONCAT函数,直接使用DB2的字符串拼接语法,指定返回类型为StringType
        registerFunction("concat", new StandardSQLFunctionTemplate(StringType.INSTANCE, "?1 || ?2 || ?3 || ?4 || ?5"));
    }

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

    @Override
    public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
        return new StandardSqlAstTranslatorFactory() {
            @Override
            protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
                    SessionFactoryImplementor sessionFactory, Statement statement) {
                return new LegacyDb2SqlAstTranslator<>(sessionFactory, statement, getVersion());
            }
        };
    }
}

方法2:在SqlAstTranslator中跳过参数CAST生成

修改自定义的LegacyDb2SqlAstTranslator,重写CAST处理逻辑,当遇到字符串参数的CAST时直接输出参数:

import org.hibernate.dialect.DB2zSqlAstTranslator;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.expression.Cast;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcOperation;

public class LegacyDb2SqlAstTranslator<T extends JdbcOperation> extends DB2zSqlAstTranslator<T> {

    public LegacyDb2SqlAstTranslator(
            SessionFactoryImplementor sessionFactory, Statement statement, DatabaseVersion version) {
        super(sessionFactory, statement, version);
    }

    @Override
    protected boolean supportsOffsetClause() {
        return false;
    }

    @Override
    protected void visitCast(Cast cast) {
        // 仅当CAST目标为字符串类型且源是参数时,跳过CAST直接输出参数
        if (cast.getTargetJdbcMapping().getJavaTypeDescriptor().getJavaTypeClass() == String.class
                && cast.getExpression() instanceof JdbcParameter) {
            visitExpression(cast.getExpression());
        } else {
            super.visitCast(cast);
        }
    }
}

方法3:调整参数类型映射规则

在自定义Dialect中指定String类型参数的默认JDBC类型映射,让Hibernate直接使用对应类型无需CAST:

import org.hibernate.dialect.DB2zDialect;
import org.hibernate.type.descriptor.java.StringJavaType;
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
import org.hibernate.dialect.pagination.LegacyDB2LimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;

public class LegacyDb2Dialect extends DB2zDialect {
    public LegacyDb2Dialect() {
        super();
        // 绑定String类型参数时直接映射为VarcharJdbcType
        getTypeConfiguration().getJavaTypeDescriptorRegistry().addDescriptor(
                StringJavaType.INSTANCE,
                (javaTypeDescriptor, sessionFactory) -> VarcharJdbcType.INSTANCE
        );
    }

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

    @Override
    public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
        return new StandardSqlAstTranslatorFactory() {
            @Override
            protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
                    SessionFactoryImplementor sessionFactory, Statement statement) {
                return new LegacyDb2SqlAstTranslator<>(sessionFactory, statement, getVersion());
            }
        };
    }
}

验证步骤

  1. 将Hibernate配置中的dialect替换为自定义的LegacyDb2Dialect
  2. 测试字符串拼接、bigint字段查询等场景,确认CAST语句已移除
  3. 验证查询结果与升级前一致,无类型匹配错误

内容的提问来源于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 04:45:02