第十二章 hibernate缓存
1、一级缓存(Session级缓存)
一级缓存很短和session的生命周期一致,因此也叫session级缓存或事务级缓存
那些方法支持一级缓存:
*get()
*load()
*iterate(查询实体对象)
如何管理一级缓存:
*session.clear(),session.evict()
如何避免一次性大量的实体数据入库导致内存溢出
方法1:先flush,再clear
for(int i = 0; i <1000000; i++){ session.save(user); if(i % 20 == 0){ session.flush(); session.clear(); } }
方法2:用StatelessSession接口
Hibernate提供了基于命令的API,可以用detachedobject(分离对象)的形式把数据以流的方法加入到数据库,或从数据库输出。StatelessSession没有持久化上下文,也不提供多少高层的生命周期语义。特别是,无状态session不实现第一级cache,也不和第二级缓存,或者查询缓存交互。用StatelessSession进行的操作甚至不级联到关联实例。
无状态session是低层的抽象,和低层JDBC相当接近。
StatelessSession statelessSession = sessionFactory.openStatelessSession(); statelessSession.insert(user);
方法3:批量更新
session.beginTransaction(); Queryquery = session.createQuery("updateUser as u set u.password=:p"); query.setString("p","000000"); query.executeUpdate(); session.getTransaction().commit();
如果数据量特别大,考虑采用jdbc实现,如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具
2、二级缓存
Hibernate默认的二级缓存是开启的。
二级缓存也称为进程级的缓存,也可称为SessionFactory级的缓存(因为SessionFactory可以管理二级缓存),它与session级缓存不一样,一级缓存只要session关闭缓存就不存在了。而二级缓存则只要进程在二级缓存就可用。
二级缓存可以被所有的session共享
二级缓存的生命周期和SessionFactory的生命周期一样,SessionFactory可以管理二级缓存
二级缓存同session级缓存一样,只缓存实体对象,普通属性的查询不会缓存
二级缓存一般使用第三方的产品,如EHCache
2.1常见缓存提供商
Cache |
Providerclass |
Type |
ClusterSafe |
QueryCache Supported |
Hashtable(not intended for production use) |
org.hibernate.cache.HashtableCacheProvider |
memory |
|
yes |
EHCache |
org.hibernate.cache.EhCacheProvider |
memory,disk |
|
yes |
OSCache |
org.hibernate.cache.OSCacheProvider |
memory,disk |
|
yes |
SwarmCache |
org.hibernate.cache.SwarmCacheProvider |
clustered(ip multicast) |
yes(clustered invalidation) |
|
JBossTreeCache |
org.hibernate.cache.TreeCacheProvider |
clustered(ip multicast), transactional |
yes(replication) |
yes(clock sync req.) |
2.2二级缓存的用法
这里以常见的EHCache为例
(1)在hibernate.cfg.xml中开启二级缓存。
设置启用二级缓存:
<propertyname="hibernate.cache.use_second_level_cache">true</property>
设置二级缓存的实现类(缓存提供商):
<propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider </property>
(2)导入所使用的二级缓存JAR包
ehcache-1.2.3.jar、commons-logging.jar、commons-logging-1.0.4.jar
(3)src下写缓存配置文件:ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <diskStore path="C:\\cache" /> <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskPersistent="false" /> </ehcache>
maxElementsInMemory属性用于指定缓存中最多可放多少个对象。
overflowToDisk当内存中缓存的记录达到maxElementsInMemory时是否被持久化到硬盘中。保存路径由diskStore决定的
eternal属性指定缓存是否永久有效。
timeToIdleSeconds属性指定缓存多久未被使用便清理掉。
timeToLiveSeconds属性指定缓存的生命长度。
diskPersistent属性指定缓存是否被持久化到硬盘中,保存路径由diskStore标签指定。
(4)设置所有缓存的实体类
a、hibernate.cfg.xml中设置
<class-cacheclass="cn.ineeke.entity.User"usage="read-only"/>
b、*.hbm.xml中设置
<hibernate-mapping> <class name="cn.framelife.hibernate.entity.User" table="user" catalog="hibernate"> <cache usage="read-only"/> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator class="native" /> </id> <property name="username" type="java.lang.String"> <column name="username" length="45" not-null="true" /> </property> <property name="password" type="java.lang.String"> <column name="password" length="45" not-null="true" /> </property> </class> </hibernate-mapping>
c、Annotation方式
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
@Table(name = "user", catalog = "hibernate")
public class User implements java.io.Serializable {}
d、缓存策略
只读缓存(read-only):没有什么好说的 ,最常用也最简单的。
读/写缓存(read-write):程序可能要的更新数据
不严格的读/写缓存(nonstrict-read-write):需要更新数据,但是两个事务更新同一条记录的可能性很小,性能比读写缓存好
事务缓存(transactional):缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境。
读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁,其他事务如果去取相应的缓存数据,发现被锁住了,然后就直接取数据库查询。
在hibernate2.1的ehcache实现中,如果锁住部分缓存的事务发生了异常,那么缓存会一直被锁住,直到60秒后超时。
不严格读写缓存不锁定缓存中的数据。
(5)测试
测试代码:
List<User> uesrs = session.createQuery("from User").list(); System.out.println("--------------"); Session s2 = sessionFactory.openSession(); User user = (User) s2.get(User.class, 2); System.out.println(user.getUsername());
结果:
Hibernate: select user0_.id as id0_, user0_.first_name as first2_0_, user0_.last_name as last3_0_, user0_.password as password0_, user0_.username as username0_ from hibernate.user user0_ -------------- zhangsan
2.3打开二级缓存统计信息
List<User> uesrs = session.createQuery("from User").list(); System.out.println("--------------"); Session s2 = sessionFactory.openSession(); User user = (User) s2.get(User.class, 2); System.out.println(user.getUsername()); Statistics st = sessionFactory.getStatistics(); System.out.println(st); System.out.println(st.getSecondLevelCacheStatistics("cn.framelife.hibernate.entity.User").getEntries());
3、查询缓存
hibernate的查询缓存是主要是针对普通属性结果集的缓存,而对于实体对象的结果集只缓存id。在一级缓存,二级缓存和查询缓存都打开的情况下作查询操作时这样的:查询普通属性,会先到查询缓存中取,如果没有,则查询数据库;查询实体(对象),会先到查询缓存中取id,如果有,则根据id到缓存(一级/二级)中取实体(对象),如果缓存中取不到实体,再查询数据库。
查询缓存的生命周期,是不确定的,当前关联的表发生改变时,查询缓存的生命周期结束。
配置和使用:
查询缓存的配置和使用也是很简单的:
1>查询缓存的启用不但要在配置文件中进行配置
<propertyname="hibernate.cache.use_query_cache">true</property>
2>还要在程序中显示的进行启用
query.setCacheable(true);
测试:
Query query = session.createQuery("select u.username from User u where u.id > 210"); //query.setCacheable(true); List<String> names = query.list(); for (String name : names) { System.out.println(name); } System.out.println("================================"); query = session.createQuery("select u.username from User u where u.id > 210"); //query.setCacheable(true); names = query.list(); for (String name : names) { System.out.println(name); }
没开启查询缓存(query.setCacheable(false))时的结果:
Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210 abcd 1111 11111 ================================ Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210 abcd 1111 11111
一模一样的查询,执行了两次SQL。
开启查询缓存(query.setCacheable(true))时的结果:
Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210 abcd 1111 11111 ================================ abcd 1111 11111
开启查询缓存后,一模一样两次的查询,只需要执行一次sql。
注意:
当只是用Hibernate查询缓存,而关闭二级缓存的时候:
第一:如果查询的是部分属性结果集:
那么当第二次查询的时候就不会发出SQL直接从Hibernate查询缓存中取数据
第二:如果查询的是实体结果集eg(fromStudent) 这个HQL那么查询出来的实体,首先Hibernate查询缓存存放实体的ID,
第二次查询的时候就到Hibernate查询缓存中取出ID一条一条的到数据库查询这样 将发出N条SQL造成了SQL泛滥
在只打开查询缓存,关闭二级缓存的情况下,不要去查询实体对象。这样会造成很大的资源浪费。
当都开启Hibernate查询缓存和二级缓存的时候
第一:如果查询的是部分属性结果集:这个和上面只是用Hibernate查询缓存而关闭 二级缓存的时候,一致 因为不涉及实体不会用到二级缓存
第二:如果查询的是实体结果集eg(fromStudent) 这个HQL那么查询出来的实体,首先Hibernate查询缓存存放实体的ID,第二次查询,的时候就到Hibernate查询缓存中取出ID,拿到二级缓存区找数据,如果有数据就不会发出SQL如果都有一条SQL都不会发出直接从二级缓存中取数据
例子:
/** * 开启查询缓存,开启二级缓存, 开启两个session,分别调用query.list查询实体对象 */ // 如果不用查询缓存的话,那两个都发出查询语句,这也是默认的情况. try { session = sessionFactory.openSession(); t = session.beginTransaction(); Query query = session.createQuery("from User as u where u.id < 90"); // 启用查询缓存 query.setCacheable(true); List<User> list = query.list(); for (User user : list) { System.out.println(user.getUsername()); } t.commit(); } catch (Exception e) { e.printStackTrace(); t.rollback(); } finally { session.close(); } System.out.println("================================"); try { session = sessionFactory.openSession(); t = session.beginTransaction(); Query query = session.createQuery("from User as u where u.id < 90"); // 启用查询缓存 query.setCacheable(true); // 不会发出查询语句,因为这种情况下,查询过程是这样的: // 在第一次执行list时,会把查询对象的id缓存到查询缓存里 // 第二次执行list时, 会遍历查询缓存里的id到缓存里去找实体对象,由于这里开启了二级缓存,可以找到目标实体对象, // 所以就不会再发出n条查询语句. List<User> list = query.list(); for (User user : list) { System.out.println(user.getUsername()); } t.commit(); } catch (Exception e) { e.printStackTrace(); t.rollback(); } finally { session.close(); }
4集群缓存
集群中的每一台机子上都有缓存。修改的成本高。
5中央缓存
服务中使用一台独立的机器作为缓存。获取数据的成本高,修改的成本低。
6使用缓存的条件
1、服务中读取数据多于修改数据。2、数据量不能大于内存容量。
3、对数据要有独享的控制。
4、可以容忍无效数据。
7注意事项
不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。
在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧。