JPA中L2 cache和query cache初探

来源:互联网 发布:红外光谱分析软件 编辑:程序博客网 时间:2024/05/17 19:20

本文欢迎转载,但请标明出处:http://blog.csdn.net/netyeaxi/article/details/49669757

        从JPA2.1中我们可以看到,它在两个层次提供了cache功能: EntityManager cache (L1 cache)和second Level cache(L2 cache),没有提供Query cache。其中,EntityManager中的cache是只存在于EntityManager实例中,一旦EntityManager实例close了,cache就不存在了。这个不是本文研究的重点,本文重点探究Query cache和L2 cache。

        一般Query cache是基于L2 cache实现的,Query cache只存Entity的类型和主键,Entity对象还是要存放在L2 cache中的。查询语句首先会从Query cache中找到此查询结果对应的所有Entity的主键,然后根据这些主键到L2 cache中找出对应的所有Entity的实例。

        JPA2.1中没有要求实现Query cache,只要求实现L2 cache。JPA2.1中只规定了EntityManager .find(....)产生的查询是必须能从L2 cache中读取数据的,这种查询是直接拿Entity的主键到L2 cache中找出对应的Entity实例;而其它一般性查询,比如由EntityManager.createQuery(. . . )产生的查询,没有规定其必须能从L2 cache读取数据,但却可以将其查询的结果存放到L2 cache中,JPA实现提供商可以自己实现这种Query cache(比如Hibernate就明确支持Query cache)。考虑到通过Query cache提高查询速度这种需求大量存在,JPA下一版规范应该会让现有的Query API支持Query cache。

        关于如何使用JPA中的L2 cache,可以在Java EE Tutorial “Using a Second-Level Cache with Java Persistence API Applications”一节中找到,本文只对针对其中讲的不太清楚的地方讲一讲。

       对于L2 cache的操作是由如下两个参数控制的:

   1. javax.persistence.cache.retrieveMode

CacheRetrieveMode.USE    //先从L2 cache读取数据,如果读不到数据,再从database读取数据。此为默认值CacheRetrieveMode.BYPASS //不从L2 cache读取数据,直接从database读取数据
        需要注意的是,此参数只负责从database读取数据,不负责将从database读取的数据保存到L2 cache中,保存到L2 cache中的操作由下面的参数控制:

 2. javax.persistence.cache.storeMode

CacheStoreMode.USE        //当数据从database中读取时或要保存到database时,如果这些数据在L2 cache中存在,不会强制更新这些数据;如果不存在,则保存到L2 cache中。此为默认值CacheStoreMode.BYPASS     //当数据从database中读取时或要保存到database时,不会更新L2 cacheCacheStoreMode.REFRESH    //当数据从database中读取时或要保存到database时,如果这些数据在L2 cache中存在,会强制更新这些数据;如果不存在,则保存到L2 cache中

    从以上可以看出,如果想要L2 cache起作用,这两个参数需要同时使用。不妨写一个简单的程序验证一下上面的内容:

@Entity@Table(name = "gf_stock")@NamedQuery(name = "GfStock.findAll", query = "SELECT g FROM GfStock g")public class GfStock implements Serializable {private static final long serialVersionUID = 1L;@Idprivate int stockid;private String stockdesc;private String stockname;public GfStock() {}public int getStockid() {return this.stockid;}public void setStockid(int stockid) {this.stockid = stockid;}public String getStockdesc() {return this.stockdesc;}public void setStockdesc(String stockdesc) {this.stockdesc = stockdesc;}public String getStockname() {return this.stockname;}public void setStockname(String stockname) {this.stockname = stockname;}@Overridepublic String toString() {return "[stockid=" + stockid + ", stockdesc=" + stockdesc + ", stockname=" + stockname + "]";}}
测试程序:
EntityManager em = null;GfStock stock = null;List<GfStock> list = null;TypedQuery<GfStock> tqFindAll = null;boolean ret = false;int stockid = 0;System.out.println("1+++++++++++++++++++++++++++++++++++++");// 查询表中所有记录,并设置成先从cache中读取数据,同时不把从database中读到的数据保存到cache中em = this.getEntityManager();System.out.println("getEntityManager: " + em);tqFindAll = em.createQuery("SELECT g FROM GfStock g", GfStock.class);tqFindAll.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);tqFindAll.setHint("javax.persistence.cache.storeMode", CacheStoreMode.BYPASS);list = tqFindAll.getResultList();// 查找cache中是否已有数据ret = getEntityManagerFactory().getCache().contains(GfStock.class, stockid);System.out.println("can find in L2 cache=" + ret);System.out.println("2+++++++++++++++++++++++++++++++++++++");// 查询表中所有记录,并设置成先从cache中读取数据,同时把从database中读到的数据保存到cache中em = this.getEntityManager();System.out.println("getEntityManager: " + em);tqFindAll = em.createQuery("SELECT g FROM GfStock g", GfStock.class);tqFindAll.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);tqFindAll.setHint("javax.persistence.cache.storeMode", CacheStoreMode.USE);list = tqFindAll.getResultList();// 查找cache中是否已有数据ret = getEntityManagerFactory().getCache().contains(GfStock.class, stockid);System.out.println("can find in L2 cache=" + ret);System.out.println("3+++++++++++++++++++++++++++++++++++++");// 查询表中所有记录,并设置成先从cache中读取数据,同时把从database中读到的数据保存到cache中em = this.getEntityManager();System.out.println("getEntityManager: " + em);tqFindAll = em.createQuery("SELECT g FROM GfStock g", GfStock.class);tqFindAll.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);tqFindAll.setHint("javax.persistence.cache.storeMode", CacheStoreMode.USE);list = tqFindAll.getResultList();// 查找cache中是否已有数据ret = getEntityManagerFactory().getCache().contains(GfStock.class, stockid);System.out.println("can find in L2 cache=" + ret);System.out.println("4+++++++++++++++++++++++++++++++++++++");// 通过EntityManager.find(....)方法查找数据,看是否会从cache中读取数据em = this.getEntityManager();System.out.println("getEntityManager: " + em);stock = em.find(GfStock.class, stockid);System.out.println(stock);System.out.println("5+++++++++++++++++++++++++++++++++++++");// 通过EntityManager.createQuery(....)方法查找数据,看是否会从cache中读取数据em = this.getEntityManager();System.out.println("getEntityManager: " + em);TypedQuery<GfStock> tqFindById = em.createQuery("SELECT g FROM GfStock g where g.stockid = ?1", GfStock.class);tqFindById.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);tqFindById.setHint("javax.persistence.cache.storeMode", CacheStoreMode.USE);tqFindById.setParameter(1, stockid);stock = tqFindById.getSingleResult();System.out.println(stock);System.out.println("+++++++++++++++++++++++++++++++++++++");
打开Hibernate SQL日志,看看执行结果:
1+++++++++++++++++++++++++++++++++++++
getEntityManager: org.hibernate.jpa.internal.EntityManagerImpl@2881ad47
Hibernate: select gfstock0_.stockid as stockid1_1_, gfstock0_.stockdesc as stockdes2_1_, gfstock0_.stockname as stocknam3_1_ from gf_stock gfstock0_
can find in L2 cache=false
2+++++++++++++++++++++++++++++++++++++
getEntityManager: org.hibernate.jpa.internal.EntityManagerImpl@7f92b990
Hibernate: select gfstock0_.stockid as stockid1_1_, gfstock0_.stockdesc as stockdes2_1_, gfstock0_.stockname as stocknam3_1_ from gf_stock gfstock0_
can find in L2 cache=true
3+++++++++++++++++++++++++++++++++++++
getEntityManager: org.hibernate.jpa.internal.EntityManagerImpl@557eb543
Hibernate: select gfstock0_.stockid as stockid1_1_, gfstock0_.stockdesc as stockdes2_1_, gfstock0_.stockname as stocknam3_1_ from gf_stock gfstock0_
can find in L2 cache=true
4+++++++++++++++++++++++++++++++++++++
getEntityManager: org.hibernate.jpa.internal.EntityManagerImpl@63716833
[stockid=0, stockdesc=111, stockname=7]
5+++++++++++++++++++++++++++++++++++++
getEntityManager: org.hibernate.jpa.internal.EntityManagerImpl@226eba67
Hibernate: select gfstock0_.stockid as stockid1_1_, gfstock0_.stockdesc as stockdes2_1_, gfstock0_.stockname as stocknam3_1_ from gf_stock gfstock0_ where gfstock0_.stockid=?
[stockid=0, stockdesc=111, stockname=7]
+++++++++++++++++++++++++++++++++++++


程序执行时每次都获取一个新的EntityManager,对执行结果分析如下:

1. 使用EntityManager.createQuery(. . . ),执行Query后,从database中读取的数据,数据没有保存到L2 cache中
2. 使用EntityManager.createQuery(. . . ),执行Query后,从database中读取的数据,数据保存到了L2 cache中
3. 使用EntityManager.createQuery(. . . ),执行Query时,仍然从database中读取数据,没从L2 cache中读取,数据保存到了L2 cache中
4. 使用EntityManager.find(...) ,通过主键查询时,是从L2 cache中读取的数据
5. 使用EntityManager.createQuery(. . . ),通过主键查询时,仍然从database中读取数据,虽然此时L2 cache中有数据

       通过以上程序执行结果可以知道,当前JPA2.1中使用EntityManager.createQuery(. . . )执行Query时,不能从L2 cache中读取数据以加快执行速度。那有没有方法可以让EntityManager.createQuery(. . . )执行Query时先从L2 cache中读取?有,但需要使用JPA实现提供商提供的解决方案。下面以Hibernate的实现方式来说明:

import javax.persistence.EntityManager;import javax.persistence.Query;import javax.persistence.TypedQuery;public class JpaHibernateCacheQueryCreator {    /**     * 1. 通过强转的方式将javax.persistence.Query或javax.persistence.TypedQuery转化为org.hibernate.Query,然后打开Query cache     */    public static <T> TypedQuery<T> createQueryWithCast(EntityManager em, String qlString, Class<T> resultClass) {        TypedQuery<T> pq = em.createQuery(qlString, resultClass);        org.hibernate.Query hq = (org.hibernate.Query) pq;        hq.setCacheable(true);        return pq;    }    public static Query createQueryWithCast(EntityManager em, String qlString) {        Query pq = em.createQuery(qlString);        org.hibernate.Query hq = (org.hibernate.Query) pq;        hq.setCacheable(true);        return pq;    }    /**     * 2. 使用javax.persistence.Query或javax.persistence.TypedQuery中的unwrap方法获取org.hibernate.Query,然后打开Query cache     */    public static <T> TypedQuery<T> createQueryWithUnwrap(EntityManager em, String qlString, Class<T> resultClass) {        TypedQuery<T> pq = em.createQuery(qlString, resultClass);        org.hibernate.Query hq = pq.unwrap(org.hibernate.Query.class);        hq.setCacheable(true);        return pq;    }    public static Query createQueryWithUnwrap(EntityManager em, String qlString) {        Query pq = em.createQuery(qlString);        org.hibernate.Query hq = pq.unwrap(org.hibernate.Query.class);        hq.setCacheable(true);        return pq;    }    /**     * 3. 使用setHint方法,设置JPA提供商提供的参数打开Query cache     */    public static <T> TypedQuery<T> createQueryWithHint(EntityManager em, String qlString, Class<T> resultClass) {        TypedQuery<T> pq = em.createQuery(qlString, resultClass);        pq.setHint("org.hibernate.cacheable", Boolean.TRUE.toString());        return pq;    }    public static Query createQueryWithHint(EntityManager em, String qlString) {        Query pq = em.createQuery(qlString);        pq.setHint("org.hibernate.cacheable", Boolean.TRUE.toString());        return pq;    }}

         前两种方式会引入JPA实现提供商自己的API,这样会污染程序代码,一定程度上失去了使用JPA的意义,所以不是最好方案。而第三种方式没有这个问题,在实际使用时如果将参数提取放入配置文件中,可以很方便的更换成其它JPA实现提供商的参数。


0 0
原创粉丝点击