本文所有测试用代码在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. gvim下用Vundle安装solarized主题的方法

    1.在.vimrc中加入 Bundle 'Solarized' 2.重启gvim,并执行 :BundleInstall 3.将solarized.vim文件放入.vim下的colors文件夹内(如果没 ...

  2. restfull 风格 参考 https://blog.csdn.net/jaryle/article/details/52141097

    https://www.cnblogs.com/xiaoxian1369/p/4332390.html :

  3. springsource-tool-suite插件各个历史版本

    转自:https://blog.csdn.net/zhen_6137/article/details/79384798 目前spring官网(http://spring.io/tools/sts/al ...

  4. style css

    Title 语文 用户名 用户名 数学 英语 <!DOCTYPE html><html lang="en"><head> <meta ch ...

  5. Haskell语言学习笔记(39)Category

    Category class Category cat where id :: cat a a (.) :: cat b c -> cat a b -> cat a c instance ...

  6. 【343】MathJax、LaTex、Mathml 数学公式

    参考:cnblog中添加数学公式支持 分类参考: 1. 基本功能 MathJax 我的LaTeX入门 MathJax basic tutorial and quick reference 分段函数:矩 ...

  7. 输入N组父子对,求父子对所组成的二叉树的高度----17年某公司的笔试题

    题目的大致意思如下: 输入N组数,一组数代表一个父子对(如,0 1,0代表父节点,1代表子节点),求这N组数所组成的二叉树的高度: 例如: 输入:6  0 1  0 2  1 3  1 4  2 5 ...

  8. 根据条件决定My97DatePicker日期控件弹出的日期格式

    代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <tit ...

  9. zg项目 应用系统编码原则

    一.编码说明: 1.系统编码采用三码为原则,通常两码简称之. 1>.子系统或类型 2>.系统小分类 3>.系统大分类 如 IPMS领域业务群: DA 应用软件发展管理系统 DE公用副 ...

  10. SpringBoot application.yml logback.xml 多环境

    启动命令为 //开发环境 java -jar app.jar --spring.profiles.active=dev--server.port=8060 //测试环境 java -jar app.j ...