前面學習了JPQL語言和Query接口。這裡學習一下如果通過JPQL和Query接口進行資料的查詢、更新和删除。
【1】普通查詢
首先說明一下FROM子句和Select-FROM。
- from 子句是查詢語句的必選子句。
- Select 用來指定查詢傳回的結果實體或實體的某些屬性。
- From 子句聲明查詢源實體類,并指定辨別符變量(相當于SQL表的别名)。
如果不希望傳回重複實體,可使用關鍵字 distinct 修飾。select、from 都是 JPQL 的關鍵字,通常全大寫或全小寫,建議不要大小寫混用。
代碼執行個體如下:
@Test
public void testHelloJPQL(){
String jpql = "select c FROM Customer c WHERE c.age > ?";
Query query = (Query) entityManager.createQuery(jpql);
//占位符的索引是從 1 開始
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
System.out.println(customers.size());
}
控制台輸出如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5yMwczN3YmZjhDNkFGOhBTYyYzX5IDO0EjM4AzLcZDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
可以看到就是一條普通的查詢語句。
但是在寫JPQL時,需要注意,這一切面向對象。即對象+屬性相當于表+列,可以聯想一下Hibernate的HQL語言。
如果不寫 select 而是直接使用from子句,表明擷取對象的全部屬性。
String jpql = FROM Customer c WHERE c.age > ?;
查詢一個對象的所有屬性時,并不能像MySQL那樣使用 * 号标志。
下面語句是錯誤的,不符合JPQL的規範。
select * FROM Customer c WHERE c.age > ?
當然,你可以查詢部分屬性
預設情況下, 若隻查詢部分屬性, 則将傳回 Object[] 類型的結果. 或者 Object[] 類型的 List。
.
也可以在實體類中建立對應的構造器, 然後再 JPQL 語句中利用對應的構造器傳回實體類的對象.
代碼執行個體如下:
@Test
public void testPartlyProperties(){
String jpql = "SELECT c.lastName, c.age FROM Customer c WHERE c.id > ?";
List result = entityManager.createQuery(jpql).setParameter(1, 1).getResultList();
System.out.println(result);
Object[] objects = (Object[]) result.get(0);
System.out.println(objects[0]);
}
控制台輸出如下:
可以看到,擷取到的list是一個
list<object[]>
,解析起來是比較麻煩的。
故而,我們更希望使用如下方式:
@Test
public void testPartlyProperties(){
String jpql = "SELECT new Customer(c.lastName, c.age) FROM Customer c WHERE c.id > ?";
List result = entityManager.createQuery(jpql).setParameter(1, 1).getResultList();
System.out.println(result);
}
控制台輸出如下:
這樣擷取到的是
list<Customer>
,這樣解析起來就很友好了。
【2】createNamedQuery和createNativeQuery
① createNamedQuery 适用于在實體類前使用 @NamedQuery 标記的查詢語句。
代碼示例如下:
@NamedQuery(name="testNamedQuery", query="FROM Customer c WHERE c.id = ?")
@Cacheable(true)
@Table(name="jpa_cutomers")
@Entity
public class Customer {
private Integer id;
private String lastName;
private String email;
private int age;
private Date createdTime;
private Date birth;
public Customer() {
}
//...
}
測試代碼執行個體如下:
@Test
public void testNamedQuery(){
Query query = entityManager.createNamedQuery("testNamedQuery").setParameter(1, 3);
Customer customer = (Customer) query.getSingleResult();
System.out.println(customer);
}
控制台輸出如下:
② createNativeQuery 适用于本地 SQL
即,你可以像MySQL那樣寫sql語句進行查詢。
代碼執行個體如下:
@Test
public void testNativeQuery(){
String sql = "SELECT age FROM jpa_cutomers WHERE id = ?";
Query query = entityManager.createNativeQuery(sql).setParameter(1, 3);
Object result = query.getSingleResult();
System.out.println(result);
}
控制台輸出如下:
【3】(Hibernate)查詢緩存
什麼是查詢緩存?參考博文Hibernate查詢緩存應用執行個體
① 不使用查詢緩存
多次查詢同條語句,代碼執行個體如下:
@Test
public void testQueryCache(){
String jpql = "FROM Customer c WHERE c.age > ?";
Query query = entityManager.createQuery(jpql);
//占位符的索引是從 1 開始
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
System.out.println(customers.size());
query = entityManager.createQuery(jpql);
//占位符的索引是從 1 開始
query.setParameter(1, 1);
customers = query.getResultList();
System.out.println(customers.size());
}
控制台輸出如下:
② 使用查詢緩存
persistence.xml配置如下:
<!-- 二級緩存相關 -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<!-- 這裡表明使用查詢緩存 -->
<property name="hibernate.cache.use_query_cache" value="true"/>
代碼執行個體如下:
@Test
public void testQueryCache(){
String jpql = "FROM Customer c WHERE c.age > ?";
// QueryHints.HINT_CACHEABLE設定為true
Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
//占位符的索引是從 1 開始
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
System.out.println(customers.size());
query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
//占位符的索引是從 1 開始
query.setParameter(1, 1);
customers = query.getResultList();
System.out.println(customers.size());
}
控制台輸出入下:
【4】更新和删除
JPQL的更新和删除主要 用的是
query.executeUpdate()方法;
代碼執行個體如下:
@Test
public void testExecuteUpdate(){
String jpql = "UPDATE Customer c SET c.lastName = ? WHERE c.id = ?";
Query query = entityManager.createQuery(jpql).setParameter(1, "YYY").setParameter(2, 12);
query.executeUpdate();
}
控制台輸出如下:
删除同上。
【5】order by,group by 和having子句
JPQL是面向對象的,和hibernate一緻。此外,像排序、分組等和普通MySQL并無差異。
group by 子句用于對查詢結果分組統計,通常需要使用聚合函數。
常用的聚合函數主要有 AVG、SUM、COUNT、MAX、MIN 等,它們的含義與SQL相同。
例如:
select max(o.id) from Orders o
沒有 group by 子句的查詢是基于整個實體類的,使用聚合函數将傳回單個結果值,可以使用Query.getSingleResult()得到查詢結果。
例如:
Query query = entityManager.createQuery(
"select max(o.id) from Orders o");
Object result = query.getSingleResult();
Long max = (Long)result;
group by執行個體:
//查詢 order 數量大于 2 的那些 Customer
@Test
public void testGroupBy(){
String jpql = "SELECT o.customer FROM Order o "
+ "GROUP BY o.customer "
+ "HAVING count(o.id) >= 2";
List<Customer> customers = entityManager.createQuery(jpql).getResultList();
System.out.println(customers);
}
Having 子句用于對 group by 分組設定限制條件,用法與where 子句基本相同。
不同是 where 子句作用于基表或視圖,以便從中選擇滿足條件的記錄;having 子句則作用于分組,用于選擇滿足條件的組,其條件表達式中通常會使用聚合函數。
order by 執行個體:
@Test
public void testOrderBy(){
String jpql = "FROM Customer c WHERE c.age > ? ORDER BY c.age DESC";
Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
//占位符的索引是從 1 開始
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
System.out.println(customers.size());
}
order by子句用于對查詢結果集進行排序。和SQL的用法類似,可以用 “asc“ 和 "desc“ 指定升降序。
如果不顯式注明,預設為升序。
【6】關聯查詢與Fetch
在JPQL中,很多時候都是通過在實體類中配置實體關聯的類屬性來實作隐含的關聯(join)查詢。
例如:
select o from Orders o where o.address.streetNumber=2000
上述JPQL語句編譯成以下SQL時就會自動包含關聯,預設為左關聯。
在某些情況下可能仍然需要對關聯做精确的控制。為此,JPQL 也支援和 SQL 中類似的關聯文法。
如:
left out join / left join
inner join
left join / inner join fetch
其中,left join和left out join等義,都是允許符合條件的右邊表達式中的實體為空。
左外連接配接執行個體如下:
@Test
public void testLeftOuterJoinFetch(){
String jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.id = ?";
Customer customer =
(Customer) entityManager.createQuery(jpql).setParameter(1, 7).getSingleResult();
System.out.println(customer.getLastName());
System.out.println(customer.getOrders().size());
}
控制台輸出如下:
需要注意的是,這裡JPQL用到了FETCH。
如果不加FETCH呢?
代碼執行個體如下:
@Test
public void testLeftOuterJoinFetch(){
String jpql = "FROM Customer c LEFT OUTER JOIN c.orders WHERE c.id = ?";
List<Object[]> result = entityManager.createQuery(jpql).setParameter(1, 7).getResultList();
System.out.println(result);
}
控制台輸出如下:
“fetch”
連接配接允許僅僅使用一個選擇語句就将相關聯的對象或一組值的集合随着他們的父對象的初始化而被初始化。
在預設的查詢中,Entity中的集合屬性預設不會被關聯,集合屬性預設是延遲加載( lazy-load )。那麼,
left fetch/left out fetch/inner join fetch
提供了一種靈活的查詢加載方式來提高查詢的性能。
綜上,在使用JPQL語言時 ,需要時刻記得和hibernate一緻–面向對象。如果你使用的Query為createNativeQuery,才可以像使用普通MySQL一樣進行資料操作。
【7】子查詢
JPQL也支援子查詢,在 where 或 having 子句中可以包含另一個查詢。
當子查詢傳回多于 1 個結果集時,它常出現在 any、all、exist s表達式中用于集合比對查詢。
它們的用法與SQL語句基本相同。
子查詢執行個體如下:
@Test
public void testSubQuery(){
//查詢所有 Customer 的 lastName 為 YY 的 Order
String jpql = "SELECT o FROM Order o "
+ "WHERE o.customer = (SELECT c FROM Customer c WHERE c.lastName = ?)";
Query query = entityManager.createQuery(jpql).setParameter(1, "YY");
List<Order> orders = query.getResultList();
System.out.println(orders.size());
}