将空列表作为参数传递给JPA查询会引发错误

如果我将空列表传递给JPA查询,我会收到错误。 例如:

List 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问题跟踪中有一个这样的票,但那里没有很多评论/活动。 我不知道其他ORM产品或JPA规范中的支持。

我不喜欢每次都必须手动检查空对象和空列表的想法。 是否有一些众所周知的方法/扩展? 你是如何处理这些情况的?

根据JPA 1.0规范中的4.6.8表达式

逗号分隔列表中必须至少有一个元素定义IN表达式的值集。

换句话说,无论Hibernate解析查询和传递IN()的能力如何,无论特定数据库支持这种语法(PosgreSQL不根据Jira问题),你应该在这里使用动态查询您希望您的代码是可移植的(我通常更喜欢使用Criteria API进行动态查询)。

在没有实际的解决方案作为回复之后,我创建了一个代理类来处理这些情况。 我们的想法是尽可能保留原生语法。

警告:这是一项正在进行中且非常危险的方法。 下面的代码绝不意味着完整的解决方案,很可能包含数以万计的错误和可怕的案例。

话虽这么说,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 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 select = Lists.newArrayList(); private List whereFrom = Lists.newArrayList(); private String from; private List 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 fields = Lists.newArrayList(); /** Split parameters into individual entries **/ int i = 0; for (Enum g : (List>) 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 it = whereFrom.iterator(); it.hasNext();) { ClausePair p = it.next(); if (StringUtils.contains(p.getWhere(), fieldName)) { it.remove(); } } } return this; } private String buildQueryString() { List from = Lists.newArrayList(); List 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(); } } 

解:

 if (municipalities==null || municipalities.isEmpty()) .setParameter("municipalities", "''") else .setParameter("municipalities", municipalities)