Java 在投影前后对不同模型使用过滤
Java using filtering at different models before and after the projection
考虑 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;
}
我想要的是:
- 对人物应用一些过滤(静态定义)
- 在 PersonVO 应用一些过滤(从@RequestParam 获取)
在 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?
}
我想将 "projection to the VO model" 与应用于它的 "where expression" 分开,但如果使用投影列(如 fullName),则间接应用它。
这在 Java 中可行吗?用什么?标准?查询dsl?溪流? (不一定要坚持 java 样本)
JPA Criteria API 没有这样的功能。还有,读起来不容易
JPA 标准API
在标准 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 标准 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)允许使用别名。但并非所有数据库都允许在 where
子句中使用别名(例如 (full_name || ' ' || last_name) as full_name
)。
根据 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'
在 Postgre 中不起作用SQL。
因此,在 where
子句中使用别名不是一种选择。
public interface PersonVO{
String getFirstName();
String getLastName();
}
public interface PersonFullNameView{
PersonVO getFullName();
}
public interface PersonRepository<Person, Long>{
@Query("SELECT first_name lastName || ' ' || last_name lastName as fullName" +
"FROM Person p" +
"WHERE p.active = :active AND p.first_name=:firstName AND" +
"p.last_name=:lastname"), nativeQuery = true)
PersonFullNameView methodName(
@Param("active" boolean active,
@Param("firstName") String firstName,
@Param("lastName") String lastNam
);
}
请注意,您的列名必须等于 "getters"
在接口中(getFirstName = firstName)
它调用基于接口的投影。然后你可以创建 PersonVO
:
的实例
PersonFullNameView pfnv = repository.methodName(args...);
PersonVo personVO = pfnv.getFullName();
这是你需要的吗?
使用这个 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();
感谢大家的帮助!
考虑 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;
}
我想要的是:
- 对人物应用一些过滤(静态定义)
- 在 PersonVO 应用一些过滤(从@RequestParam 获取)
在 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?
}
我想将 "projection to the VO model" 与应用于它的 "where expression" 分开,但如果使用投影列(如 fullName),则间接应用它。
这在 Java 中可行吗?用什么?标准?查询dsl?溪流? (不一定要坚持 java 样本)
JPA Criteria API 没有这样的功能。还有,读起来不容易
JPA 标准API
在标准 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 标准 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)允许使用别名。但并非所有数据库都允许在 where
子句中使用别名(例如 (full_name || ' ' || last_name) as full_name
)。
根据 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'
在 Postgre 中不起作用SQL。
因此,在 where
子句中使用别名不是一种选择。
public interface PersonVO{
String getFirstName();
String getLastName();
}
public interface PersonFullNameView{
PersonVO getFullName();
}
public interface PersonRepository<Person, Long>{
@Query("SELECT first_name lastName || ' ' || last_name lastName as fullName" +
"FROM Person p" +
"WHERE p.active = :active AND p.first_name=:firstName AND" +
"p.last_name=:lastname"), nativeQuery = true)
PersonFullNameView methodName(
@Param("active" boolean active,
@Param("firstName") String firstName,
@Param("lastName") String lastNam
);
}
请注意,您的列名必须等于 "getters" 在接口中(getFirstName = firstName)
它调用基于接口的投影。然后你可以创建 PersonVO
:
PersonFullNameView pfnv = repository.methodName(args...);
PersonVo personVO = pfnv.getFullName();
这是你需要的吗?
使用这个 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();
感谢大家的帮助!