红茶的个人站点

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

JPA 学习笔记 4:JPQL

2025年10月7日 22点热度 0人点赞 0条评论
  • 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开发教程全套完整版

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

魔芋红茶

加一点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号