`
leobluewing
  • 浏览: 241742 次
  • 性别: Icon_minigender_1
  • 来自: 宁波
社区版块
存档分类
最新评论

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. 一级缓存测试:

文件配置:

	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
		<property name="packagesToScan" value="com.restjplat.quickweb" />
		<property name="jpaProperties">
			<props>
				<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
				<prop key="hibernate.format_sql">true</prop>
			</props>
		</property>
	</bean>


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

	private void firstCacheTest(){	
		EntityManager em = emf.createEntityManager();
		Dict d1 = em.find(Dict.class, 1); //find id为1的对象
		Dict d2 = em.find(Dict.class, 1); //find id为1的对象
		logger.info((d1==d2)+""); //true
	
		EntityManager em1 = emf.createEntityManager();
		Dict d3 = em1.find(Dict.class, 1); //find id为1的对象
		EntityManager em2 = emf.createEntityManager();
		Dict d4 = em2.find(Dict.class, 1); //find id为1的对象
		logger.info((d3==d4)+""); //false
	}


输出为:因为sql语句打出来太长,所以用*号代替
Hibernate: ***********
2014-03-17 20:41:44,819  INFO [main] (DictTest.java:76) - true
Hibernate: ***********
Hibernate: ***********
2014-03-17 20:41:44,869  INFO [main] (DictTest.java:84) - false


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

2. 二级缓存测试:

文件配置:

1:实体类直接打上 javax.persistence.Cacheable 标记。
@Entity
@Table(name ="dict")
@Cacheable
public class Dict extends IdEntity{}


2:配置文件修改,在 jpaProperties 下添加,用ehcache来实现二级缓存,另外因为加入了二级缓存,我们将hibernate的统计打开来看看到底是不是被缓存了。
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="javax.persistence.sharedCache.mode">ENABLE_SELECTIVE</prop>
<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标记也可以。

@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
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都不用打,直接默认全部开启二级缓存


测试代码:

private void secondCachetest(){
		EntityManager em1 = emf.createEntityManager();
		Dict d1 = em1.find(Dict.class, 1); //find id为1的对象
		logger.info(d1.getName());
		em1.close();
		
		EntityManager em2 = emf.createEntityManager();
		Dict d2 = em2.find(Dict.class, 1); //find id为1的对象
		logger.info(d2.getName());
		em2.close();
	}

输出:
Hibernate: **************
a
a
===================L2======================
com.restjplat.quickweb.model.Dict : 1


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

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

Hibernate: ************
a
a
===================L2======================
com.restjplat.quickweb.model.Children : 0
com.restjplat.quickweb.model.Dict : 1
org.hibernate.cache.spi.UpdateTimestampsCache : 0
org.hibernate.cache.internal.StandardQueryCache : 0
com.restjplat.quickweb.model.Parent : 0
=================query cache=================


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


3. 查询缓存测试:

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

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

文件配置:
在 jpaProperties 下添加,这里必须明确标出增加查询缓存。
<prop key="hibernate.cache.use_query_cache">true</prop>


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

public interface DictDao extends JpaRepository<Dict, Integer>,JpaSpecificationExecutor<Dict>{

	// spring-data-jpa默认继承实现的一些方法,实现类为
	// SimpleJpaRepository。
	// 该类中的方法不能通过@QueryHint来实现查询缓存。
	@QueryHints({ @QueryHint(name = "org.hibernate.cacheable", value ="true") })
	List<Dict> findAll();
	
	@Query("from Dict")
	@QueryHints({ @QueryHint(name = "org.hibernate.cacheable", value ="true") })
	List<Dict> findAllCached();
	
	@Query("select t from Dict t where t.name = ?1")
	@QueryHints({ @QueryHint(name = "org.hibernate.cacheable", value ="true") })
	Dict findDictByName(String name);
}


测试方法:
private void QueryCacheTest(){
		//无效的spring-data-jpa实现的接口方法
		//输出两条sql语句
		dao.findAll();
		dao.findAll();
		System.out.println("================test 1 finish======================");
		//自己实现的dao方法可以被查询缓存
		//输出一条sql语句
		dao.findAllCached();
		dao.findAllCached();
		System.out.println("================test 2 finish======================");
		//自己实现的dao方法可以被查询缓存
		//输出一条sql语句
		dao.findDictByName("a");
		dao.findDictByName("a");
		System.out.println("================test 3 finish======================");
	}


输出结果:
Hibernate: **************
Hibernate: **************
================test 1 finish======================
Hibernate: ***********
================test 2 finish======================
Hibernate: ***********
================test 3 finish======================
===================L2======================
com.restjplat.quickweb.model.Dict : 5
org.hibernate.cache.spi.UpdateTimestampsCache : 0
org.hibernate.cache.internal.StandardQueryCache : 2
=================query cache=================
select t from Dict t where t.name = ?1
select generatedAlias0 from Dict as generatedAlias0
from Dict


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

4.集合缓存测试:

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

测试类:

@Entity
@Table(name ="parent")
@Cacheable
public class Parent extends IdEntity {
	
	private static final long serialVersionUID = 1L;
	private String name;
	private List<Children> clist;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@OneToMany(fetch = FetchType.EAGER,mappedBy = "parent")
        @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	public List<Children> getClist() {
		return clist;
	}
	public void setClist(List<Children> clist) {
		this.clist = clist;
	}
}

@Entity
@Table(name ="children")
@Cacheable
public class Children extends IdEntity{

	private static final long serialVersionUID = 1L;
	private String name;
	private Parent parent;
	
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "parent_id")
	public Parent getParent() {
		return parent;
	}

	public void setParent(Parent parent) {
		this.parent = parent;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}	
}


测试方法:
private void cellectionCacheTest(){
		EntityManager em1 = emf.createEntityManager();
		Parent p1 = em1.find(Parent.class, 1);
		List<Children> c1 = p1.getClist();
		em1.close();
		System.out.println(p1.getName()+" ");
		for (Children children : c1) {
			System.out.print(children.getName()+",");
		}
		System.out.println();
		EntityManager em2 = emf.createEntityManager();
		Parent p2 = em2.find(Parent.class, 1);
		List<Children> c2 = p2.getClist();
		em2.close();
		System.out.println(p2.getName()+" ");
		for (Children children : c2) {
			System.out.print(children.getName()+",");
		}
		System.out.println();
	}


输出:
Hibernate: ********************
Michael 
kate,Jam,Jason,Brain,
Michael 
kate,Jam,Jason,Brain,
===================L2======================
com.restjplat.quickweb.model.Children : 4
com.restjplat.quickweb.model.Dict : 0
org.hibernate.cache.spi.UpdateTimestampsCache : 0
com.restjplat.quickweb.model.Parent.clist : 1
org.hibernate.cache.internal.StandardQueryCache : 0
com.restjplat.quickweb.model.Parent : 1
=================query cache=================


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

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


接下来是源代码篇

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

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

打断点跟踪吧

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

public List<T> findAll() {
		return getQuery(null, (Sort) null).getResultList();
	}

然后到 QueryImpl<X>类的
private List<X> list() {
		if (getEntityGraphQueryHint() != null) {
			SessionImplementor sessionImpl = (SessionImplementor) getEntityManager().getSession();
			HQLQueryPlan entityGraphQueryPlan = new HQLQueryPlan( getHibernateQuery().getQueryString(), false,
					sessionImpl.getEnabledFilters(), sessionImpl.getFactory(), getEntityGraphQueryHint() );
			// Safe to assume QueryImpl at this point.
			unwrap( org.hibernate.internal.QueryImpl.class ).setQueryPlan( entityGraphQueryPlan );
		}
		return query.list();
	}

进入query.list();

query类的代码解析google一下很多,于是直接到最后:

进入QueryLoader的list方法。

protected List list(
			final SessionImplementor session,
			final QueryParameters queryParameters,
			final Set<Serializable> querySpaces,
			final Type[] resultTypes) throws HibernateException {

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

		if ( cacheable ) {
			return listUsingQueryCache( session, queryParameters, querySpaces, resultTypes );
		}
		else {
			return listIgnoreQueryCache( session, queryParameters );
		}
	}



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

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

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

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


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

在query.list()的方法内部:

public List list() throws HibernateException {
		verifyParameters();
		Map namedParams = getNamedParams();
		before();
		try {
			return getSession().list(
					expandParameterLists(namedParams),
			        getQueryParameters(namedParams)
				);
		}
		finally {
			after();
		}
	}

getQueryParameters(namedParams)这个方法实际获取的是query对象的cacheable属性的值,也就是说,query对象新建的时候cacheable的值决定了这个query方法能不能被查询缓存。


接下来query的建立过程:

在 SimpleJpaRepository 类中 return applyLockMode(em.createQuery(query));

直接由emcreate,再跟踪到 AbstractEntityManagerImpl中

@Override
	public <T> QueryImpl<T> createQuery(
			String jpaqlString,
			Class<T> resultClass,
			Selection selection,
			QueryOptions queryOptions) {
		try {
			org.hibernate.Query hqlQuery = internalGetSession().createQuery( jpaqlString );

			....
			return new QueryImpl<T>( hqlQuery, this, queryOptions.getNamedParameterExplicitTypes() );
		}
		catch ( RuntimeException e ) {
			throw convert( e );
		}
	}
即通过session.createQuery(jpaqlString ) 创建初始化对象。

在query类定义中
public abstract class AbstractQueryImpl implements Query {

        private boolean cacheable;
}
cacheable不是对象类型,而是基本类型,所以不赋值的情况下默认为“false”。


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

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

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


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


在返回query的过程中通过applyHints()方法读取了方法上的QueryHint注解从而设置了查询缓存。
分享到:
评论
3 楼 slowvic 2014-09-22  
lz,除了spring-data-jpa原生的findAll方法,其他方法,比如最重要的findOne方法,有断点调试过吗?我试了下,用@Cachable注解,貌似确实没效果。
@Cache注解的运行机制是什么呢,为什么这个可以正常缓存?
哈哈,这两个问题调试下就知道的,不过如果lz调过我就省事了。
另外要纠正下,lz自己都说了一、二级缓存的key都是id,findAll方法虽然查询的是list,但也是针对id循环的,所以不是查询缓存而是二级缓存。
2 楼 leobluewing 2014-03-21  
happyjeef18 写道
你们好。 谢谢分享你的文章。
请问你的说的测试代码 在哪里?https://github.com/wwlleo0730/restjplat   这个上面找不到你说的addDB分支

这个缓存方面的问题也弄的我头痛。 希望你能给个例子 学习学习! 3Q


https://github.com/wwlleo0730/restjplat/tree/addDB/
1 楼 happyjeef18 2014-03-21  
你们好。 谢谢分享你的文章。
请问你的说的测试代码 在哪里?https://github.com/wwlleo0730/restjplat   这个上面找不到你说的addDB分支

这个缓存方面的问题也弄的我头痛。 希望你能给个例子 学习学习! 3Q

相关推荐

    手动创建 SpringMvc +SpringDataJpa+Hibernate+ freemarker mavenProject+ 环境切换 webDemo

    总结来说,本项目是一个基础的Web开发框架,结合了SpringMVC的MVC设计模式、Spring Data JPA的数据访问层、Hibernate的ORM能力以及FreeMarker的模板引擎,同时还实现了环境配置的灵活切换,为开发高效、可维护的Web...

    Spring+SpringMVC+SpringDataJPA+Hibernate

    在本文中,我们将深入探讨"Spring+SpringMVC+SpringDataJPA+Hibernate"这个集成框架,这是一个在Java开发中广泛使用的强大组合,用于构建高效、可扩展的企业级应用程序。 首先,Spring是一个全面的Java应用框架,它...

    Spring+SpringMVC+SpringData+JPA+hibernate+shiro

    在这个"Spring+SpringMVC+SpringData+JPA+Hibernate+Shiro"的组合中,我们涉及到了Spring生态系统的多个核心组件,以及两个重要的持久层技术和一个安全框架。下面将逐一详细介绍这些技术及其整合方式。 1. **Spring...

    SpringMVC +Hibernate JPA+Spring-data-jpa实例

    1. **配置文件**:设置SpringMVC、Hibernate JPA和Spring Data JPA的相关配置,如数据源、实体扫描路径、JPA配置等。 2. **实体类**:定义与数据库表对应的实体类,包含属性和注解。 3. **Repository接口**:定义...

    spring-boot+kafka+hibernate+redis示例

    通过在项目中引入相应的starter依赖,如`spring-boot-starter-data-jpa`(用于Hibernate)、`spring-kafka`(用于Kafka)和`spring-boot-starter-data-redis`(用于Redis),我们可以快速地集成这些技术。...

    spring-boot-starter-data-jpa 的二级缓存

    总之,Spring Boot通过starter-data-jpa模块和Hibernate、Ehcache整合,为我们提供了一个强大的工具集,可以有效地实现二级缓存机制,优化应用性能,减少数据库压力。开发者需要掌握这些知识点,以确保在实际的项目...

    Spring-session2整合spring5+redis

    在Spring框架中,通常使用Spring Data JPA或者Hibernate的SessionFactory来集成ORM功能。 接下来,Spring-data-redis是Spring Data的一个子项目,为Spring应用提供了与Redis的集成。它包含了一个丰富的Repository...

    spring-data-jpa hibernate

    - 当Spring Data JPA配置了Hibernate作为其JPA供应商时,它会利用Hibernate的实体管理和查询功能。 - Spring Data JPA的Repository接口在底层使用Hibernate的Session来执行查询和操作数据库。 - Spring的事务管理...

    springboot+jpa(hibernate配置redis为二级缓存) springboot2.1.4

    在本文中,我们将深入探讨如何在Spring Boot 2.1.4.RELEASE项目中结合JPA(Java Persistence API)和Hibernate实现Redis作为二级缓存。首先,我们需要理解这些技术的基本概念。 Spring Boot 是一个用于简化Spring...

    (转)jpa+Spring+hibernate 简单配置实例

    【标题】"(转)jpa+Spring+hibernate 简单配置实例" 提供了一个关于如何在Java应用中整合使用Java Persistence API (JPA), Spring框架, 和Hibernate ORM工具的基础教程。这个实例旨在展示如何搭建一个简单但完整的...

    SpringBoot + SpringData Jpa + Scala + Mysql(java+Scala混编)

    SpringData JPA是Spring框架的一个模块,用于简化JPA(Java Persistence API)的使用。它提供了一个统一的API来访问各种持久化技术,如Hibernate、EclipseLink等。通过SpringData JPA,你可以利用注解驱动的方法来...

    spring mvc + spring + hibernate 全注解整合开发视频教程 11

    此外,教程可能还会涵盖事务管理的配置,以及如何使用Spring Data JPA进一步简化数据访问层的代码。 整合这三大框架,可以实现高效、灵活且易于维护的Java Web应用。通过注解,开发者可以减少XML配置,提高代码的...

    spring boot+jpa+jsp(web)+hibernate

    在Spring Boot项目中,通过配置Spring Data JPA模块,可以轻松地集成Hibernate,并利用其强大的数据操作能力。 JSP(JavaServer Pages)是Java Web开发中的视图层技术,用于生成动态网页内容。它允许开发者在HTML中...

    spring4.2,spring mvc,spring data,jpa,hibernate的程序构架

    在Spring项目中,我们通常结合Spring Data JPA和Hibernate来利用其强大的特性,例如事务管理、缓存机制和性能优化。Hibernate不仅支持JPA规范,还提供了一些额外的功能,如HQL(Hibernate查询语言)和Criteria API,...

    spring-data-JPA帮助文档

    Spring Data JPA遵循JPA规范,这意味着它可以与其他JPA提供商(如Hibernate、EclipseLink等)无缝集成。开发者可以选择最适合项目需求的JPA实现。 七、分页和排序 Spring Data JPA提供了PagingAndSortingRepository...

    spring-data Jpa Jar包

    - 使用JPA的缓存机制,如一级缓存(Entity Manager级别的缓存)和二级缓存(可选,如Hibernate的第二级缓存),提升查询效率。 11. **Spring Boot集成** - 在Spring Boot项目中,Spring Data JPA的配置非常简单,...

    Spring-data-jpa 学习笔记.docx

    #### 三、SpringData方法定义规范 Spring Data JPA 通过定义方法名的约定来推断方法的行为。以下是一些常用的方法名前缀和示例: - **查询所有数据**:`findAll()` 或 `findUsers()`(假设实体类名为 User)。 - *...

    spring-data-jpa-1.1.0.RELEASE

    8. 配置灵活性:Spring Data JPA允许通过Java配置或XML配置来设置JPA的相关属性,如实体管理工厂、事务管理器等。 9. 支持JPA供应商:如Hibernate、EclipseLink等,具备良好的兼容性。 10. 异常处理:统一的异常...

    spring-data-jpa-1.3.0.RELEASE.zip

    在性能优化方面,Spring Data JPA 支持缓存机制,通过集成如 Hibernate 的二级缓存,能够显著提高数据访问速度。同时,通过 `@Modifying` 注解,我们可以在不启动事务的情况下执行更新或删除操作,提高系统的响应...

    spring4.2+hibernate5环境开发全部jar包

    - **数据访问**:Spring JDBC和Spring Data JPA模块提供了数据库访问的支持,包括事务管理和数据源配置。 - **Spring Boot**:虽然Spring 4.2时代Spring Boot尚未成为主流,但其简化配置的理念已经开始流行,为...

Global site tag (gtag.js) - Google Analytics