本文所有测试用代码在https://github.com/wwlleo0730/restjplat 的分支addDB上

目前在使用spring-data-jpa和hibernate4的时候,对于缓存关系不是很清楚,以及二级缓存和查询缓存的各种配置等等,于是就有了这篇初级的jpa+hibernate缓存配置使用的文章。

JPA和hibernate的缓存关系,以及系统demo环境说明

JPA全称是:Java Persistence API

引用
JPA itself is just a specification, not a product; it cannot perform persistence or anything else by itself.

JPA仅仅只是一个规范,而不是产品;使用JPA本身是不能做到持久化的。

所以,JPA只是一系列定义好的持久化操作的接口,在系统中使用时,需要真正的实现者,在这里,我们使用Hibernate作为实现者。所以,还是用spring-data-jpa+hibernate4+spring3.2来做demo例子说明本文。

JPA规范中定义了很多的缓存类型:一级缓存,二级缓存,对象缓存,数据缓存,等等一系列概念,搞的人糊里糊涂,具体见这里:

http://en.wikibooks.org/wiki/Java_Persistence/Caching

不过缓存也必须要有实现,因为使用的是hibernate,所以基本只讨论hibernate提供的缓存实现。

很多其他的JPA实现者,比如toplink(EclipseLink),也许还有其他的各种缓存实现,在此就不说了。

先直接给出所有的demo例子

hibernate实现中只有三种缓存类型:

一级缓存,二级缓存和查询缓存。

在hibernate的实现概念里,他把什么集合缓存之类的统一放到二级缓存里去了。

1. 一级缓存测试:

文件配置:

  1. <bean id="entityManagerFactory"
  2. class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  3. <property name="dataSource" ref="dataSource" />
  4. <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
  5. <property name="packagesToScan" value="com.restjplat.quickweb" />
  6. <property name="jpaProperties">
  7. <props>
  8. <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
  9. <prop key="hibernate.format_sql">true</prop>
  10. </props>
  11. </property>
  12. </bean>

可见没有添加任何配置项。

  1. private void firstCacheTest(){
  2. EntityManager em = emf.createEntityManager();
  3. Dict d1 = em.find(Dict.class, 1); //find id为1的对象
  4. Dict d2 = em.find(Dict.class, 1); //find id为1的对象
  5. logger.info((d1==d2)+""); //true
  6. EntityManager em1 = emf.createEntityManager();
  7. Dict d3 = em1.find(Dict.class, 1); //find id为1的对象
  8. EntityManager em2 = emf.createEntityManager();
  9. Dict d4 = em2.find(Dict.class, 1); //find id为1的对象
  10. logger.info((d3==d4)+""); //false
  11. }
  1. 输出为:因为sql语句打出来太长,所以用*号代替
  2. Hibernate: ***********
  3. 2014-03-17 20:41:44,819  INFO [main] (DictTest.java:76) - true
  4. Hibernate: ***********
  5. Hibernate: ***********
  6. 2014-03-17 20:41:44,869  INFO [main] (DictTest.java:84) - false

由此可见:同一个session内部,一级缓存生效,同一个id的对象只有一个。不同session,一级缓存无效。

2. 二级缓存测试:

文件配置:

1:实体类直接打上 javax.persistence.Cacheable 标记。

  1. @Entity
  2. @Table(name ="dict")
  3. @Cacheable
  4. public class Dict extends IdEntity{}

2:配置文件修改,在 jpaProperties 下添加,用ehcache来实现二级缓存,另外因为加入了二级缓存,我们将hibernate的统计打开来看看到底是不是被缓存了。

  1. <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
  2. <prop key="javax.persistence.sharedCache.mode">ENABLE_SELECTIVE</prop>
  3. <prop key="hibernate.generate_statistics">true</prop>

注1:如果在配置文件中加入了

<prop
key="javax.persistence.sharedCache.mode">ENABLE_SELECTIVE</prop>,则不需要在实体内配置hibernate的
@cache标记,只要打上JPA的@cacheable标记即可默认开启该实体的2级缓存。

注2:如果不使用javax.persistence.sharedCache.mode配置,直接在实体内打@cache标记也可以。

  1. @Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
  2. public class Dict extends IdEntity{}

至于 hibernate的 hibernate.cache.use_second_level_cache这个属性,文档里是这么写的:

引用
Can be used
to completely disable the second level cache, which is enabled by
default for classes which specify a <cache> mapping.

即打上只要有@cache标记,自动开启。

所以有两种方法配置开启二级缓存:

第一种不使用hibernate的@cache标记,直接用@cacheable标记和缓存映射配置项。

第二种用hibernate的@cache标记使用。

另外javax.persistence.sharedCache.mode的其他配置如下:

The javax.persistence.sharedCache.mode property can be set to one of the following values:

    • ENABLE_SELECTIVE (Default and recommended value): entities are not cached unless explicitly marked as cacheable.
    • DISABLE_SELECTIVE: entities are cached unless explicitly marked as not cacheable.
    • NONE: no entity are cached even if marked as cacheable. This option can make sense to disable second-level cache altogether.
    • ALL: all entities are always cached even if marked as non cacheable.

如果用all的话,连实体上的@cacheable都不用打,直接默认全部开启二级缓存

测试代码:

  1. private void secondCachetest(){
  2. EntityManager em1 = emf.createEntityManager();
  3. Dict d1 = em1.find(Dict.class, 1); //find id为1的对象
  4. logger.info(d1.getName());
  5. em1.close();
  6. EntityManager em2 = emf.createEntityManager();
  7. Dict d2 = em2.find(Dict.class, 1); //find id为1的对象
  8. logger.info(d2.getName());
  9. em2.close();
  10. }

输出:

  1. Hibernate: **************
  2. a
  3. a
  4. ===================L2======================
  5. com.restjplat.quickweb.model.Dict : 1

可见二级缓存生效了,只输出了一条sql语句,同时监控中也出现了数据。

另外也可以看看如果是配置成ALL,并且把@cacheable删掉,输出如下:

  1. Hibernate: ************
  2. a
  3. a
  4. ===================L2======================
  5. com.restjplat.quickweb.model.Children : 0
  6. com.restjplat.quickweb.model.Dict : 1
  7. org.hibernate.cache.spi.UpdateTimestampsCache : 0
  8. org.hibernate.cache.internal.StandardQueryCache : 0
  9. com.restjplat.quickweb.model.Parent : 0
  10. =================query cache=================

并且可以看见,所有的实体类都加入二级缓存中去了

3. 查询缓存测试:

一,二级缓存都是根据对象id来查找,如果需要加载一个List的时候,就需要用到查询缓存。

在Spring-data-jpa实现中,也可以使用查询缓存。

文件配置:

在 jpaProperties 下添加,这里必须明确标出增加查询缓存。

  1. <prop key="hibernate.cache.use_query_cache">true</prop>

然后需要在方法内打上@QueryHint来实现查询缓存,我们写几个方法来测试如下:

  1. public interface DictDao extends JpaRepository<Dict, Integer>,JpaSpecificationExecutor<Dict>{
  2. // spring-data-jpa默认继承实现的一些方法,实现类为
  3. // SimpleJpaRepository。
  4. // 该类中的方法不能通过@QueryHint来实现查询缓存。
  5. @QueryHints({ @QueryHint(name = "org.hibernate.cacheable", value ="true") })
  6. List<Dict> findAll();
  7. @Query("from Dict")
  8. @QueryHints({ @QueryHint(name = "org.hibernate.cacheable", value ="true") })
  9. List<Dict> findAllCached();
  10. @Query("select t from Dict t where t.name = ?1")
  11. @QueryHints({ @QueryHint(name = "org.hibernate.cacheable", value ="true") })
  12. Dict findDictByName(String name);
  13. }

测试方法:

  1. private void QueryCacheTest(){
  2. //无效的spring-data-jpa实现的接口方法
  3. //输出两条sql语句
  4. dao.findAll();
  5. dao.findAll();
  6. System.out.println("================test 1 finish======================");
  7. //自己实现的dao方法可以被查询缓存
  8. //输出一条sql语句
  9. dao.findAllCached();
  10. dao.findAllCached();
  11. System.out.println("================test 2 finish======================");
  12. //自己实现的dao方法可以被查询缓存
  13. //输出一条sql语句
  14. dao.findDictByName("a");
  15. dao.findDictByName("a");
  16. System.out.println("================test 3 finish======================");
  17. }

输出结果:

  1. Hibernate: **************
  2. Hibernate: **************
  3. ================test 1 finish======================
  4. Hibernate: ***********
  5. ================test 2 finish======================
  6. Hibernate: ***********
  7. ================test 3 finish======================
  8. ===================L2======================
  9. com.restjplat.quickweb.model.Dict : 5
  10. org.hibernate.cache.spi.UpdateTimestampsCache : 0
  11. org.hibernate.cache.internal.StandardQueryCache : 2
  12. =================query cache=================
  13. select t from Dict t where t.name = ?1
  14. select generatedAlias0 from Dict as generatedAlias0
  15. from Dict

很明显,查询缓存生效。但是为什么第一种方法查询缓存无法生效,原因不明,只能后面看看源代码了。

4.集合缓存测试:

根据hibernate文档的写法,这个应该是算在2级缓存里面。

测试类:

  1. @Entity
  2. @Table(name ="parent")
  3. @Cacheable
  4. public class Parent extends IdEntity {
  5. private static final long serialVersionUID = 1L;
  6. private String name;
  7. private List<Children> clist;
  8. public String getName() {
  9. return name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. @OneToMany(fetch = FetchType.EAGER,mappedBy = "parent")
  15. @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
  16. public List<Children> getClist() {
  17. return clist;
  18. }
  19. public void setClist(List<Children> clist) {
  20. this.clist = clist;
  21. }
  22. }
  23. @Entity
  24. @Table(name ="children")
  25. @Cacheable
  26. public class Children extends IdEntity{
  27. private static final long serialVersionUID = 1L;
  28. private String name;
  29. private Parent parent;
  30. @ManyToOne(fetch = FetchType.LAZY)
  31. @JoinColumn(name = "parent_id")
  32. public Parent getParent() {
  33. return parent;
  34. }
  35. public void setParent(Parent parent) {
  36. this.parent = parent;
  37. }
  38. public String getName() {
  39. return name;
  40. }
  41. public void setName(String name) {
  42. this.name = name;
  43. }
  44. }

测试方法:

  1. private void cellectionCacheTest(){
  2. EntityManager em1 = emf.createEntityManager();
  3. Parent p1 = em1.find(Parent.class, 1);
  4. List<Children> c1 = p1.getClist();
  5. em1.close();
  6. System.out.println(p1.getName()+" ");
  7. for (Children children : c1) {
  8. System.out.print(children.getName()+",");
  9. }
  10. System.out.println();
  11. EntityManager em2 = emf.createEntityManager();
  12. Parent p2 = em2.find(Parent.class, 1);
  13. List<Children> c2 = p2.getClist();
  14. em2.close();
  15. System.out.println(p2.getName()+" ");
  16. for (Children children : c2) {
  17. System.out.print(children.getName()+",");
  18. }
  19. System.out.println();
  20. }

输出:

  1. Hibernate: ********************
  2. Michael
  3. kate,Jam,Jason,Brain,
  4. Michael
  5. kate,Jam,Jason,Brain,
  6. ===================L2======================
  7. com.restjplat.quickweb.model.Children : 4
  8. com.restjplat.quickweb.model.Dict : 0
  9. org.hibernate.cache.spi.UpdateTimestampsCache : 0
  10. com.restjplat.quickweb.model.Parent.clist : 1
  11. org.hibernate.cache.internal.StandardQueryCache : 0
  12. com.restjplat.quickweb.model.Parent : 1
  13. =================query cache=================

在统计数据里可见二级缓存的对象数量。

本文我们不讨论关于缓存的更新策略,脏数据等等的东西,只是讲解配置方式。

接下来是源代码篇

理清楚各种配置以后,我们来看一下hibernate和spring-data-jpa的一些缓存实现源代码。

上面有个遗留问题,为什么spring-data-jpa默认实现的findAll()方法无法保存到查询缓存?只能啃源代码了。

打断点跟踪吧

入口方法是spring-data-jpa里的 SimpleJpaRepository类

  1. public List<T> findAll() {
  2. return getQuery(null, (Sort) null).getResultList();
  3. }
  4. 然后到 QueryImpl<X>类的
  5. private List<X> list() {
  6. if (getEntityGraphQueryHint() != null) {
  7. SessionImplementor sessionImpl = (SessionImplementor) getEntityManager().getSession();
  8. HQLQueryPlan entityGraphQueryPlan = new HQLQueryPlan( getHibernateQuery().getQueryString(), false,
  9. sessionImpl.getEnabledFilters(), sessionImpl.getFactory(), getEntityGraphQueryHint() );
  10. // Safe to assume QueryImpl at this point.
  11. unwrap( org.hibernate.internal.QueryImpl.class ).setQueryPlan( entityGraphQueryPlan );
  12. }
  13. return query.list();
  14. }
  15. 进入query.list();
  16. query类的代码解析google一下很多,于是直接到最后:
  17. 进入QueryLoader的list方法。
  18. protected List list(
  19. final SessionImplementor session,
  20. final QueryParameters queryParameters,
  21. final Set<Serializable> querySpaces,
  22. final Type[] resultTypes) throws HibernateException {
  23. final boolean cacheable = factory.getSettings().isQueryCacheEnabled() &&
  24. queryParameters.isCacheable();
  25. if ( cacheable ) {
  26. return listUsingQueryCache( session, queryParameters, querySpaces, resultTypes );
  27. }
  28. else {
  29. return listIgnoreQueryCache( session, queryParameters );
  30. }
  31. }

果然有个cacheable,值为false,说明的确是没有从缓存里取数据。

用自定义的jpa查询方法测试后发现,这个值为true。

于是接着看cacheable的取值过程:

  1. final boolean cacheable = factory.getSettings().isQueryCacheEnabled() &&
  2. queryParameters.isCacheable();

factory.getSettings().isQueryCacheEnabled() 这个一定是true,因为是在配置文件中打开的。那只能是queryParameters.isCacheable() 这个的问题了。

  1. 在query.list()的方法内部:
  2. public List list() throws HibernateException {
  3. verifyParameters();
  4. Map namedParams = getNamedParams();
  5. before();
  6. try {
  7. return getSession().list(
  8. expandParameterLists(namedParams),
  9. getQueryParameters(namedParams)
  10. );
  11. }
  12. finally {
  13. after();
  14. }
  15. }
  16. getQueryParameters(namedParams)这个方法实际获取的是query对象的cacheable属性的值,也就是说,query对象新建的时候cacheable的值决定了这个query方法能不能被查询缓存。

接下来query的建立过程:

  1. 在 SimpleJpaRepository 类中 return applyLockMode(em.createQuery(query));
  2. 直接由emcreate,再跟踪到 AbstractEntityManagerImpl中
  3. @Override
  4. public <T> QueryImpl<T> createQuery(
  5. String jpaqlString,
  6. Class<T> resultClass,
  7. Selection selection,
  8. QueryOptions queryOptions) {
  9. try {
  10. org.hibernate.Query hqlQuery = internalGetSession().createQuery( jpaqlString );
  11. ....
  12. return new QueryImpl<T>( hqlQuery, this, queryOptions.getNamedParameterExplicitTypes() );
  13. }
  14. catch ( RuntimeException e ) {
  15. throw convert( e );
  16. }
  17. }
  18. 即通过session.createQuery(jpaqlString ) 创建初始化对象。
  19. 在query类定义中
  20. public abstract class AbstractQueryImpl implements Query {
  21. private boolean cacheable;
  22. }
  23. cacheable不是对象类型,而是基本类型,所以不赋值的情况下默认为“false”。

也就是说spring-data-jpa接口提供的简单快速的各种接口实现全是不能使用查询缓存的,完全不知道为什么这么设计。

接下来看看我们自己实现的查询方法实现:

直接找到query方法的setCacheable()方法打断点,因为肯定改变这个值才能有查询缓存。

  1. 于是跟踪到 SimpleJpaQuery类中
  2. protected Query createQuery(Object[] values) {
  3. return applyLockMode(applyHints(doCreateQuery(values), method), method);
  4. }

在返回query的过程中通过applyHints()方法读取了方法上的QueryHint注解从而设置了查询缓存。

spring-data-jpa+hibernate 各种缓存的配置演示的更多相关文章

  1. Spring Boot 2.x 之 Spring Data JPA, Hibernate 5

    1. Spring Boot常用配置项 基于Spring Boot 2.0.6.RELEASE 1.1 配置属性类 spring.jpa前缀的相关配置项定义在JpaProperties类中, 1.2 ...

  2. spring data jpa hibernate jpa 三者之间的关系

    JPA规范与ORM框架之间的关系是怎样的呢? JPA规范本质上就是一种ORM规范,注意不是ORM框架——因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服 ...

  3. Spring data jpa hibernate:查询异常java.sql.SQLException: Column '列名' not found

    使用spring boot,jap,hibernate不小心的错误: java.sql.SQLException: Column '列名' not found: 这句话的意思是:找不到此列 为什么会出 ...

  4. Spring Data JPA Hibernate @QueryHints

    另一个实例: http://leobluewing.iteye.com/blog/2032396 : 本文内容来源:https://blog.csdn.net/gavinchen1985/articl ...

  5. Spring Data Jpa(Hibernate) OneToMany

    这个其实非常简单.假设有topic 和 subscriber两个实体类,不考虑关联关系,则连个类的代码如下: /** * Created by csonezp on 2017/8/31. */ @En ...

  6. maven springmvc spring data jpa hibernate sqlserver demo

    搭建费了半天费,各种报错,缺少各种jar包,不兼容等,给那些没弄过的一个参考. 点击我下载

  7. 转:spring data jpa、 hibernate、 jpa 三者之间的关系

    原文链接:spring data jpa. hibernate. jpa 三者之间的关系 spring data jpa hibernate jpa 三者之间的关系 JPA规范与ORM框架之间的关系是 ...

  8. Spring Boot (五)Spring Data JPA 操作 MySQL 8

    一.Spring Data JPA 介绍 JPA(Java Persistence API)Java持久化API,是 Java 持久化的标准规范,Hibernate是持久化规范的技术实现,而Sprin ...

  9. Spring Data JPA 和MyBatis比较

    现在Dao持久层的解决方案中,大部分是采用Spring Data JPA或MyBatis解决方案,并且传统企业多用前者,互联网企业多用后者. Spring Data JPA 是Spring Data ...

随机推荐

  1. PHP 程序员学数据结构与算法之《栈》

    “要成高手,必练此功”.   要成为优秀的程序员,数据结构和算法是必修的内容.而现在的Web程序员使用传统算法和数据结构都比较少,因为很多算法都是包装好的,不用我们去操心具体的实现细节,如PHP的取栈 ...

  2. 笔记本 F1 键盘

    笔记本 F1 键盘与传统不符. 在控制面板,键盘设置,选择ok.

  3. xe DateTimePicker.Date bug

    xe6 bug xe7 ok DateTimePicker1->DateTime.DateString(); DateTimePicker1->DateTime.DateTimeStrin ...

  4. PHP图像 因其本身有错无法显示

    昨天终于将客户的一个网站迁移至虚拟主机上,满怀希望的敲入网址.唰的一声,网站很轻松的被打开了. 心里那个高兴啊~~~ 咦,怎么产品图片都没有显示出来.一块块都是空白.敲入img src对应的地址,看看 ...

  5. 15 MySQL--索引

    索引: http://www.cnblogs.com/linhaifeng/articles/7356064.html http://www.cnblogs.com/linhaifeng/articl ...

  6. UI5-文档-4.32-Routing with Parameters

    现在我们可以在overview和detail页面之间导航,但是我们在overview中选择的实际项目还没有显示在detail页面上.我们的应用程序的一个典型用例是在详细信息页面上显示所选项目的附加信息 ...

  7. mysql 2003: Can't connect to MySQL server on '127.0.0.1:3306' (99)

    连接断开的频率太高导致报错,可以在每次连接之间sleep,或者保持一个长连接. ref:https://stackoverflow.com/questions/24884438/2003-cant-c ...

  8. 程序员教程-11章-Java程序设计

    自己是学java的,先看第十一章java吧. 列出章节目录,便于自己回忆内容. 11.1 Java语言概述 1 Java语言的特点 2 Java开发环境 11.2 Java语言基础 11.2.1 基本 ...

  9. 首届阿里巴巴在线技术峰会,9位大V演讲整理!

    https://yq.aliyun.com/articles/57826 感谢参加阿里巴巴在线技术峰会.7月19日的3场专家分享:Blink.Docker.电商互动:7月20日的云数据库十大经典案 例 ...

  10. Linux就业技术指导(五):Linux运维核心管理命令详解

    一,Linux核心进程管理命令 1.1 ps:查看进程 1.1.1 命令解释 功能说明 ps命令用于列出执行ps命令的那个时刻的进程快照,就像用手机给进程照了一张照片.如果想要动态地显示进程,就需要使 ...