プロジェクションの前後に異なるモデルでフィルタリングを使用するJava


8

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?
}

「VOモデルへの投影」を「where式」に適用したものから分離したいのですが、投影された列(fullNameなど)を使用している場合は、間接的に適用します。

これはJavaで可能ですか?何を使うの?基準?Querydsl?ストリーム?(必ずしもJavaサンプルに固執しないでください)


1
使い方Streamのあなたのような何かを行っている可能性- personList.stream().filter(p -> p.active).map(p -> new PersonV0(p.id, p.firstName + " " + p.lastName)).filter(pv -> pv.fullName.equals(fullNameRequestParameter)).collect(Collectors.toList());どこPredicateで使用filterした後mapのpingは基づいているPersonV0
ナマン

しかし、ストリームの場合、すべての「クエリ」はSQLを生成するデータベース(Hibernateを使用)で解決されますか、それともメモリ内オブジェクトでのみ機能しますか?
jvitor83

上記はメモリオブジェクトでのみ機能します。あなたは絵で休止状態でそれを実装することを選択するべきかJavaでコードを扱うことができず、どのようにそのちょうどヒント(なぜコメントしていない答えだ)。
ナマン

1
とった!コメント@Namanをありがとう!このORM speedment.com/streamを使用stream()して、データベースのクエリに使用できることがわかります。これは私の質問に部分的に答えることができると思います。しかし、誰かが具体的な例で答えることができるかどうか(できればormとしてhibernateを使用するかどうか)を確認するために、開いたままにします。
jvitor83

Entity FrameworkがSQLを介して(メモリ内ではなく)FullNameでフィルターを実行しますか?
Olivier

回答:


5

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 Criteria API

Hibernate Criteria API(JPA Criteria APIに代わってHibernate 5.2以降非推奨)では、エイリアスを使用できます。ただし、すべてのデータベース(full_name || ' ' || last_name) as full_nameで、where句でエイリアス(など)を使用できるわけではありません。

PostgreSQLのドキュメントによると:

出力列の名前を使用して、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句でエイリアスを使用することはできません。


0
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
                     );

}

インターフェースで「ゲッター」に等しい列名を呼び出す必要があることに注意してください(getFirstName = firstName)

インターフェースベースのプロジェクションを呼び出します。次に、次のインスタンスを作成できますPersonVO

PersonFullNameView pfnv = repository.methodName(args...);
PersonVo personVO = pfnv.getFullName();

それが必要ですか?


完全にではありません。いくつかの「モデルベースAPI」でロジックを適用したい。しかし、答えてくれてありがとう。
jvitor83

0

この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();

助けてくれてありがとう!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.