Después de no tener una solución real como respuestas, creé una clase proxy para manejar estas situaciones. La idea es retener la sintaxis nativa cuando sea posible.
ADVERTENCIA: Este es un método de trabajo en progreso y muy peligroso. El siguiente código no significa en absoluto una solución completa y muy posiblemente contiene millones de errores y casos de miedo.
Dicho esto, la clase BlankAwareQuery ajusta la consulta javax.persistence y se inicializa con EntityManager y la cadena de consulta principal (que no puede contener listas vacías o listas de enumeraciones).
BlankAwareQuery query = new BlankAwareQuery(em, "SELECT p FROM Profile p");
Después de la creación de la clase, partes dinámicas se insertan con
query.from("p.address a");
query.where("a IN (:addresses)");
parámetros se insertan como siempre:
query.setParameter("addresses", addresses);
El punto aquí es que la clase elimina estos (su de- parte también) de la consulta si son listas vacías o las manipula si son listas de enumeraciones.
Entonces llama:
query.getResultList();
Así, por ejemplo:
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();
El código real (código utiliza Lombok para @Data y anotaciones @NonNull y Apache Commons Lang para 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();
}
}
"" IN() ", que me da error con la base de datos Hypersonic." Esto sucede con MySQL también. –