红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

JPA 学习笔记 4:JPQL

2025年10月7日 74点热度 0人点赞 0条评论

二级缓存

示例:

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开发教程全套完整版

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: jpa jpql
最后更新:2025年10月21日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号