前面学习了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());
}

控制台输出如下:

JPQL-Query查询实例详解_hibernate


可以看到就是一条普通的查询语句。

但是在写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]);
}

控制台输出如下:

JPQL-Query查询实例详解_实例_02


可以看到,获取到的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);

}

控制台输出如下:

JPQL-Query查询实例详解_代码实例_03

这样获取到的是​​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);
}

控制台输出如下:

JPQL-Query查询实例详解_查询_04


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

控制台输出如下:

JPQL-Query查询实例详解_查询_05


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

控制台输出如下:

JPQL-Query查询实例详解_代码实例_06


② 使用查询缓存

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

控制台输出入下:

JPQL-Query查询实例详解_hibernate_07


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

控制台输出如下:

JPQL-Query查询实例详解_hibernate_08

删除同上。


【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-Query查询实例详解_查询_09


需要注意的是,这里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);
}

控制台输出如下:

JPQL-Query查询实例详解_jpql_10

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

控制台输出如下:

JPQL-Query查询实例详解_实例_11