2020年5月23日 星期六

Map Join


本文為【Spring Boot情境式網站開發指南:使用Spring Data JPA、Spring Security、Spring Web Flow】一書的【 第4章 Criteria API入門】延續,完整範例程式碼可至出版社下載

Entity類別以可以關連到Map型態的欄位,如同List或Set,也可以使用@ElementCollection、@ManyToMany、@OneToMany等標註。在JPQL中提供KEY、 VALUE、ENTRY等關鍵字存取Map物件,Criteria API中則為:
  1. Root<Entity>使用join()方法和Map<Key, Value>屬性欄位聯結後,可以取得MapJoin<Entity, Key, Value>物件。
  2. MapJoin<Entity, Key, Value>具備以下方法存取關連到Map型態的欄位:
  • key():取得Map物件的key值,等同JPQL的KEY敘述。
  • value():取得Map物件的value值,等同JPQL的VALUE敘述。
  • entry():同時取得Map物件的key與value值,等同JPQL的ENTRY敘述。
 當JPQL使用以下查詢時:

TypedQuery typedQuery  = em.createQuery(
  "SELECT c.name, KEY(map), VALUE(map) FROM Customer c JOIN c.itemQtyMap map", 
  Tuple.class);
對應的Criteria API用法如下:
@Test
public void Criteria_GetMapKeyValueByTuple() {
    EntityManager em = emf.createEntityManager();
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery sql = cb.createTupleQuery();
    Root cust = sql.from(Customer.class);
    MapJoin itemQtyMap = 
                          cust.join(Customer_.itemQtyMap);
    sql.multiselect(cust.get(Customer_.name), 
                    itemQtyMap.key(), 
                    itemQtyMap.value() );
    TypedQuery typedQuery = em.createQuery(sql);
    List resultList = typedQuery.getResultList();
    List stringList = resultList.stream()
            .map(t -> t.get(0) + ", " + t.get(1) + ", " + t.get(2))
            .collect(Collectors.toList());
    stringList.forEach(System.out::println);
    assertThat(stringList, containsInAnyOrder("jim, computer, 1", 
                                                "jim, mouse, 4", 
                                                "colin, notebook, 3"));
}
  • 行5 
使用Tuple封裝查詢結果的複數欄位。

  • 行7-8

使用Root<Customer> cust的join()方法聯結Map型態的欄位,並回傳MapJoin<Customer, String, Integer>物件。介面MapJoin<1, 2, 3>的泛型指定3種型態:
1. Entity型態,本例為Customer。
2. Map欄位的key型態,本例為String。
3. Map欄位的value型態,本例為Integer。

  • 行9-11

使用CriteriaBuilder物件的multiselect()方法查詢name欄位, Map欄位的key欄位由itemQtyMap.key()表示,Map欄位的value欄位由itemQtyMap.value()表示。
當JPQL使用以下查詢時:
TypedQuery typedQuery = em.createQuery(
    "SELECT ENTRY(map) FROM Customer c JOIN c.itemQtyMap map", 
    Map.Entry.class);  

對應的Criteria API用法如下。本例只查詢Map的key與value等2個欄位,使用Map.Entry型態封裝查詢後的結果:
@Test
public void Criteria_GetEntryByMapEntry() {
    EntityManager em = emf.createEntityManager();
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery sql = cb.createQuery(Map.Entry.class);
    Root cust = sql.from(Customer.class);
    MapJoin orderMap = 
                                  cust.join(Customer_.itemQtyMap);
    sql.select(orderMap.entry());
    TypedQuery typedQuery = em.createQuery(sql);
    // hibernate.version=5.2.10.Final will throw ClassCastException
    List resultList = typedQuery.getResultList();
    List stringList = resultList.stream()
            .map(entry -> entry.getKey()+ ", " + entry.getValue())
            .collect(Collectors.toList());
    stringList.forEach(System.out::println);
    assertThat(stringList, containsInAnyOrder("computer, 1", 
                                                "mouse, 4", 
                                                "notebook, 3"));
}       
  • 行5
使用Map.Entry封裝查詢後的Map的key與value欄位。
  • 行9-11
使用CriteriaBuilder物件的select()方法查詢itemQtyMap.entry(),將同時取得key與value欄位。
值得注意的是,本例在5.2.10.Final的hibernate版本下將拋出ClassCastException的例外,使用較新版本則無此問題。

沒有留言:

張貼留言