-
二级缓存
示例:
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
)添加注解:name = "tb_order") ( 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:name = "tb_customer") ( 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();
本文的所有示例代码可以从获取。
参考资料
-
文章评论