zoukankan      html  css  js  c++  java
  • spring-data-jpa+hibernate 各种缓存的配置演示

    本文所有测试用代码在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. 一级缓存测试:

    文件配置:

    Java代码  收藏代码
    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>  



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

    Java代码  收藏代码
    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.   
    7.     EntityManager em1 = emf.createEntityManager();  
    8.     Dict d3 = em1.find(Dict.class, 1); //find id为1的对象  
    9.     EntityManager em2 = emf.createEntityManager();  
    10.     Dict d4 = em2.find(Dict.class, 1); //find id为1的对象  
    11.     logger.info((d3==d4)+""); //false  
    12. }  



    Java代码  收藏代码
    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 标记。

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



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

    Java代码  收藏代码
    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标记也可以。

    Java代码  收藏代码
    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都不用打,直接默认全部开启二级缓存


    测试代码:

    Java代码  收藏代码
    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.           
    7.         EntityManager em2 = emf.createEntityManager();  
    8.         Dict d2 = em2.find(Dict.class, 1); //find id为1的对象  
    9.         logger.info(d2.getName());  
    10.         em2.close();  
    11.     }  


    输出:

    Java代码  收藏代码
    1. Hibernate: **************  
    2. a  
    3. a  
    4. ===================L2======================  
    5. com.restjplat.quickweb.model.Dict : 1  



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

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

    Java代码  收藏代码
    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 下添加,这里必须明确标出增加查询缓存。

    Java代码  收藏代码
    1. <prop key="hibernate.cache.use_query_cache">true</prop>  



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

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



    测试方法:

    Java代码  收藏代码
    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.     }  



    输出结果:

    Java代码  收藏代码
    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级缓存里面。

    测试类:

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



    测试方法:

    Java代码  收藏代码
    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.     }  



    输出:

    Java代码  收藏代码
    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类

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



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

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

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

    Java代码  收藏代码
    1. final boolean cacheable = factory.getSettings().isQueryCacheEnabled() &&  
    2.             queryParameters.isCacheable();  



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

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



    接下来query的建立过程:

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



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

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

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


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



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

  • 相关阅读:
    关于学习
    两个有序链表序列的合并
    谜题 UVA227
    周期串(Periodic Strings, UVa455)
    数数字(Digit Counting,ACM/ICPC Danang 2007,UVa1225)
    得分(Score, ACM/ICPC Seoul 2005,UVa 1585)
    201505061055_《Javascript编码规范笔记》
    201505031734_《JavaScript中的函数》
    201505030956_《Javascript变量整理》
    201505022013_《js好的坏的》
  • 原文地址:https://www.cnblogs.com/qianzf/p/7476847.html
Copyright © 2011-2022 走看看