Java sử dụng lọc ở các mô hình khác nhau trước và sau khi chiếu


8

Hãy xem xét mô hình JAVA sau đây để ngủ đông :

@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;
}

và mô hình sau đây cho tuần tự hóa API (sử dụng bộ điều khiển nghỉ khởi động mùa xuân ):

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

Điều tôi muốn là:

  • Có một số bộ lọc được áp dụng tại Người (được xác định tĩnh)
  • Có một số bộ lọc được áp dụng tại PersonVO (lấy từ @RequestParam)

Trong C # .NET tôi có thể thực hiện như sau:

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

Những gì tôi nhận được trong Java là:

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

Tôi muốn tách "phép chiếu sang mô hình VO" khỏi "biểu thức nơi" được áp dụng cho nó, nhưng nó được áp dụng gián tiếp nếu sử dụng một cột được chiếu (như fullName).

Điều này có thể có trong Java không? Dùng cái gì? Tiêu chí? Truy vấn? Suối? (không nhất thiết phải dính vào mẫu java)


1
Sử dụng Streambạn có thể đã làm một cái gì đó như - 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());nơi mà việc Predicatesử dụng filtersau mapping được dựa trênPersonV0
Naman

Nhưng đối với các luồng, tất cả "truy vấn" sẽ được giải quyết tại cơ sở dữ liệu tạo sql (sử dụng hibernate) hoặc nó chỉ hoạt động với các đối tượng trong bộ nhớ?
jvitor83

Ở trên sẽ chỉ làm việc với các đối tượng bộ nhớ. Nó chỉ là một gợi ý về cách bạn có thể đối phó với các mã trong Java và không phải là cách bạn nên chọn để thực hiện nó với chế độ ngủ đông trong hình (đó là lý do một bình luận và không phải là một câu trả lời).
Naman

1
Hiểu rồi! Cảm ơn vì nhận xét @Naman! Tôi thấy rằng ORM speedment.com/stream này có thể cho phép sử dụng stream()để truy vấn cơ sở dữ liệu. Tôi nghĩ rằng điều này có thể trả lời một phần câu hỏi của tôi. Nhưng tôi sẽ giữ cho nó mở để xem ai đó có thể trả lời điều đó với một ví dụ cụ thể (tốt nhất là sử dụng ngủ đông như orm).
jvitor83

Bạn có chắc chắn Entity Framework thực hiện bộ lọc trên FullName thông qua SQL (và không có trong bộ nhớ) không?
Olivier

Câu trả lời:


5

API tiêu chí JPA không có chức năng như vậy. Ngoài ra, không dễ để đọc

API tiêu chí JPA

Trong API Tiêu chí, bạn cần sử dụng lại Expression.

Mã làm việc trông như thế này:

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

Mã SQL được tạo:

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 tiêu chí JPA và @org.hibernate.annotations.Formula

Hibernate có một chú thích org.hibernate.annotations.Formulacó thể đơn giản hóa mã một chút.

Thêm vào thực thể một trường tính toán được chú thích bằng @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
}

Và trong tham chiếu API tiêu chí JPA tham chiếu trường 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();
}

Và SQL được tạo:

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 tiêu chí ngủ đông

API tiêu chí Hibernate (không dùng nữa từ Hibernate 5.2 có lợi cho API tiêu chí JPA) cho phép sử dụng bí danh. Nhưng không phải tất cả các cơ sở dữ liệu đều cho phép sử dụng các bí danh (ví dụ (full_name || ' ' || last_name) as full_name) trong một wheremệnh đề.

Theo các tài liệu PostgreSQL :

Tên của cột đầu ra có thể được sử dụng để chỉ giá trị của cột trong mệnh đề ORDER BY và GROUP BY, nhưng không phải trong các mệnh đề WHERE hoặc HAVING; ở đó bạn phải viết ra biểu thức thay thế.

Nó có nghĩa là truy vấn 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'

không hoạt động trong PostgreSQL.

Vì vậy, sử dụng bí danh trong wheremệnh đề không phải là một lựa chọn.


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

}

Lưu ý rằng bạn phải gọi tên cột của mình bằng "getters" trong giao diện (getFirstName = firstName)

Nó gọi phép chiếu dựa trên giao diện. Sau đó, bạn có thể tạo ví dụ về PersonVO:

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

Đó có phải là những gì bạn cần?


Không hoàn toàn. Tôi muốn áp dụng logic tại một số "API dựa trên mô hình". Nhưng cảm ơn vì câu trả lời.
jvitor83

0

Sử dụng thư viện http://www.jinq.org/ này tôi có thể làm điều đó và được áp dụng vào chế độ ngủ đông (và do đó là cơ sở dữ liệu).

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

Cảm ơn tất cả vì sự giúp đỡ!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.