为什么休眠内联整数参数列表传递给 JPA 条件查询?

我正在使用JPA Criteria API构建一个查询。当我使用方法创建两个限制谓词时,生成的SQL查询与我执行的略有不同。javax.persistence.criteria.Path#in(Collection<?>)

第一个谓词是在属性上构建的,它生成了 SQL,其中内联了参数集合的所有元素:。intin (10, 20, 30)

第二个谓词是在属性上构建的,它生成了参数化的 SQL: 。Stringin (?, ?, ?)

让我来演示一下:

实体:

@Entity
public class A {
    @Id 
    private Integer id;
    private int intAttr;
    private String stringAttr;
    //getter/setters
}

查询:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<A> q = cb.createQuery(A.class);
Root<A> root = q.from(A.class);
q.where(
    root.get("intAttr").in(Arrays.asList(10, 20, 30)),
    root.get("stringAttr").in(Arrays.asList("a", "b", "c"))
);
entityManager.createQuery(q).getResultList();

日志:

select
    a0_.id as id1_0_,
    a0_.intAttr as intAttr2_0_,
    a0_.stringAttr as stringAt3_0_ 
from
    A a0_ 
where
    (
        a0_.intAttr in (
            10 , 20 , 30
        )
    ) 
    and (
        a0_.stringAttr in (
            ? , ? , ?
        )
    ) 
org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [a] 
org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [VARCHAR] - [b] 
org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [VARCHAR] - [c] 

我的问题:

  1. 为什么整数列表的元素直接内联到 sql,而字符串列表的元素作为预准备语句参数进行处理?
  2. 此功能是特定于休眠的还是由 JPA 保证的?
  3. 从数据库的角度来看,两者中哪一个应该更可取?
  4. 这种 int-yes string-no 内联是否以某种方式与 sql 注入相关?
  5. 这是否与 RDMBS 可以处理的 sql IN 子句中值数量的限制有关?
  6. 如何编写条件查询,该查询将以与字符串参数列表相同的方式处理整数参数列表。

答案 1

为什么字符串绑定而数字文本未绑定?

应始终对字符串进行参数绑定(而不是将文本放在查询中),以避免SQL注入。

但是,真正的问题是,为什么将文本直接插入到查询中,而不是使用绑定。最初的原因是:

因此,导致我在这里使用文字的问题与规模和操作有关。这意味着(再次,iirc)一些数据库需要知道类型信息才能正确处理类似... ?+ ?等等。因此,选择是将所有此类参数包装在CAST函数调用中,并希望/祈祷db实现了正确的CAST函数或使用文本。最后,我选择了文字路线,因为,嗯,这就是用户预先要求的。包装函数调用将限制数据库利用相当多的数据库中的索引的能力。

哪个对数据库更好?

这取决于数据库和查询,可能不会产生巨大的差异。例如,Oracle 只能在值为文本时执行某些分区,其他数据库只能在值为绑定参数时执行某些优化。如果它成为一个问题(例如,你分析它,你知道这就是减慢你速度的原因),那么只需切换到另一种方法。

这是否在 JPA 规范中?

不。

这是否与 in 语句中允许的值数相关?

不。

我可以有一个数字文本绑定,而不是直接插入到查询中吗?

是的,但它有点冗长。

CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Foo> query = cb.createQuery(Foo.class);
Root<Foo> root = query.from(Foo.class);
ParameterExpression<Long> paramOne = cb.parameter(Long.class);
Predicate versionPredicate = root.get("bar").in(paramOne);
query.select(root).where(versionPredicate);
TypedQuery<Foo> typedQuery = getEntityManager().createQuery(query);
typedQuery.setParameter(paramOne, 1L);

这将长时间使用参数绑定。它只是一个参数,但可以很容易地从这里推断出多个参数,帮助器方法可以清理东西。

引用:

大多数推理在HHH-6280中进行了解释和讨论。进行此呈现的特定方法是 LiteralExpression.render


答案 2

在问题 HHH-9576 中,添加了一个新参数来解决此问题,该参数自版本 5.2.12 (?) 起适用

<property name="hibernate.criteria.literal_handling_mode" value="bind"/>

如果您使用此参数,则不再需要 Pace 建议的详细解决方案。

literal_handling_mode的休眠文档中:

此枚举定义了 JPA 条件如何处理文本。默认情况下 (AUTO),条件查询对任何非数值的文本使用绑定参数。但是,为了增加 JDBC 语句缓存的可能性,您可能还希望对数值使用绑定参数。BIND 模式将对任何文本值使用绑定变量。内联模式将按原样内联文本值。若要防止 SQL 注入,切勿将 INLINE 与字符串变量结合使用。始终在内联模式下使用常量。


推荐