Considere el siguiente modelo JAVA para hibernar :
@Entity
@Table
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
@Column
public String firstName;
@Column
public String lastName;
@Column
public Boolean active;
}
y el siguiente modelo para la serialización de API (utilizando el controlador de Spring Boot Rest):
public class PersonVO {
public Long id;
public String fullName;
}
Lo que quiero es:
- Aplicar un poco de filtrado a la Persona (estáticamente definido)
- Aplicar un poco de filtrado en PersonVO (obtener de @RequestParam)
En C # .NET podría hacer como:
IQueryable<Person> personsQuery = entityFrameworkDbContext.Persons;
// FIRST POINT - Here i could make some predefined filtering like 'only active', 'from the same city'... at the database model
personsQueryWithPreDefinedFilters = personsQuery.Where(person => person.active == true);
IQueryable<PersonVO> personsProjectedToVO = personsQueryWithPreDefinedFilters.Select(person => new PersonVO()
{
id = person.id,
fullName = person.firstName + " " + person.lastName
});
// SECOND POINT - At this point i could add more filtering based at PersonVO model
if (!String.IsNullOrWhiteSpace(fullNameRequestParameter)) {
personsProjectedToVO = personsProjectedToVO.Where(personVO => personVO.FullName == fullNameRequestParameter);
}
// The generated SQL at database is with both where (before and after projection)
List<PersonVO> personsToReturn = personsProjectedToVO.ToList();
Lo que obtuve en Java es:
CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
CriteriaQuery<PersonVO> cq = cb.createQuery(PersonVO.class);
Root<Person> root = cq.from(Person.class);
// FIRST POINT - Here i could make some predefined filtering like 'only active', 'from the same city'... at the database model
cq.where(cb.equal(root.get(Person_.active), true));
Expression<String> fullName = cb.concat(root.get(Person_.firstName), root.get(Person_.lastName));
cq.select(cb.construct(
PersonVO.class,
root.get(Person_.id),
fullName
));
// SECOND POINT - At this point i could add more filtering based at PersonVO model??? HOW???
if (fullNameRequestParameter != null) {
cq.where(cb.equal(fullName, fullNameRequestParameter));
// i only could use based at the fullName expression used, but could i make a Predicate based only on PersonVO model without knowing or having the expression?
}
Quiero haber separado la "proyección al modelo VO" de la "expresión de donde" se le aplicó, pero que se aplique indirectamente si se usa una columna proyectada (como fullName).
¿Es esto posible en Java? ¿Usando qué? Criterios? Querydsl? ¿Corriente? (no se quede necesariamente con la muestra de Java)
java
hibernate
java-stream
projection
querydsl
jvitor83
fuente
fuente
Stream
s, podría haber hecho algo como:personList.stream().filter(p -> p.active).map(p -> new PersonV0(p.id, p.firstName + " " + p.lastName)).filter(pv -> pv.fullName.equals(fullNameRequestParameter)).collect(Collectors.toList());
donde sePredicate
usa el utilizado en el pingfilter
posteriormap
PersonV0
stream()
para consultar la base de datos. Creo que esto puede responder parcialmente a mi pregunta. Pero lo mantendré abierto para ver si alguien puede responder eso con un ejemplo concreto (preferiblemente usando hibernate como orm).Respuestas:
JPA Criteria API no tiene dicha funcionalidad. Además, no es fácil de leer 😊
API de criterios JPA
En la API de criterios, debe volver a utilizar el
Expression
.El código de trabajo se ve así:
El código SQL generado:
API de criterios JPA y
@org.hibernate.annotations.Formula
Hibernate tiene una anotación
org.hibernate.annotations.Formula
que puede simplificar un poco el código.Agregue a la entidad un campo calculado anotado con
@Formula("first_name || ' ' || last_name")
:Y en la consulta API de Criterios JPA, haga referencia al campo
fullName
:Y el SQL generado:
API de criterios de hibernación
Hibernate Criteria API (en desuso desde Hibernate 5.2 a favor de JPA Criteria API) permite usar alias. Pero no todas las bases de datos permiten usar alias (por ejemplo
(full_name || ' ' || last_name) as full_name
) en unawhere
cláusula.De acuerdo con los documentos de PostgreSQL :
Significa la consulta SQL
no funciona en PostgreSQL.
Entonces, usar un alias en una
where
cláusula no es una opción.fuente
Tenga en cuenta que debe llamar a los nombres de sus columnas iguales a "getters" en las interfaces (getFirstName = firstName)
Se llama proyección basada en interfaz. Luego puede crear una instancia de
PersonVO
:¿Es eso lo que necesitabas?
fuente
Usando esta biblioteca http://www.jinq.org/ podría hacerlo y aplicarme a la hibernación (y en consecuencia a la base de datos).
¡Gracias por toda la ayuda!
fuente