jvi*_*r83 9 java hibernate projection querydsl java-stream
考虑以下用于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;
}
Run Code Online (Sandbox Code Playgroud)
以及以下 API 序列化模型(使用spring boot rest 控制器):
public class PersonVO {
public Long id;
public String fullName;
}
Run Code Online (Sandbox Code Playgroud)
我想要的是:
在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();
Run Code Online (Sandbox Code Playgroud)
我在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?
}
Run Code Online (Sandbox Code Playgroud)
我想将“投影到 VO 模型”与应用于它的“where 表达式”分开,但如果使用投影列(如 fullName),则间接应用它。
这在Java中可能吗?用什么?标准?查询dsl?溪流?(不一定要坚持Java示例)
JPA Criteria 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();
}
Run Code Online (Sandbox Code Playgroud)
生成的 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
)=?
Run Code Online (Sandbox Code Playgroud)
@org.hibernate.annotations.FormulaHibernate 有一个注解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
}
Run Code Online (Sandbox Code Playgroud)
在 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();
}
Run Code Online (Sandbox Code Playgroud)
以及生成的 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=?
Run Code Online (Sandbox Code Playgroud)
Hibernate Criteria API(自 Hibernate 5.2 起弃用,支持 JPA Criteria API)允许使用别名。但并非所有数据库都允许(full_name || ' ' || last_name) as full_name在where子句中使用别名(例如)。
输出列的名称可用于在 ORDER BY 和 GROUP BY 子句中引用列的值,但不能在 WHERE 或 HAVING 子句中引用;在那里你必须写出表达式。
这意味着 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'
Run Code Online (Sandbox Code Playgroud)
在 PostgreSQL 中不起作用。
因此,在where子句中使用别名不是一种选择。
使用这个http://www.jinq.org/库,我可以做到这一点并应用于休眠(以及数据库)。
JinqJPAStreamProvider jinqJPAStreamProvider = new JinqJPAStreamProvider(this.entityManager.getMetamodel());
JPAJinqStream<Person> personStream = jinqJPAStreamProvider.streamAll(this.entityManager, Person.class);
personStream = personStream.where(person -> person.getFirstName().equals("Joao"));
// The only trouble is that we have to register the Model we want to project to (i believe it could be solved with reflection)
jinqJPAStreamProvider.registerCustomTupleConstructor(PersonVO.class.getConstructor(Long.class, String.class), PersonVO.class.getMethod("getId"), PersonVO.class.getMethod("getFullName"));
JPAJinqStream<PersonVO> personVOStream = personStream.select(person -> new PersonVO(person.getId(), person.getFirstName() + person.getLastName()));
personVOStream = personVOStream.where(person -> person.getFullName().equals("JoaoCarmo"));
List<PersonVO> resultList = personVOStream.toList();
Run Code Online (Sandbox Code Playgroud)
感谢大家的帮助!
| 归档时间: |
|
| 查看次数: |
369 次 |
| 最近记录: |