JPA 学习笔记 4:JPQL
JPA 学习笔记 4:JPQL
二级缓存
示例:
Order order1 = entityManager.find(Order.class, 1L);
Order order2 = entityManager.find(Order.class, 1L);
System.out.println(order1.getOrderName());
实际执行时 Hibernate 只会执行一次 SELECT 语句进行查询,因为这两次查询是在一个 Session 内,Hibernate 默认启用一级缓存,所以只会有一次查询。
这里人为将两次查询放在两个 Session 内:
Order order1 = entityManager.find(Order.class, 1L);
// 提交事务,重启一个事务
entityTransaction.commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
Order order2 = entityManager.find(Order.class, 1L);
System.out.println(order1.getOrderName());
此时会有两条 SELECT 语句被执行。可以使用 Hibernate 的二级缓存来避免重复查询,因为二级缓存是跨事务的,在一个 EntityManagerFactory
内都有效。
要开启 Hibernate 的二级缓存需要添加以下依赖:
<!-- hibernate jcache -->
<dependency><groupId>org.hibernate.orm</groupId><artifactId>hibernate-jcache</artifactId><version>${hibernate.version}</version>
</dependency>
<!-- jcache 实现,这里使用 ehcache -->
<dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.11.1</version><exclusions><exclusion><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId></exclusion><exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions>
</dependency>
<!-- JCache API -->
<dependency><groupId>javax.cache</groupId><artifactId>cache-api</artifactId><version>1.1.1</version>
</dependency>
<!-- JAXB运行时 -->
<dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>4.0.5</version>
</dependency>
简单解释一下依赖:
- jcache,一种 sun 制订的缓存标准,这里的 cache-api 是具体的 API
- hibernate-jcache,Hibernate 使用 jcache 连接缓存的 jar 包
- ehcache,一种实现了 jcache 接口的缓存,还有其他实现,具体可以看这里
需要说明的是,这里的依赖适用于 JDK21 + Hibernate7 + Ehcache3 的组合,如果是其他版本,可能引入的 jar 包略有不同,这里牵扯高版本的 JDK 剥离某些 jar 包的问题,具体问题具体分析即可。
修改 JPA 配置文件,开启二级缓存:
<!-- 二级缓存 -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.jcache.internal.JCacheRegionFactory"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
<property name="hibernate.javax.cache.provider" value="org.ehcache.jsr107.EhcacheCachingProvider"/>
设置二级缓存策略:
<!-- 二级缓存策略 -->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
可以选择的策略有:
- ALL:所有实体类都被缓存
- NONE:所有实体类都不被缓存
- ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
- DISABLE_SELECTIVE:除标识 @Cacheable(false) 以外的所有实体将被缓存
- UNSPECIFIED:默认值,JPA 产品默认值将被使用
添加 Ehcache 的配置文件src/main/resources/ehcache.xml
:
<config xmlns="http://www.ehcache.org/v3"xmlns:jsr107="http://www.ehcache.org/v3/jsr107"><service><jsr107:defaults enable-management="true" enable-statistics="true"/></service><cache alias="entityCache"><key-type>java.lang.Object</key-type><value-type>java.lang.Object</value-type><expiry><ttl unit="minutes">10</ttl></expiry><resources><heap unit="entries">1000</heap><offheap unit="MB">10</offheap></resources></cache><cache alias="queryCache"><key-type>java.lang.Object</key-type><value-type>java.lang.Object</value-type><expiry><ttl unit="minutes">5</ttl></expiry><resources><heap unit="entries">1000</heap></resources></cache>
</config>
为需要缓存的实体类(这里是Order
)添加注解:
@Cacheable
@Entity
@Table(name = "tb_order")
@Data
@NoArgsConstructor
public class Order {// ...
}
再次执行测试用例就能看到二级缓存生效,SELECT 语句不会重复执行。
JPQL
JPQL(Java Persistence Query Language)是一种和 SQL 类似的中间性对象化查询语言,和 SQL 不同的是它其中使用的是实体类和属性,而不是表和字段。
快速开始
使用 JPQL 语句进行查询:
String jpql = "SELECT c FROM Customer c WHERE c.age>?1";
Query query = entityManager.createQuery(jpql);
query.setParameter(1, 10);
List<?> customers = query.getResultList();
System.out.println(customers);
JPQL 中的?1
是位置参数,使用query.setParameter
可以设置对应位置的参数值。
更常见的方式是使用命名参数:
String jpql = "SELECT c FROM Customer c WHERE c.age>:age";
Query query = entityManager.createQuery(jpql);
query.setParameter("age", 10);
如果查询结果包含全部的实体属性,JPQL 中的 SELECT 部分也可以省略:
String jpql = "FROM Customer c WHERE c.age>:age";
当然也可以让查询结果只返回部分属性:
String jpql = "SELECT c.age,c.lastName FROM Customer c WHERE c.age>:age";
Query query = entityManager.createQuery(jpql);
query.setParameter("age", 10);
List<?> customers = query.getResultList();
System.out.println(customers);
需要注意的是,此时customers
中的每个元素是 Object[]
类型,这样可能对后续数据的处理和展示带来不便,因此可以这样:
String jpql = "SELECT new Customer(c.lastName,c.age) FROM Customer c WHERE c.age>:age";
在 JPQL 中显式地使用实体类的构造器封装了返回的结果,此时返回的结果都会是Customer
对象。当然,这需要为实体类添加对应的构造器:
public class Customer {// ...public Customer(String lastName, Integer age) {this.lastName = lastName;this.age = age;}
}
可以在实体类上使用@NamedQuery
定义具名 Query:
@Entity
@Table(name = "tb_customer")
@Data
@ToString
@NamedQuery(name = "Customer.findByLastName", query = "select c from Customer c where c.lastName = :lastName")
public class Customer {// ...
}
执行具名 Query:
Query query = entityManager.createNamedQuery("Customer.findByLastName");
query.setParameter("lastName", "张三");
List<?> customers = query.getResultList();
System.out.println(customers);
也可以通过 JPA 执行原生 SQL:
Query query = entityManager.createNativeQuery("SELECT * FROM tb_customer");
List<?> customers = query.getResultList();
System.out.println(customers);
查询缓存
示例:
Query query = entityManager.createNativeQuery("SELECT * FROM tb_customer");
List<?> customers = query.getResultList();
System.out.println(customers);
Query query2 = entityManager.createNativeQuery("SELECT * FROM tb_customer");
List<?> customers2 = query2.getResultList();
System.out.println(customers2);
会执行两条 SELECT 语句。
使用查询缓存:
Query query = entityManager.createNativeQuery("SELECT * FROM tb_customer").setHint(AvailableHints.HINT_CACHEABLE, true);
List<?> customers = query.getResultList();
System.out.println(customers);
Query query2 = entityManager.createNativeQuery("SELECT * FROM tb_customer").setHint(AvailableHints.HINT_CACHEABLE, true);;
List<?> customers2 = query2.getResultList();
System.out.println(customers2);
只会执行一次 SELECT 语句。
当然,前提是已经在 JPA 配置中开启了 Hibernate 的查询缓存:
<property name="hibernate.cache.use_query_cache" value="true"/>
Group By
在 JPQL 中同样可以使用 Group By:
TypedQuery<Customer> query = entityManager.createQuery("SELECT o.customer FROM Order o " +"GROUP BY o.customer HAVING count(o.customer)>=3", Customer.class);
List<Customer> customers = query.getResultList();
System.out.println(customers);
关联查询
示例:
TypedQuery<Person> query = entityManager.createQuery("select p from Person p where p.id=:id", Person.class);
query.setParameter("id", 1L);
List<Person> persons = query.getResultList();
System.out.println(persons);
实体Person
和Car
是一对多的双向关联关系,这里查询会使用两条 SELECT 获取结果。
可以在 JPQL 中使用外连接,这样使用一条 SQL 就能查询出结果:
TypedQuery<Person> query = entityManager.createQuery("select p from Person p left outer join fetch p.cars where p.id=:id", Person.class);
query.setParameter("id", 1L);
List<Person> persons = query.getResultList();
System.out.println(persons);
子查询
JPQL 也可以使用子查询:
TypedQuery<Order> query = entityManager.createQuery("select o from Order o where o.customer in ((select c from Customer c where c.age>:age))", Order.class);
query.setParameter("age", 20);
List<Order> orders = query.getResultList();
System.out.println(orders);
函数
JPQL 也可以使用预定义函数,这些函数的命名和用途与 SQL 中的函数类似:
TypedQuery<String> query = entityManager.createQuery("select concat(c.lastName,'(',c.birth,')') from Customer c", String.class);
List<String> names = query.getResultList();
System.out.println(names);
UPDATE
JPQL 也可以完成更新操作:
entityManager.createQuery("update Customer c set c.lastName=:lastName where c.id=:id").setParameter("id", 1L).setParameter("lastName", "张三丰").executeUpdate();
本文的所有示例代码可以从这里获取。
参考资料
- 尚硅谷jpa开发教程全套完整版