使用大型列表解决Restrictions.in的更好方法是什么?

时间:2015-07-02 09:46:45

标签: java oracle hibernate

已经确定,当您使用Hibernate的 Restrictions.in(字符串属性,列表列表)时,您必须限制列表的大小。
这是因为数据库服务器可能无法处理长查询。除了调整数据库服务器的配置外。

以下是我找到的解决方案:

解决方案1:将列表拆分为较小的列表,然后将较小的列表分别添加到几个限制中。

public List<Something> findSomething(List<String> subCdList) {
    Criteria criteria = getSession().createCriteria(getEntityClass());
    //if size of list is greater than 1000, split it into smaller lists. See List<List<String>> cdList
    if(subCdList.size() > 1000) {
        List<List<String>> cdList = new ArrayList<List<String>>();
        List<String> tempList = new ArrayList<String>();
        Integer counter = 0;
        for(Integer i = 0; i < subCdList.size(); i++) {
            tempList.add(subCdList.get(i));
            counter++;
            if(counter == 1000) {
                counter = 0;
                cdList.add(tempList);
                tempList = new ArrayList<String>();
            }
        }

        if(tempList.size() > 0) {
            cdList.add(tempList);
        }
        Criterion criterion = null;
        //Iterate the list of lists, add the restriction for smaller list
        for(List<String> cds : cdList) {
            if (criterion == null) {
                criterion = Restrictions.in("subCd", cds);
            } else {
                criterion = Restrictions.or(criterion, Restrictions.in("subCd", cds));
            }
        }
        criteria.add(criterion);
    } else {
        criteria.add(Restrictions.in("subCd", subCdList));
    }
    return criteria.list();
}

这是一个很好的解决方案,因为您只有一个select语句。但是,我认为在DAO层上设置for循环是一个坏主意,因为我们不希望连接长时间打开。

解决方案2:使用DetachedCriteria。而不是传递列表,而是在WHERE子句上查询它。

public List<Something> findSomething() {

    Criteria criteria = getSession().createCriteria(getEntityClass());

    DetachedCriteria detached = DetachedCriteria.forClass(DifferentClass.class);
    detached.setProjection(Projections.property("cd"));

    criteria.add(Property.forName("subCd").in(detached));

    return criteria.list();
}

此解决方案中的问题在于DetachedCriteria的技术用法。当您想要在当前类上完全没有连接(或没有关系)的另一个类创建查询时,通常会使用它。在该示例中,Something.class具有属性subCd,该属性是来自DifferentClass的外键。另一个,它在where子句上产生一个子查询。

当你看代码时:
1. 解决方案2 更简单明了 2.但 SOLUTION 1 提供只有一个选择的查询 请帮我决定哪一个更有效率。

感谢。

2 个答案:

答案 0 :(得分:4)

对于解决方案1: 而不是使用for循环,您可以尝试以下

为避免这种情况,如果传递的参数值的数量大于1000,则使用实用程序方法来构建Criterion Query IN子句。

class HibernateBuildCriteria {

private static final int PARAMETER_LIMIT = 800;

public static Criterion buildInCriterion(String propertyName, List<?> values) {
      Criterion criterion = null;
      int listSize = values.size();
      for (int i = 0; i < listSize; i += PARAMETER_LIMIT) {
      List<?> subList;
      if (listSize > i + PARAMETER_LIMIT) {
             subList = values.subList(i, (i + PARAMETER_LIMIT));
      } else {
             subList = values.subList(i, listSize);
      }
      if (criterion != null) {
       criterion = Restrictions.or(criterion, Restrictions.in(propertyName, subList));
     } else {
       criterion = Restrictions.in(propertyName, subList);
     }
    }
     return criterion;
   }
 }     

使用方法:

  

criteria.add(HibernateBuildCriteria.buildInCriterion(propertyName,list));

希望这会有所帮助。

答案 1 :(得分:2)

解决方案1 ​​有一个主要缺点:您最终可能需要解析许多不同的预处理语句,并且需要计算和缓存执行计划。此过程可能比数据库已缓存该语句的查询的实际执行要昂贵得多。有关详细信息,请参阅此question

我解决这个问题的方法是利用Hibernate使用的algorithm批量提取延迟加载的关联实体。基本上,我使用ArrayHelper.getBatchSizes来获取id的子列表,然后我为每个子列表执行单独的查询。

仅当您可以在子查询中投影ID时,

解决方案2 才适用。但如果你不能,那么你就不能使用它。例如,您的应用的用户在屏幕上编辑了20个实体,现在他们正在保存更改。您必须按ID读取实体以合并更改,并且无法在子查询中表达它。

然而,解决方案2的另一种方法可能是使用临时表。例如,Hibernate does it sometimes用于批量操作。您可以将id存储在临时表中,然后在子查询中使用它们。我个人认为这与解决方案1相比是一个不必要的复杂问题(当然,对于这个用例; Hibernate的推理对他们的用例很有用),但它是一个有效的替代方案。