JPA中L2 cache和query cache初探

本文欢迎转载,但请标明出处: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 cache

CacheStoreMode.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;

	@Id
	private 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;
	}

	@Override
	public 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实现提供商的参数。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JPA ,可以使用 Query 对象执行查询操作。Query 接口通常由 EntityManager 创建,可以使用以下方法之一获取 Query 对象: - EntityManager.createQuery(String jpql):使用 JPQL 创建 Query 对象。 - EntityManager.createNativeQuery(String sql):使用 SQL 创建 Query 对象。 - EntityManager.createNamedQuery(String name):使用命名查询创建 Query 对象。 使用 Query 对象可以执行以下操作: - 设置查询参数:可以使用 Query 对象的 setParameter 方法设置查询参数。例如,可以使用 setParameter 方法设置查询参数的值,如下所示: ``` Query query = entityManager.createQuery("SELECT e FROM Employee e WHERE e.name = :name"); query.setParameter("name", "John Doe"); ``` - 执行查询:可以使用 Query 对象的 getResultList 方法获取查询结果列表,或者使用 getSingleResult 方法获取单个查询结果。例如,以下代码查询 Employee 实体所有的记录: ``` Query query = entityManager.createQuery("SELECT e FROM Employee e"); List<Employee> employees = query.getResultList(); ``` - 分页查询:可以使用 Query 对象的 setFirstResult 和 setMaxResults 方法进行分页查询。例如,以下代码查询 Employee 实体前 10 条记录: ``` Query query = entityManager.createQuery("SELECT e FROM Employee e"); query.setFirstResult(0); query.setMaxResults(10); List<Employee> employees = query.getResultList(); ``` - 使用动态查询:可以使用 Criteria API 构建动态查询。例如,以下代码使用 Criteria API 构建查询: ``` CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Employee> cq = cb.createQuery(Employee.class); Root<Employee> root = cq.from(Employee.class); cq.select(root); cq.where(cb.equal(root.get("name"), "John Doe")); Query query = entityManager.createQuery(cq); List<Employee> employees = query.getResultList(); ``` 以上是 JPA Query 的基本使用方法,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值