Hibernate3.x教程(三) Hibernate缓存介绍

缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频率,从而提高应用的运行性能。
 
Hibernate的缓存包括Sesssion缓存和SessionFactory全局缓存。
Session缓存被称为一级缓存,是Hibernate的内置缓存。使用内存存储,应用于事务范围之内,事务结束,缓存的生命周期同时结束。
SessionFactory全局缓存被称为二级缓存,它是SessionFactory的外置缓存,可以使用不同的缓存类库(如,ehcache、oscache等)实现。可以存储在内存或硬盘中,在整个应用程序中共享,应用结束时,缓存的生命周期才会结束。
 
下面分别介绍两种缓存的配置和使用。
 
Session缓存(一级缓存):
当调用Session的save(),update(),saveOrUpdate()更新对象,或使用load(),get(),list(),iterator(),scroll()等查询对象时,在Session缓存中不存在相应对象,都会把这些对象加入Session缓存。缓存大多都是以key-value形式进行保存的,Session缓存的key值主要由对象id和对象的类名来决定的,value就是实体对象。所以,之后在同一个事务范围内,Session再次根据id查询对象时,便会直接从Session缓存中将该对象返回,不再执行数据库查询操作。
例如,以下代码执行时,只会产生一条数据库查询语句:
Student student = studentDao.get(1);
Student student2 = studentDao.get(1);
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
 
前面已经提到Session缓存的生命周期之存在于当前事务,所以当Session关闭后,Session缓存就会无效。在studentDao.get()方法中执行session.close();会发现,上面代码仍然会执行两次SQL查询,表示Session缓存已经失效。
 
对于Session缓存,Hibernate提供了以下方法进行缓存管理:
  evit(Object obj)  将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源
  clear()  将一级缓存中的所有持久化对象清除,释放其占用的内存资源
  contains(Object obj) 判断指定的对象是否存在于一级缓存中.
  flush() 刷新一级缓存区的内容,使之与数据库数据保持同步.
 
Session缓存的问题就是命中率极低,因为Session的生命周期很短,尤其为了减少并发带来的数据问题,我们通常是倡导尽量使用短事务。
 
查询缓存:
注:Hibernate3.2版本之后查询缓存必须结合二级缓存才可以使用,示例代码是使用Hibernate3.1构建,本节内容可以忽略
查询缓存是缓存普通属性结果集的,对实体对象的结果集只缓存id,主要通过list()方法存储缓存并调用。
 
查询缓存的配置和使用(两者缺一不可):
1、在配置文件hibernate.cfg.xml中启用查询缓存:
<property name="hibernate.cache.use_query_cache">true</property>
2、在程序中必须手动启用查询缓存:
query.setCachable(true);
 
假设我们重复执行下面这段代码:
 session.createQuery("from Student").setCacheable(true).list();
 session.createQuery("from Student").setCacheable(true).list();
它将会仅产生一条SQL查询:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_, student0_.grade_id as grade4_0_ from school.student student0_
 
但是,这里要注意的是,查询缓存必须配合Session缓存使用,如果只启用查询缓存,不对查询对象启用二级缓存,则会大大降低查询效率。
因为,当第一次通过启用查询缓存的session进行语句查询时,系统只执行一次数据库查询将所有的记录取出,并将对象存入Session缓存,语句及id集合存入查询缓存;而当用户第二次查询该语句时,系统将先执行去查询缓存中查找,取出所有符合条件的id集合,如果这时候该对象的Session缓存没启用或在class缓存中已过期,系统将根据id,一个个去数据库load,实际上是进行了1+N次查询。
例如,我们修改上面的代码:
session.createQuery("from Student").setCacheable(true).list();
session.clear();
//session.close();
session.createQuery("from Student").setCacheable(true).list();
查询一次后,清空Session缓存,或关闭Session,它将产生的SQL查询如下:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_, student0_.grade_id as grade4_0_ from school.student student0_
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
......
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
 
就像Session缓存一样,查询缓存同样存在命中问题,并且存在严重的性能问题(1+N)查询。
以上介绍,目的并非是告诉大家如何使用查询缓存,而是要慎用之。
在实际应用中,查询缓存通常会和二级缓存一起使用。
 
二级缓存:
二级缓存是由SessionFactory创建的所有Session对象共享使用, 二级缓存可使用第三方的缓存插件,如EHCache、OSChahe、SwarmCache、JBossCache。
 
二级缓存配置步骤:
1、在hibernate中启动二级类缓存,需要在hibernate.cfg.xml配置以下参数(以EHCache为例).
<hibernate-configuration>
    <session-factory>
        <!-- 设置二级缓存插件EHCache的Provider类-->
        <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
       <!-- 启动"查询缓存" -->
       <property name="hibernate.cache.use_query_cache">true</property>
    </session-factory>
</hibernate-configuration>
2、接下来,需要对EHCache进行配置,添加ehcache.xml配置文件,默认路径为class根目录:
<ehcache>
    <diskStore path="java.io.tmpdir" />
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="300"
            timeToLiveSeconds="300"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="300"/>
    <cache name="com.boya.hibernate.entity.Student" 
        maxElementsInMemory="10000"
        eternal="true" 
        overflowToDisk="false" 
        timeToIdleSeconds="200"
        timeToLiveSeconds="200"/>
      
</ehcache>
上面配置了默认缓存策略和学生对象的缓存策略:
<diskStore>:设置磁盘缓存的位置
<defaultCache>:默认缓存策略
<cache>:自定义缓存策略
name:cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)。
maxElementsInMemory:cache 中最多可以存放的元素的数量。如果放入cache中的元素超过这个数值,有两种情况:1、若overflowToDisk的属性值为true,会将cache中多出的元素放入磁盘文件中。2、若overflowToDisk的属性值为false,会根据memoryStoreEvictionPolicy的策略替换cache中原有的元素。
maxElementsOnDisk:DiskStore中最大允许保存的对象数量,默认值为0,表示不限制。
eternal:设定缓存是否过期,如果是,则它的超时设置会被忽略。
overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上。
timeToIdleSeconds:对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。
timeToLiveSeconds:对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
diskPersistent:是否在磁盘上持久化缓存。指重启jvm后,数据是否有效。默认为false。
diskExpiryThreadIntervalSeconds:对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。
diskSpoolBufferSizeMB:DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。
memoryStoreEvictionPolicy:如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。
 
3、在实体类的映射文件中配置缓存同步策略,如:<cache useage="read-only"/>
    Hibernate提供了四种缓存同步策略:
read-only:只读缓存策略
read-write:读/写缓存策略
nonstrict-read-write:不严格的读/写缓存策略。如果程序对并发数据修改要求不是非常严格,只是偶尔需要更新数据或者两个事务同时更新一条记录的可能性很小,可以采用本选项,获得较好的性能
transactional:事务型缓存策略,发生异常的时候,缓存也能够回滚
 
以上,二级缓存配置就完成了,执行下面代码测试一下二级缓存:
List<Student> list = studentDao.findAll();
Session session = HibernateSessionFactory.getSession();
session.get(Student.class, 1);
session.close();
session = HibernateSessionFactory.getSession();
session.get(Student.class, 1);
session.close();
查看SQL执行情况:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_, student0_.grade_id as grade4_0_ from school.student student0_
只有一条获取所有记录的查询语句,并且,我们知道在session关闭之后,Session缓存是被清空的,所以两次执行session.get()都是从二级缓存获取的数据。
 
对于二级缓存,仍然有其弊端存在:
首先,使用条件查询,或者返回所有结果的查询不会使用二级缓存。
其次,二级缓存适合更新频率较低的数据,对于频繁更新的数据不适合使用二级缓存,缓存这样的数据反而会影响性能。可悲的是,这样的数据正是关乎我们核心业务逻辑的数据,也就是说二级缓存对我们的核心业务帮助不大。
最值得注意的是,使用二级缓存时,所有数据操作必须通过hibernate,否则缓存无法做到同步更新,使缓存数据与实际数据库中出现不一致的情况。而实际应用中,难免会有直接的数据库操作,或者使用jdbc处理错误数据或批量更新等。
 
总结
合理使用Hibernate缓存,可以提升Hibernate的性能。而对于整个应用的性能提升,我们首先应该找出性能瓶颈,再做相应的处理,比如增加数据库索引,优化业务逻辑等。当然,也可以对整个应用设置缓存,这时通常的做法并非是考虑数据库级的缓存,而是尽量从最高的层面设置缓存。比如,可以使用页面静态化,可以在Web层使用分布式缓存策略。分布式缓存大多是用Json+Memcached,现在我更偏爱Redis多一些。
所以,对于Hibernate缓存,要合理使用,不要过分依赖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
hibernate 3中的缓存小结 2.2. 一级缓存的管理: 当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象。 2.3. 二级缓存的管理: 2.3.1. Hibernate的二级缓存策略的一般过程如下: 1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。 2) 把获得的所有数据对象根据ID放入到第二级缓存中。 3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。 4) 删除、更新、增加数据的时候,同时更新缓存。   Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。 2.3.2. 什么样的数据适合存放到第二级缓存中? 1 很少被修改的数据 2 不是很重要的数据,允许出现偶尔并发的数据 3 不会被并发访问的数据 4 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。 2.3.3. 不适合存放到第二级缓存的数据? 1 经常被修改的数据 2 财务数据,绝对不允许出现并发 3 与其他应用共享的数据。 2.3.4. 常用的缓存插件 Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件: l EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。 l OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。 l SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。 l JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。 2.3.5. 配置二级缓存的主要步骤: 1) 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。 2) 选择合适的缓存插件,然后编辑该插件的配置文件。 2.4. 使用EhCache配置二级缓存: 2.4.1. 配置准备: 1) 把ehcache-1.2.3.jar加入到当前应用的classpath中。 2) 在hibernate.cfg.xml文件中加入EhCache缓存插件的提供类。 <!--配置缓存插件 --> <property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property> 3) 挎贝ehcache.xml文件到类路径(项目工程的src目录下),这个文件在Hibernate安装目录的etc下。 2.4.2. 配置步骤: Hibernate允许在类和集合的粒度上设置第二级缓存。在映射文件中,<class>和<set>元素都有一个<cache>子元素,这个子元素用来配置二级缓存。 示例:以category(产品类别)和product(产品)的映射为例: 1) 修改要配置缓存的那个持久化类的对象关系映射文件: Category.hbm.xml <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="org.qiujy.domain.cachedemo.Category" table="categories"> <!— 配置缓存,必须紧跟在class元素后面 对缓存中的Category对象采用读写型的并发访问策略 --> <cache usage="read-write"/> <id name="id" type="java.lang.Long"> <column name="id" /> <generator class="native" /> </id> <!-- 配置版本号,必须紧跟在id元素后面 --> <version name="version" column="version" type="java.lang.Long" /> <property name="name" type="java.lang.String"> <column name="name" length="32" not-null="true"/> </property> <property name="description" type="java.lang.String"> <column name="description" length="255"/> </property> <set name="products" table="products" cascade="all" inverse="true"> <!-- Hibernate只会缓存对象的简单属性的值, 要缓存集合属性,必须在集合元素中也加入<cache>子元素 而Hibernate仅仅是把与当前持久对象关联的对象的OID存放到缓存中。 如果希望把整个关联的对象的所有数据都存入缓存, 则要在相应关联的对象的映射文件中配置<cache>元素 --> <cache usage="read-write"/> <key column="categoryId" not-null="true"/> <one-to-many class="org.qiujy.domain.cachedemo.Product"/> </set> </class> </hibernate-mapping>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值