已经确定,当您使用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 提供只有一个选择的查询
请帮我决定哪一个更有效率。
感谢。
答案 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的子列表,然后我为每个子列表执行单独的查询。
解决方案2 才适用。但如果你不能,那么你就不能使用它。例如,您的应用的用户在屏幕上编辑了20个实体,现在他们正在保存更改。您必须按ID读取实体以合并更改,并且无法在子查询中表达它。
然而,解决方案2的另一种方法可能是使用临时表。例如,Hibernate does it sometimes用于批量操作。您可以将id存储在临时表中,然后在子查询中使用它们。我个人认为这与解决方案1相比是一个不必要的复杂问题(当然,对于这个用例; Hibernate的推理对他们的用例很有用),但它是一个有效的替代方案。