【问题标题】:Why Hibernate inlines Integer parameter list passed to JPA Criteria Query?为什么 Hibernate 内联整数参数列表传递给 JPA Criteria Query?
【发布时间】:2016-08-17 03:06:05
【问题描述】:

我正在使用 JPA Criteria 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. 从 DB 的角度来看,应该首选哪两个?
  4. 这种 int-yes string-no inlining 是否与 sql 注入有关?
  5. 这是否与 RDMBS 可以处理的 sql IN 子句中的值数量限制有关?
  6. 如何编写一个条件查询,它将像处理字符串参数列表一样处理整数参数列表。

【问题讨论】:

  • 真的好像是 Hibernate 论坛的问题,你不觉得吗?

标签: java hibernate jpa prepared-statement criteria-api


【解决方案1】:

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

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

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

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

哪个对数据库更好?

这取决于数据库和查询,可能不会有很大的不同。比如Oracle只能在value是一个字面量的时候做一定的分区,其他的数据库只能在value是一个绑定参数的时候做一定的优化。如果它成为一个问题(例如,您对其进行分析并且您知道这会减慢您的速度),那么只需切换到其他方法。

这在 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

【讨论】:

  • “应该始终对字符串进行参数绑定(而不是将文字放在查询中)以避免 SQL 注入。” - 这是一个很好的默认建议,但是“总是”是错误的。偶尔内联字符串文字也是有充分理由的。 “可能不会有很大的不同” - 您是否曾尝试从生产中的 cursor pin S wait on X 事件中恢复,因为严重的执行计划缓存争用?当您的数据库具有执行计划缓存时,您将尽可能需要绑定变量(并避免在 Oracle 中使用高度动态的 IN 列表)
【解决方案2】:

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

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

如果你使用这个参数,你就不再需要 Pace 提出的冗长的解决方案了。

来自literal_handling_mode的休眠文档:

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

【讨论】:

    【解决方案3】:
    1. 因为字符串可以包含 SQL 而整数不能包含,所以从安全方面(SQL 注入)来说没有必要。
    2. JPA 规范并未将其指定为您希望的那样明确。这似乎是一个实现细节。
    3. 为字符串参数准备了语句参数。对于 int 参数,这无关紧要,因为它们不会被黑客滥用。
    4. 是的
    5. 您应该在您正在使用的特定数据库的文档中查找它。 JPA 不关心这些事情。
    6. 为什么?有什么好处?当您不知道自己在改进什么时,不要试图改进。

    【讨论】:

    • 好处是有助于数据库的Query Plan Cache。如果查询是相同的,但它有一个变量作为文字传递,那么每次执行查询时数据库都必须创建一个新的查询计划,这会减慢执行速度。
    • 关于 1):正确转义内联字符串值并不难,这样 SQLi 就不会成为问题。毕竟,这是一个动态查询生成器,所以它可以全局处理。 3):从性能的角度来看是不正确的。这非常重要。在存在查询计划缓存的情况下,绑定变量是更好的默认值,但有时应该首选内联值。 4)你能支持你的假设吗?我个人对此表示怀疑。它只是看起来像一个错误。例如。 hibernate.atlassian.net/browse/HHH-9576 中没有给出这样的理由 5) 应该 6) 是什么让你这么认为?
    猜你喜欢
    • 2021-03-27
    • 2021-02-15
    • 1970-01-01
    • 2011-11-10
    • 1970-01-01
    • 1970-01-01
    • 2015-12-18
    • 2017-06-01
    • 2012-05-30
    相关资源
    最近更新 更多