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

如何让Hibernate使用IN (VALUES ...)替代常规IN查询以提升性能?

解决方案:生成IN (VALUES...)格式的查询

HQL本身不支持直接在IN子句中使用VALUES语法,要生成这类SQL,主要有以下几种可行方案:

方案1:使用原生SQL查询(最直接)

直接编写带VALUES子句的原生SQL,通过EntityManager.createNativeQuery执行,参数按顺序绑定即可。

// 构建VALUES部分的占位符,每个id对应一个(?)
String valuePlaceholders = IntStream.range(0, ids.size())
        .mapToObj(i -> "(?)")
        .collect(Collectors.joining(", "));

// 拼接完整原生SQL,注意替换为实际表名(可通过Hibernate元数据获取避免硬编码)
String nativeSql = String.format(
        "SELECT * FROM %s x WHERE x.id IN (VALUES %s)",
        entityTableName,
        valuePlaceholders
);

// 执行查询并绑定参数
List<T> result = entityManager.createNativeQuery(nativeSql, queryEntity)
        .setParameterList("ids", ids)
        .getResultList();

注:如果实体类和数据库表名不一致,可通过Hibernate的SessionFactory获取实体对应的表名,避免硬编码。

方案2:自定义Hibernate SQL函数(复用性强)

如果想在HQL中复用这类语法,可以注册一个自定义函数,让HQL解析时自动转换成VALUES格式的SQL。

步骤1:注册自定义函数

SessionFactory sessionFactory = entityManager.getEntityManagerFactory().unwrap(SessionFactory.class);
SqlFunctionRegistry functionRegistry = ((SessionFactoryImplementor) sessionFactory).getSqlFunctionRegistry();

// 注册名为value_list的自定义函数
functionRegistry.register("value_list", new SqlFunction() {
    @Override
    public boolean hasArguments() { return true; }
    @Override
    public boolean hasParenthesesIfNoArguments() { return true; }
    @Override
    public Type getReturnType(Type firstArgumentType, Mapping mapping) { return firstArgumentType; }

    @Override
    public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory) {
        int paramCount = ((Collection) args.get(0)).size();
        return "VALUES " + IntStream.range(0, paramCount)
                .mapToObj(i -> "(?)")
                .collect(Collectors.joining(", "));
    }
});

步骤2:在HQL中使用

SELECT x FROM ${entityName} x WHERE x.id IN value_list(:ids)

注:该方案需注意Hibernate版本兼容性,不同版本的API可能略有差异。

方案3:分批次查询(最简单的优化方案)

如果不想修改SQL语法,直接将大ids集合拆分为多个小批次(比如每次1000个),分别执行原有HQL查询后合并结果,也能有效解决性能问题,同时规避数据库对IN子句参数数量的限制。

int batchSize = 1000;
List<T> result = new ArrayList<>();

for (int i = 0; i < ids.size(); i += batchSize) {
    int endIndex = Math.min(i + batchSize, ids.size());
    List<Long> batchIds = ids.subList(i, endIndex);
    
    List<T> batchResult = entityManager.createQuery(
                    "FROM " + entityName + " x WHERE x.id IN :ids", queryEntity)
            .setParameter("ids", batchIds)
            .getResultList();
    result.addAll(batchResult);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 16:17:40