java - query - jpql pdf
Pasar la lista vacía como parámetro a la consulta JPA arroja un error (3)
Si paso una lista vacía en una consulta JPA, aparece un error. Por ejemplo:
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();
Como la lista está vacía, Hibernate la genera en SQL como "IN ()", lo que me da error con la base de datos Hypersonic.
Hay un ticket para esto en el seguimiento de problemas de Hibernate, pero no hay muchos comentarios / actividades allí. No sé sobre soporte en otros productos ORM o en la especificación JPA tampoco.
No me gusta la idea de tener que verificar manualmente los objetos nulos y las listas vacías cada vez. ¿Hay alguna aproximación / extensión comúnmente conocida para esto? ¿Cómo manejas estas situaciones?
De acuerdo con la sección 4.6.8 en expresiones de la especificación JPA 1.0:
Debe haber al menos un elemento en la lista separada por comas que define el conjunto de valores para la expresión
IN
.
En otras palabras, independientemente de la capacidad de Hibernate para analizar la consulta y pasar un IN()
, independientemente del soporte de esta sintaxis por bases de datos particulares (PosgreSQL no lo hace según el problema de Jira), debe usar una consulta dinámica aquí si desea que su código sea portátil (y generalmente prefiero usar la API de criterios para consultas dinámicas).
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 enfoque 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, las partes dinámicas se insertan con
query.from("p.address a");
query.where("a IN (:addresses)");
Los parámetros se insertan como siempre:
query.setParameter("addresses", addresses);
El punto aquí es que la clase los elimina (también de su parte) de la consulta si son listas vacías o los manipula si son listas de enumeraciones.
Luego llame:
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 (el código usa Lombok para las anotaciones @Data y @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();
}
}
Solución:
if (municipalities==null || municipalities.isEmpty())
.setParameter("municipalities", "''''")
else
.setParameter("municipalities", municipalities)