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

标签 java hibernate java-stream projection querydsl

考虑针对和 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;
}

我想要的是:
  • 在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示例)

    最佳答案

    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/

    相关文章:

    java - Hibernate:无法加载在 Hibernate 配置 <mapping/> 条目中声明的类

    java - 在Android中使用setVisibility?

    带有 EmbeddedId 的 hibernate 条件

    java - 如何使用第一维作为流的键将二维数组映射到 HashMap

    Java 对 anyMatch 使用多个谓词

    java - 在 hql 查询中使用 % 时出现问题

    java - java中的数字模式程序

    java - 了解为 Join 表上的一对多和多对一生成的查询

    java - Hibernate 通过 Spring 注入(inject)使用查询

    java - 为什么Collectors类中toMap方法的Java mapFactory参数需要指定类型?