Hibernate缓存与集合性能分析

Hibernate作为一个应用级的数据访问层封装,只能在其作用范围内保持cache中数据的有效性,如果系统与第三方系统共享数据库的情况下,Hibernate的Cache机制可能失效。

Hibernate在本地JVM中维护了一个缓存池,并将从数据库获得的数据保存到池中以供下次重用。

外部系统的定义,并非限于本系统之外的第三方系统,即使在本系统中,如果出现了绕过Hibernate数据存储机制的其他数据存取手段,那么Cache的有效性也必须细加考量。

1. Cache分类

Hibernate中的Cache大致分为两层:

(1) Session级别缓存:在Session实现,属于事务级数据缓冲;一旦事务结束,这个Cache也就失效。此层为内置实现,无需进行干涉。

(2) 二级缓存:Hibernate中对其实力范围内的数据进行缓存的管理容器。

2. 缓存映射

配置缓存映射时通过设置类或集合映射的<cache>元素来设定的。如:

   
   
   
   
< cache usage ="transactional | read-write | nonstrict-read-write | read-only" region ="RegionName" include ="all | non-lazy" />

usage:必须。说明了缓存的策略;、

  ----read-only:如果只需要读取一个持久化类的实例,而无需修改,可以进行制度缓存,最简单也是最实用的方法,甚至在集群中,也能完美地运作;

  ----read-write:如果应用程序需要更新数据,实用读/写策略比较合适。但若要求“序列化事务”的隔离级别,则绝不能使用这种缓存策略;

                           如果想在集群环境中使用此策略,必须保证底层的缓存实现支持锁定(Locking),Hibernate内置的缓存策略并不支持锁定。

                           如果在JTA环境中使用缓存,那么必须指定hibernate.transaction.manager_lookup_class属性的值。

  ----nonstrict-read-write:非严格读/写缓存策略。如果只是偶尔需要更新数据,很少出现两个事务同时更新同一记录的情况,则可用此项。

                           如果在JTA环境中使用缓存,那么必须指定hibernate.transaction.manager_lookup_class属性的值。

  ----transactional:Hibernate的事务缓存策略提供了全事务的缓存支持。这样的缓存只能用于JTA环境中,需指定hibernate.transaction.manager_lookup_class属性。

region:可选。默认为类或集合的名字,指定第二级缓存的区域名;

include:可选。默认为all。当属性级延迟读取打开时,标记为lazy=”true”的实体的属性可能无法被缓存。

3. 管理缓存

一级缓存

在应用程序中,当给save()、update()、saveOrUpdate()方法传递一个对象或使用load()、get()、list()、iterate()或scroll()方法获得一个对象时,该对象都将被加入到Session的内部缓存中。

当flush()方法被调用时,对象的状态会和数据库取得同步。如果不希望此同步操作发生或者正处理大量对象、需要有效管理内存时,就可以调用evict()方法,从一级缓存中移除这些对象及其集合。

Session还提供了一个contains()方法,用来判断某个实例是否处于当前session的缓存中。如果要把所有的对象从session的缓存中彻底清除,则需要调用Session.clear()方法。

   
   
   
   
ScrollableResult cats = session.createQuery(“FROM Cat as cat”).scroll(); // 大量数据集 while (cats.next()){ Cat cat = (Cat)cats.get( 0 ); ... session.evict(cat); // 移除cat缓存 }

二级缓存

对于二级缓存来说,在SessionFactory中定义了许多方法,用于清除缓存中的实例、整个类、集合实例或者整个集合。

如:

   
   
   
   
sessionFactory.evict(Cat. class , catId); // 清除某个Cat sessionFactory.evict(Cat. class ); // 清除所有Cats sessionFactory.evictCollection( " Cat.kittens " , catId); // 清除某个kittens对象的集合 sessionFactory.evictCollection( " Cat.kittens " ); // 清除所有kittens对象的集合

4. 查询缓存

在Hibernate中查询的结果集也可以被缓存。只有当经常使用同样的参数进行查询时,才会有些用处。

要使用查询缓存,需要在配置文件中设置:

hibernate.cache.use_query_cache=true

该设置将会创建两个缓存区域:

① org.hibernate.cache.StandardQueryCache:用于保存查询结果集;

② org.hibernate.cache.UpdateTimestampsCache:用于保存最近查询的一系列表的时间戳;

需要注意的是在查询缓存中,它并不缓存结果集中所包含的实体的确切状态,只缓存这些实体的标识符属性的值以及各值类型的结果,所以查询缓存通常会和二级缓存一起使用。

绝大多数的查询并不能从查询缓存中受益,所以Hibernate默认是不进行查询缓存的。

如果需要进行查询缓存,需要调用Query.setCacheable(true)方法。这个调用会让查询在执行过程中先从缓存中查找结果,并将自己的结果集放到缓存中去。

如果要对查询缓存的失效政策进行精确地控制,必须调用Query.setCacheRegion()方法,为每个查询指定其命名的缓存区域。

如:

   
   
   
   
List cats = session.createQuery(“FROM Cat as cat WHERE cat.kittens = :kit”) .setEntity(“kittens”, kittens) .setMaxResults( 20 ) .setCacheable( true ) .setCacheRegion( " pages " ) .list();

若查询需要强行刷新其查询缓存区域,那么应该调用Query.setCacheMode(CacheMode.REFRESH)。

这对在其他进程中修改底层数据或对那些需要选择性更新特定查询结果集的情况特别有用。这是对SessionFactory.evictQueries()更为有效的替代方案,同样可以清除查询缓存区域。

CacheMode参数用于控制具体的Session如何与二级缓存进行交互,主要属性如下:

· CacheMode.NORMAL:从二级缓存中读、写数据;

· CacheMode.GET:从二级缓存中读取数据,仅在数据更新时对二级缓存写数据;

· CacheMode.PUT:仅向二级缓存写数据,但不从二级缓存中读取数据;

· CacheMode.REFRESH:仅向二级缓存写数据,但不从二级缓存中读数据。通过hibernate.cache.use_minimal_puts的设置,强制二级缓存从数据库中读取数据、刷新缓存内容。

5. 提升集合性能

在Hibernate中定义了三种基本类型的集合,分别是值数据集合、一对多关联和多对多关联。

这个分类区分了不同的表和外键关系类型,如果要了解关系模型的所有内容,必须同时考虑”用于Hibernate更新或删除集合行数据的主键结构”,因此得到以下分类:

· 有序集合类

· 集合(sets)

· 包(bags)

所有的有序集合类(maps/lists/arrays)都拥有一个由<key>和<index>组成的主键。

这种情况下集合类的更新时非常高效的——主键已经被有效地索引,因此当Hibernate试图更新或删除一行时,可以迅速找到该行数据。

集合(sets)的主键由<key>和其他元素字段构成。对于有些元素类型来说,效率很低,特别是组合元素或大文本、大二进制字段,数据库可能无法有效地对复杂的主键进行索引。

另一方面,对于一对多、多对多关联,特别是合成的标识符来说,集合也可以达到同样的高效性能。

<idbag>映射定义了代理键,因此它总是可以很高效地被更新。事实上,<idbag>拥有着最好的性能表现,Bag的效率是最差的,因为bag允许重复的元素值,也没有索引字段,因此不可能定义主键。

Hibernate无法判断重复的行,当这种集合被更改时,Hibernate将会先完整地移除整个集合,然后再重新创建整个集合,因此Bag是非常低效的。

需要注意的是,对于一对多关联来说,主键很可能并不是数据库表的物理主键,但在此情况下,上面的分类仍然有用。

6. Lists、Maps、Sets更新性能分析

对于多对多关联、值数据集合而言,有序集合类比集合(set)有一个好处,因为Set的内在结构,如果改变了一个元素,Hibernate并不会更新这一行。

对于Set来说,只有在插入和删除操作时“改变”才有效。

需要注意的是,数组无法延迟载入。

大概结论:list、map和idbags市最高效的(非反向)集合类型,set则紧随其后。

在Hibernate中因为set的语义在关系模型中是最自然的,所以set是最通用的集合类型。

在设计良好的Hibernate领域模型中,通常可以看到更多的集合,事实上是带有inverse="true”的一对多的关联。对于这些关联,更新操作会在多对一的这一端进行处理,因此对于此类情况,无需考虑其集合的更新性能。

7. bag和list在反向集合类中的性能分析

对于指明了inverse="true”的集合类(如标准的双向的一对多关联),可以在未初始化(fetch)包元素的情况下直接向bag或list添加新元素。

这是因为Collection.add或者addAll方法对bag或list总是返回true(这点与Set不同)。

如:

   
   
   
   
Parent p = (Parent)session.load(Parent. class , id); Child c = new Child(); c.setParent(p); p.getChildren().add(c); // 这里不会返回整个集合 session.flush();

你可能感兴趣的:(Hibernate缓存与集合性能分析)