考虑针对和 hibernate 的以下JAVA模型:
@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;
}
和以下用于API序列化的模型(使用 spring boot rest Controller ):
public class PersonVO {
public Long id;
public String fullName;
}
我想要的是:
在 C#.NET 中,我可以这样:
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();
我在中得到的Java 是:
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?
}
我想将“到VO模型的投影”与应用于它的“where表达式”分开,但是如果使用投影列(例如fullName),则可以间接应用它。
这在Java中可能吗?使用什么?标准? Querydsl?溪流? (不一定要坚持使用Java示例)
最佳答案
JPA Criteria API没有此类功能。另外,不容易阅读😊
JPA标准API
在Criteria API中,您需要重用Expression
。
工作代码如下所示:
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();
}
生成的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
)=?
JPA Criteria API和
@org.hibernate.annotations.Formula
Hibernate具有注释
org.hibernate.annotations.Formula
,可以稍微简化代码。将带有
@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
}
在JPA Criteria API查询中,请引用
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();
}
并生成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 标准API
Hibernate Criteria API(自Hibernate 5.2开始不赞成使用JPA Criteria API)允许使用别名。但并非所有数据库都允许在
(full_name || ' ' || last_name) as full_name
子句中使用别名(例如where
)。根据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.
这意味着SQL查询
select p.id,
(p.first_name || ' ' || p.last_name) as full_name
from person p
where p.active = true
and full_name = 'John Doe'
在PostgreSQL中不起作用。
因此,不能在
where
子句中使用别名。
关于Java在投影前后在不同模型上使用过滤,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60646912/