【问题标题】:Java using filtering at different models before and after the projectionJava 在投影前后使用不同模型的过滤
【发布时间】:2020-03-12 02:45:16
【问题描述】:

考虑 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 休息控制器):

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 中可能吗?用什么?标准?查询dsl?溪流? (不一定要坚持 java 示例)

【问题讨论】:

  • 使用Streams 你可以做类似的事情 - personList.stream().filter(p -&gt; p.active).map(p -&gt; new PersonV0(p.id, p.firstName + " " + p.lastName)).filter(pv -&gt; pv.fullName.equals(fullNameRequestParameter)).collect(Collectors.toList()); 其中Predicatefilter 之后使用mapping 是基于PersonV0
  • 但是对于流,所有的“查询”都将在数据库生成 sql(使用休眠)时解决,还是只适用于内存中的对象?
  • 以上内容仅适用于内存对象。它只是提示您如何处理 Java 中的代码,而不是您应该如何选择使用图片中的 hibernate 来实现它。(这就是为什么评论而不是答案)
  • 知道了!感谢@Naman 的评论!我看到这个 ORM speedment.com/stream 可以允许使用stream() 来查询数据库。我认为这可以部分回答我的问题。但我会保持开放,看看是否有人可以用一个具体的例子来回答这个问题(最好使用 hibernate 作为 orm)。
  • 您确定实体框架通过 SQL(而不是在内存中)对 FullName 执行过滤器吗?

标签: java hibernate java-stream projection querydsl


【解决方案1】:

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 标准 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=?

休眠标准 API

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

根据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 子句中使用别名。

【讨论】:

    【解决方案2】:
    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();
    

    这是你需要的吗?

    【讨论】:

    • 不完全。我想在一些“基于模型的 API”中应用逻辑。不过谢谢你的回答。
    【解决方案3】:

    使用这个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();
    

    感谢大家的帮助!

    【讨论】:

      猜你喜欢
      • 2019-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-19
      • 2022-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多