Consider the following JAVA model for hibernate:
@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;
}
and the following model for API serialization (using spring boot rest controller):
public class PersonVO {
public Long id;
public String fullName;
}
What i want is to:
In C# .NET i could make like:
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();
What i got in Java is:
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?
}
I want to have separated the "projection to the VO model" from the "where expression" applied to it, but have it indirectly applied if used a projected column (like fullName).
Is this possible in Java? Using what? Criteria? Querydsl? Stream? (don't get necessarily stick to the java sample)
JPA Criteria API doesn't have such functionality. Also, it is not easy to read 😊
In the Criteria API you need to reuse the Expression
.
The working code looks like this:
public List<PersonVO> findActivePersonByFullName(String fullName) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<PersonVO> cq = cb.createQuery(PersonVO.class);
Root<Person> root = cq.from(Person.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("active"), true));
Expression<String> fullNameExp =
cb.concat(cb.concat(root.get("firstName"), " "), root.get("lastName"));
cq.select(cb.construct(
PersonVO.class,
root.get("id"),
fullNameExp
));
if (fullName != null) {
predicates.add(cb.equal(fullNameExp, fullName));
}
cq.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(cq).getResultList();
}
The generated SQL code:
select
person0_.id as col_0_0_,
((person0_.first_name||' ')||person0_.last_name) as col_1_0_
from
person person0_
where
person0_.active=?
and (
(
person0_.first_name||?
)||person0_.last_name
)=?
@org.hibernate.annotations.Formula
Hibernate has an annotation org.hibernate.annotations.Formula
that can simplify the code a little.
Add to the entity a computed field annotated with @Formula("first_name || ' ' || last_name")
:
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
@Column
public String firstName;
@Column
public String lastName;
@Column
public boolean active;
@Formula("first_name || ' ' || last_name")
private String fullName;
//...getters and setters
}
And in the JPA Criteria API query reference the field fullName
:
public List<PersonVO> findActivePersonByFullName(String fullName) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<PersonVO> cq = cb.createQuery(PersonVO.class);
Root<Person> root = cq.from(Person.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("active"), true));
cq.select(cb.construct(
PersonVO.class,
root.get("id"),
root.get("fullName")
));
if (fullName != null) {
predicates.add(cb.equal(root.get("fullName"), fullName));
}
cq.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(cq).getResultList();
}
And the generated SQL:
select
person0_.id as col_0_0_,
person0_.first_name || ' ' || person0_.last_name as col_1_0_
from
person person0_
where
person0_.active=?
and person0_.first_name || ' ' || person0_.last_name=?
Hibernate Criteria API (deprecated since Hibernate 5.2 in favour of JPA Criteria API) allows to use aliases. But not all databases allows to use aliases (e.g. (full_name || ' ' || last_name) as full_name
) in a where
clause.
According to the PostgreSQL docs:
An output column's name can be used to refer to the column's value in ORDER BY and GROUP BY clauses, but not in the WHERE or HAVING clauses; there you must write out the expression instead.
It means the SQL query
select p.id,
(p.first_name || ' ' || p.last_name) as full_name
from person p
where p.active = true
and full_name = 'John Doe'
doesn't work in PostgreSQL.
So, using an alias in a where
clause is not an option.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With