本文欢迎转载,但请标明出处: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实现提供商的参数。