JPA 缓存

本章节将介绍 JPA 中缓存,合理使用缓存可以提高程序的性能。

在 JPA2.0 中,缓存分为一级缓存和二级缓存(JPA1.0 只支持一级缓存)。

一级缓存

在 JPA 中,持久化上下文(EntityManager)就是 JPA 的一级缓存。在该缓存区中,会将查询到的对象缓存到该区域中。

如果在同一个 EntityManager 中,查询相同 OID 的数据,那么只需要发送一条 sql。在事务提交/关闭 EntityManager 之后,一级缓存会清空。所以在不同的 EntityManager 中使用不同的一级缓存。

JPA 中的一级缓存也可以使用下面的方法手动清除缓存数据:

  • detach:清除一级缓存中指定的对象

  • clear:清除一级缓存中的所有的缓存数据

JPA 中的一级缓存的缓存能力是非常有限的,因为我们不会经常在一个 EntityManager 中查询相同的数据。

JPA 中的一级缓存属于请求范围级别的缓存,如下图:

上图中,在持久化上下文1和持久化上下文2中的User对象是不同的对象,它们之间不共享。

二级缓存

JPA 中的二级缓存通常是用来提高应用程序性能的,它可以避免访问已经从数据库加载的数据,提高访问未被修改数据对象的速度。

JPA 二级缓存是跨越持久化上下文的,是真正意义上的全局应用缓存,如下图:

如果二级缓存激活,JPA 会先从一级缓存中查找实体,未找到再从二级缓存中查找。当二级缓存有效时,就不能依靠事务来保护并发的数据,而是依靠锁策略,如在确认修改后,需要手工处理乐观锁失败等。

注意:JPA 二级缓存只能缓存通过 EntityManager 的 find 或 getReference 查询到的实体,以及通过实体的 getter 方法获取到的关联实体;而不能缓存通过 JPQL 查询获得的数据。

JPA 二级缓存通常用来提高性能,同时,使用二级缓存可能会导致提取到“陈旧”数据,也会出现并发写的问题。所以二级缓存最好是用在经常读取的数据,比较少更新数据的情况,而不应该对重要数据使用二级缓存。

对于不同的 JPA 实现产品,开启二级缓存的方式会有所不同,下面以 OpenJPA 为例,开启二级缓存,步骤如下:

(1)修改 persistence.xml 配置文件

<persistence-unit name="openJPA" transaction-type="RESOURCE_LOCAL">
    <!-- JPA提供者 -->
    <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
    <!-- 声明实体类 -->
    <class>com.hxstrive.openjpa.model.User</class>
    <!-- 配置JPA数据库属性 -->
    <properties>
        <property name="openjpa.ConnectionURL"
                  value="jdbc:mysql://localhost:3306/openjpa_learn?useSSL=false&amp;
                  serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver"/>
        <property name="openjpa.ConnectionUserName" value="root"/>
        <property name="openjpa.ConnectionPassword" value="aaaaaa"/>
        <property name="openjpa.Log" value="SQL=TRACE"/>
        <!-- 开启二级缓存 -->
        <property name="openjpa.DataCache" value="true(CacheSize=3, SoftReferenceSize=3)" />
        <property name="openjpa.RemoteCommitProvider" value="sjvm" />
        <!-- 自动生成表 -->
        <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/>
        <!-- 不使用加载时强化和编译时强化,使用运行时Unenhanced(不能发挥OpenJPA的最大效能,所以也不推荐) -->
        <property name="openjpa.ClassLoadEnhancement" value="false"/>
        <property name="openjpa.DynamicEnhancementAgent" value="false"/>
        <property name="openjpa.RuntimeUnenhancedClasses" value="supported"/>
    </properties>
</persistence-unit>

上面配置中,openjpa.DataCache 用来缓存 EntityManagerFactory 级别的持久化实体的。当启用 openjpa.DataCache 后,OpenJPA 会先检查 openjpa.DataCache,如果实体不存在再从存储介质中读取。

同其他缓存技术需要管理的属性相似,可能在缓存管理的算法上有所区别,OpenJPA 中属性管理如下:

  • CacheSize:指的是缓存实体的最大数目,默认 CacheSize 是 1000,如果缓存实体超出这一数值时,OpenJPA 会随机逐出一些实体,直至实体数目小于设定的最大数目。

  • SoftReferenceSize:指 OpenJPA 的缓存如果达到最大存储数目,或根据一定的算法需要从缓存中移出数据时,会把它们放到一个 softReferenceMap 中,softReferenceMap 中的数据也会删除只是这些数据比直接删除会待的时间久一些,以防止在缓存删除他们以后程序马上又需要访问他们。这样下次访问到这些移出的数据时就会到 softReferenceMap 中去找,这样比直接删除和重新访问数据库查找的效率好一点(这点跟其他的缓存实现有点不一样,其它的缓存是直接从缓存中删除这些数据)。如果你不想使用sofeReferenceMap,就把它的值设成 0。

(2)测试代码

下面代码将分别获取 ID 为 1 的 User 对象,通过输出的 SQL 语句观察是否使用缓存。代码如下:

import com.hxstrive.openjpa.model.User;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Demo {
    private static final String PERSISTENCE_NAME = "openJPA";

    public static void main(String[] args) {
        Demo demo = new Demo();
        EntityManagerFactory managerFactory = null;
        try {
            managerFactory = Persistence.createEntityManagerFactory(
                    PERSISTENCE_NAME, System.getProperties());
            System.out.println("第一次:");
            demo.queryUser(managerFactory, 1);

            System.out.println("\n第二次:");
            demo.queryUser(managerFactory, 1);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != managerFactory) {
                managerFactory.close();
            }
        }
    }

    private void queryUser(EntityManagerFactory managerFactory, int userId){
        EntityManager entityManager = null;
        try {
            entityManager = managerFactory.createEntityManager();
            User user = entityManager.find(User.class, userId);
            System.out.println(user);
        } finally {
            if (null != entityManager) {
                entityManager.close();
            }
        }
    }

}

如果开启 DataCache 缓存,执行上面 Java 代码,输出结果如下:

第一次:
141  openJPA  INFO   [main] openjpa.Runtime - Starting OpenJPA 2.4.2
344  openJPA  INFO   [main] openjpa.jdbc.JDBC - Using dictionary class "org.apache.openjpa.jdbc.sql.MySQLDictionary".
1845  openJPA  INFO   [main] openjpa.jdbc.JDBC - Connected to MySQL version 5.5 using JDBC driver MySQL Connector Java version mysql-connector-java-5.1.46 ( Revision: 9cc87a48e75c2d2e87c1a293b2862ce651cb256e ). 
2439  openJPA  INFO   [main] openjpa.Enhance - Creating subclass and redefining methods for "[class com.hxstrive.openjpa.model.User]". This means that your application will be less efficient than it would if you ran the OpenJPA enhancer.
3017  openJPA  TRACE  [main] openjpa.jdbc.SQL - <t 565372776, conn 1462044018> executing prepstmnt 1778629809 SELECT t0.age, t0.birthday, t0.name, t0.salary FROM User t0 WHERE t0.id = ? [params=?]
3033  openJPA  TRACE  [main] openjpa.jdbc.SQL - <t 565372776, conn 1462044018> [16 ms] spent
User{id=1, name='update-update-User-7ef16e-0', age=80, salary=2357.7673, birthday=Thu Sep 02 22:17:23 CST 2021}

第二次:
User{id=1, name='update-update-User-7ef16e-0', age=80, salary=2357.7673, birthday=Thu Sep 02 22:17:23 CST 2021}

如果关闭 DataCache 缓存,执行上面 Java 代码,输出结果如下:

第一次:
172  openJPA  INFO   [main] openjpa.Runtime - Starting OpenJPA 2.4.2
281  openJPA  INFO   [main] openjpa.jdbc.JDBC - Using dictionary class "org.apache.openjpa.jdbc.sql.MySQLDictionary".
1065  openJPA  INFO   [main] openjpa.jdbc.JDBC - Connected to MySQL version 5.5 using JDBC driver MySQL Connector Java version mysql-connector-java-5.1.46 ( Revision: 9cc87a48e75c2d2e87c1a293b2862ce651cb256e ). 
1799  openJPA  INFO   [main] openjpa.Enhance - Creating subclass and redefining methods for "[class com.hxstrive.openjpa.model.User]". This means that your application will be less efficient than it would if you ran the OpenJPA enhancer.
2424  openJPA  TRACE  [main] openjpa.jdbc.SQL - <t 931675031, conn 111900554> executing prepstmnt 940584193 SELECT t0.age, t0.birthday, t0.name, t0.salary FROM User t0 WHERE t0.id = ? [params=?]
2440  openJPA  TRACE  [main] openjpa.jdbc.SQL - <t 931675031, conn 111900554> [16 ms] spent
User{id=1, name='update-update-User-7ef16e-0', age=80, salary=2357.7673, birthday=Thu Sep 02 22:17:23 CST 2021}

第二次:
2471  openJPA  TRACE  [main] openjpa.jdbc.SQL - <t 931675031, conn 111900554> executing prepstmnt 555273695 SELECT t0.age, t0.birthday, t0.name, t0.salary FROM User t0 WHERE t0.id = ? [params=?]
2471  openJPA  TRACE  [main] openjpa.jdbc.SQL - <t 931675031, conn 111900554> [0 ms] spent
User{id=1, name='update-update-User-7ef16e-0', age=80, salary=2357.7673, birthday=Thu Sep 02 22:17:23 CST 2021}
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号