Java在投影之前和之后在不同模型上使用过滤

问题描述 投票:4回答:1

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控制器):

public class PersonVO {
    public Long id;
    public String fullName;
}

我想要的是:

  • 在Person上应用了一些过滤(静态定义)
  • 在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?
}

我想将“到VO模型的投影”与对其应用的“ where表达式”分开,但是如果使用投影列(例如fullName),则将其间接应用。

在Java中可能吗?使用什么?条件? Querydsl?流? (不一定要坚持使用Java示例)

java hibernate java-stream projection querydsl
1个回答
2
投票

JPA Criteria API没有这种功能。另外,不容易阅读😊

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();
}

生成的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 Criteria API

Hibernate Criteria API(自Hibernate 5.2开始支持JPA Criteria API弃用)允许使用别名。但并非所有数据库都允许在(full_name || ' ' || last_name) as full_name子句中使用别名(例如where)。

根据PostgreSQL docs

输出列的名称可以用来引用列中的值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'

在PostgreSQL中不起作用。

因此,不能在where子句中使用别名。

© www.soinside.com 2019 - 2024. All rights reserved.