如果我将空列表传递给JPA查询,则会出错。例如:
List<Municipality> municipalities = myDao.findAll(); // returns empty list
em.createQuery("SELECT p FROM Profile p JOIN p.municipality m WHERE m IN (:municipalities)")
.setParameter("municipalities", municipalities)
.getResultList();
因为列表是空的,所以Hibernate在SQL中将其生成为“IN()”,这使我在Hypersonic数据库中出错。
Hibernate issue tracking中有一张此票,但那里的评论/活动不多。我不了解其他ORM产品或JPA规范中的支持。
我不喜欢每次都必须手动检查空对象和空列表的想法。是否有一些众所周知的方法/扩展?你是如何处理这些情况的?
答案 0 :(得分:33)
根据JPA 1.0规范中的 4.6.8 In表达式部分:
逗号分隔列表中必须至少有一个元素定义
IN
表达式的值集。
换句话说,无论Hibernate解析查询和传递IN()
的能力如何,不管特定数据库支持这种语法(PosgreSQL不根据Jira问题),你应该使用如果您希望代码是可移植的,请在此处进行动态查询(我通常更喜欢使用Criteria API进行动态查询)。
答案 1 :(得分:3)
我也在这个问题上挣扎。我发现Hibernate社区已在 Hibernate 5.4.10 版本中解决了该问题,这是故障单:https://hibernate.atlassian.net/browse/HHH-8091
您可以检查您的Hibernate版本System.out.println(org.hibernate.Version.getVersionString());
您可以将Hibernate的版本更新为最新版本,这里是一个有用的链接:https://bintray.com/hibernate/artifacts/hibernate-orm#
答案 2 :(得分:1)
在没有实际解决方案作为回复之后,我创建了一个代理类来处理这些情况。我们的想法是尽可能保留原生语法。
警告:这是一项正在进行中且非常危险的方法。下面的代码绝不意味着完整的解决方案,很可能包含数以万计的错误和可怕的案例。
话虽如此,BlankAwareQuery类包装了javax.persistence Query,并使用EntityManager和核心查询字符串(不能包含空列表或枚举列表)进行初始化。
BlankAwareQuery query = new BlankAwareQuery(em, "SELECT p FROM Profile p");
创建类后,将使用
插入动态部分query.from("p.address a");
query.where("a IN (:addresses)");
一如既往地插入参数:
query.setParameter("addresses", addresses);
这里的要点是,如果它们是空列表,则类会从查询中删除它们(它们也是部分),如果它们是枚举列表,则会对它们进行操作。
然后致电:
query.getResultList();
所以,例如:
List<Profile> profiles = new BlankAwareQuery(em, "SELECT p FROM Profile p")
.from("p.address a JOIN a.municipality m").where("m IN (:municipalities)")
.where("p.gender IN (:genders)")
.where("p.yearOfBirth > :minYear")
.where("p.yearOfBirth < :maxYear")
.from("p.platforms f").where("f IN (:platforms)")
.setParameter("municipalities", municipalities)
.setParameter("genders", genders)
.setParameter("minYear", minYear)
.setParameter("maxYear", maxYear)
.setParameter("platforms", platforms)
.getResultList();
实际代码(代码使用Lombok代表@Data和@NonNull注释,Apache公共代码使用StringUtils):
public class BlankAwareQuery {
private @Data class Parameter {
private @NonNull String fieldName;
private @NonNull Object value;
}
private @Data class ClausePair {
private @NonNull String from;
private @NonNull String where;
}
private EntityManager em;
private List<String> select = Lists.newArrayList();
private List<ClausePair> whereFrom = Lists.newArrayList();
private String from;
private List<Parameter> parameters = Lists.newArrayList();
Query query;
public BlankAwareQuery(EntityManager em, String query) {
this.em = em;
/** Select **/
int selectStart = StringUtils.indexOf(query, "SELECT ") + 7;
int selectEnd = StringUtils.indexOf(query, " FROM ");
select(StringUtils.substring(query, selectStart, selectEnd));
/** From **/
int fromStart = selectEnd + 6;
int fromEnd = StringUtils.indexOf(query, " WHERE ");
if (fromEnd == -1) fromEnd = query.length();
from(StringUtils.substring(query, fromStart, fromEnd));
/** Where **/
String where = "";
if (StringUtils.contains(query, " WHERE ")) {
where = StringUtils.substring(query, fromEnd + 7);
}
where(where);
}
private BlankAwareQuery select(String s) {
select.add(s);
return this;
}
public BlankAwareQuery from(String s) {
from = s;
return this;
}
public BlankAwareQuery where(String s) {
ClausePair p = new ClausePair(from, s);
whereFrom.add(p);
from = "";
return this;
}
public BlankAwareQuery setParameter(String fieldName, Object value) {
/** Non-empty collection -> include **/
if (value != null && value instanceof List<?> && !((List<?>) value).isEmpty()) {
/** List of enums -> parse open (JPA doesn't support defining list of enums as in (:blaa) **/
if (((List<?>) value).get(0) instanceof Enum<?>) {
List<String> fields = Lists.newArrayList();
/** Split parameters into individual entries **/
int i = 0;
for (Enum<?> g : (List<Enum<?>>) value) {
String fieldSingular = StringUtils.substring(fieldName, 0, fieldName.length() - 1) + i;
fields.add(":" + fieldSingular);
parameters.add(new Parameter(fieldSingular, g));
i++;
}
/** Split :enums into (:enum1, :enum2, :enum3) strings **/
for (ClausePair p : whereFrom) {
if (p.getWhere().contains(":" + fieldName)) {
int start = StringUtils.indexOf(p.getWhere(), ":" + fieldName);
int end = StringUtils.indexOfAny(StringUtils.substring(p.getWhere(), start + 1), new char[] {')', ' '});
String newWhere = StringUtils.substring(p.getWhere(), 0, start) + StringUtils.join(fields, ", ") + StringUtils.substring(p.getWhere(), end + start + 1);
p.setWhere(newWhere);
}
}
}
/** Normal type which doesn't require customization, just add it **/
else {
parameters.add(new Parameter(fieldName, value));
}
}
/** Not to be included -> remove from and where pair from query **/
else {
for (Iterator<ClausePair> it = whereFrom.iterator(); it.hasNext();) {
ClausePair p = it.next();
if (StringUtils.contains(p.getWhere(), fieldName)) {
it.remove();
}
}
}
return this;
}
private String buildQueryString() {
List<String> from = Lists.newArrayList();
List<String> where = Lists.newArrayList();
for (ClausePair p : whereFrom) {
if (!p.getFrom().equals("")) from.add(p.getFrom());
if (!p.getWhere().equals("")) where.add(p.getWhere());
}
String selectQuery = StringUtils.join(select, ", ");
String fromQuery = StringUtils.join(from, " JOIN ");
String whereQuery = StringUtils.join(where, " AND ");
String query = "SELECT " + selectQuery + " FROM " + fromQuery + (whereQuery == "" ? "" : " WHERE " + whereQuery);
return query;
}
public Query getQuery() {
query = em.createQuery(buildQueryString());
setParameters();
return query;
}
private void setParameters() {
for (Parameter par : parameters) {
query.setParameter(par.getFieldName(), par.getValue());
}
}
public List getResultList() {
return getQuery().getResultList();
}
public Object getSingleResult() {
return getQuery().getSingleResult();
}
}
答案 3 :(得分:1)
解决方案:
if (municipalities==null || municipalities.isEmpty())
.setParameter("municipalities", "''")
else
.setParameter("municipalities", municipalities)
答案 4 :(得分:1)
假定SQL查询类似于
(COALESCE(:placeHolderName,NULL) IS NULL OR Column_Name in (:placeHolderName))
现在,如果列表的类型为字符串,那么您可以通过
传递query.setParameterList("placeHolderName",
!CollectionUtils.isEmpty(list)? list : new ArrayList<String>(Arrays.asList("")).
如果列表包含Integer值,则语法如下:
If(!CollectionUtils.isEmpty(list)){
query.setParameterList("placeHolderName",list)
}else{
query.setParameter("placeHolderName",null, Hibernate.INTEGER)
}
答案 5 :(得分:1)
我今天遇到了同样的问题,@r_divyas's response 几乎为我解决了这个问题,但为了让它发挥作用,我必须做一些小改动。
我有一个查询保存在数据库中,因此我无法即时更改它。此查询根据结果集中的字段触发动态 UI 的构建。
在用户界面中,我有一个多选下拉菜单,这是可选的。因此可以从中选择 0、1 或多个元素。
因此,我需要找到一种方法使我的查询中的这个参数成为可选的。 最初,我将其设置为:
(:assignedUsers IS NULL OR wr.USR_ID IN (:assignedUsers))
不幸的是,这仅适用于空参数或单个元素的列表。在设计时,我没有使用列表中的多个元素对其进行测试。当我终于到达那里时,我遇到了以下错误:
SQL Error [4145] [S0001]: An expression of non-boolean type specified in a context where a condition is expected, near ','.
这是由以下代码引起的:
AND (('id1', 'id2', 'id3') IS NULL OR wr.USR_ID IN ('id1', 'id2', 'id3'))
然后我尝试了他的解决方案,当我的列表不为空或不为空时,它确实有效。但是当它是时,查询变成了这样:
AND (COALESCE('', NULL) IS NULL OR wr.USR_ID IN (''))
这导致结果集为空。
现在,继续我找到的解决方案。我所要做的就是更改查询中的条件:
AND (COALESCE(:assignedUsers, NULL) = '' OR wr.USR_ID IN (:assignedUsers))
这适用于空列表、空值、具有单个或多个元素的列表。
答案 6 :(得分:0)
我的简单解决方案是添加一个 aux 参数来标记条件,如下例所示:
where (:doNotCheckList = 1)
or (fieldName in :valuesList)
query
.setParameter("doNotCheckList", (!valuesList.isEmpty() ? 1 : 0))
.setParameter("valuesList", valuesList);
答案 7 :(得分:-1)
如果您正在使用spring / hibernate注释,那么解决此问题的最简单方法之一就是拥有两个参数。首先让我们定义我们的JPA批注:
+ "AND (p.myVarin :state OR :nullCheckMyVar is null) "
然后像往常一样传递这些参数:
@Param("myVarin") List<Integers> myVarin,
@Param("nullCheckMyVar") Integer nullCheckMyVar,
最后,在任何服务呼叫中,您都可以执行以下操作:
Integer nullCheckInteger = null;
if (myVarIn != null && !myVarIn.isEmpty()) {
// we can use any integer
nullCheckInteger = 1;
}
repo.callService(myVarIn, nullCheckInteger);
然后将这两个参数传递给调用。
这是做什么的?如果myVarIn不为null且已填充,则可以将“ all”指定为1!= null。否则,我们传入列表,并且null为null,因此被忽略。 如果为空,则选择“全部”,否则为空。
答案 8 :(得分:-1)
由于您要查询的数据库序列ID通常以1开始,因此可以在列表中添加0
if (excludeIds.isEmpty()) {
excludeIds.add(new Long("0"));
}
List<SomeEntity> retval = someEntityRepo.findByIdNotIn(excludeIds);
也许-1也可以。使用jpa仓库的小巧工作。