为什么 Hibernate 内联传递给 JPA Criteria Query 的 Integer 参数列表?

Why Hibernate inlines Integer parameter list passed to JPA Criteria Query?

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

基于 int 属性构建的第一个谓词生成 SQL,内联参数集合的所有元素:in (10, 20, 30).

基于 String 属性生成的第二个谓词生成参数化 SQL:in (?, ?, ?)

让我展示一下:

实体:

@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. 为什么 Integer 列表的元素直接内联到 sql 而 String 列表的元素作为准备语句参数处理?
  2. 此功能是 Hibernate 特有的还是由 JPA 保证?
  3. 从数据库的角度来看,应该首选两者中的哪一个?
  4. 这个 int-yes string-no 内联是否以某种方式与 sql 注入有关?
  5. 这是否与 RDMBS 可以处理的 sql IN 子句中值的数量限制有某种关系?
  6. 如何编写一个标准查询,它将以与处理字符串参数列表相同的方式处理整数参数列表。
  1. 因为字符串可以包含 SQL 而整数不能,所以从安全方面来说没有必要(SQL 注入)。
  2. JPA 规范没有像您希望的那样明确指定它。好像是一个实现细节。
  3. 为字符串参数准备语句参数。对于 int 参数,这并不重要,因为它们不会被黑客滥用。
  4. 您应该在您正在使用的特定数据库的文档中查找它。 JPA 不关心这些事情。
  5. 为什么?有什么好处?当您不知道自己在改进什么时,不要尝试改进。

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

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

然而,真正的问题是为什么要将文字直接插入查询而不是使用绑定。原来的原因是:

So iirc the issue that lead me to use literals here had to do with scale and operations. Meaning (again, iirc) some databases needed to know type information to be able to properly handle something like ... ? + ? ..., etc. So the choice was to either wrap all such params in CAST function calls and hope/pray the db implemented a proper CAST function or use literals. In the end I opted for the literal route because, well, thats what the user asked for up front. Wrapping in function calls will limit the databases ability to leverage indexes in quite a few databases.

哪个数据库比较好?

这取决于数据库和查询,可能不会有太大的不同。例如,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.

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

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

如果使用此参数,则不再需要 Pace 提出的冗长解决方案。

来自 literal_handling_mode 的休眠文档:

This enum defines how literals are handled by JPA Criteria. By default (AUTO), Criteria queries uses bind parameters for any literal that is not a numeric value. However, to increase the likelihood of JDBC statement caching, you might want to use bind parameters for numeric values too. The BIND mode will use bind variables for any literal value. The INLINE mode will inline literal values as-is. To prevent SQL injection, never use INLINE with String variables. Always use constants with the INLINE mode.