hibernate的内部缓存
hibernate的缓存分为两级:一级缓存(session级)和二级缓存(sessionFactory级)
缓存的作用主要用来提高性能,可以简单的理解成一个Map;使用缓存涉及到三个操作:把数据放入缓存,从缓存中获取数据,删除缓存中的无效数据。
一级缓存,Session级共享。
save,update,saveOrUpdate,load,get,list,iterate,lock这些方法都会将对象放在一级缓存中。一级缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出;可以用evict,clear方法清除缓存中的内容。
例:
Person p = (Person)s.get(Person.class, id); Person p = (Person)s.get(Person.class, id);
连续两次执行该语句,sql语句的select查询只有一句输出。说明其中一次查询是到缓存中拿数据的。
query不能够从缓存中取数据。从缓存中取数据的方法不多。把数据放到缓存中的方法有很多。
static void query(int id) { Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); Worker worker = (Worker) s.get(Person.class, id); Worker worker2 = (Worker) s.get(Worker.class, id); tx.commit(); System.out.println(worker.getWork_year()); }
运行以上代码,执行的sql语句为:
执行了两次s.get(),但只有一条select语句,说明第二次是从缓存中取的数据。
session的evict方法用于清除对应的缓存对象。session的clear方法清除整个session缓存。
一级缓存的缺陷: 对缓存中的数据量没有限制。数据量的不断累积,造成内存溢出。 没有什么保护,共享的范围较小。缓存的时间较短暂。一般存在于一次请求中。
二级缓存,sessionFactory级共享
hibernate的二级缓存交给了第三方的缓存框架来处理。
打开hibernate的二级缓存,在hibernate配置文件中配置。不过默认二级缓存是打开的:
<property name="hibernate.cache.use_second_level_cache">true</property>
实现为可插拔,通过修改cache.provider_class参数来改变;
缓存提供者配置,一般是第三方缓存框架:
<property name="hibernate.cache.provider_class">org.hibernate.cache.OSCacheProvider</property>
在配置前要确认使用的缓存的jar包应该导入到项目中了。
还要配置需要缓存的类:方法有两种。
①在hibernate配置文件中指定:
<class-cache class="hibernatetest.User" include="all" usage="read-only"/>
类必须是全限定名。usage表示缓存的策略,有四个值可选。
注意: The content of element type "session-factory" must match "(property*,mapping*,(class-cache|collection-cache)*,event*,listener*)" 缓存类的配置必须在maping文件配置的后面,否则会报错。
②在映射文件中指定:
<hibernate-mapping package="hibernatetest">
<class name="User" table="`user`">
<cache usage="read-only"/>
<id name="id">
<generator class="hilo"/>
</id>
.......
hibernate内置了对EhCache,OSCache,TreeCache,SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入Hibernate不支持的缓存实现。
验证:
public class Main {
static User query(int id) {
User user = null;
Session session = null;
try {
session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
user = (User) session.get(User.class, id);
System.out.println(user.getUserName());
user = (User) session.get(User.class, id);
tx.commit();
session.clear();
} finally {
if (session != null) {
session.close();
}
}
Session s = null;
try {
s = HibernateUtil.getSession();
Transaction txn = s.beginTransaction();
user = (User) s.get(User.class, id);
txn.commit();
} finally {
if (s != null) {
s.close();
}
}
return user;
}
public static void saveUser(User user) {
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
Name name = new Name();
name.setFirstName("firstName");
name.setLastName("lastName");
user.setUserName(name);
session.save(user);
tx.commit();
session.close();
}
public static void main(String[] args) {
User user = new User();
user.setBirthday(new Date());
saveUser(user);
query(1);
Statistics st = HibernateUtil.getSessionFactory().getStatistics();
System.out.println("put:"+st.getSecondLevelCachePutCount());
System.out.println("hit:"+st.getSecondLevelCacheHitCount());
System.out.println("miss:"+st.getSecondLevelCacheMissCount());
System.out.println(st.getConnectCount());
}
使用信息统计之前要在hibernate配置文件中配置:
<property name="hibernate.generate_statistics">true</property>
不设置缓存会打印输出sql语句。我设置了缓存,却无法查询了,连一条select语句也没有。sql语句为:
Hibernate: insert into user (first_name, last_name, birthday, id) values (?, ?, ?, ?)
hibernatetest.Name@12a55aa
put:1
hit:2
miss:0
3
后来把user的主键生成方式改为native(一开始使用的hilo)
输出的sql语句为:
Hibernate: insert into user (first_name, last_name, birthday) values (?, ?, ?)
Hibernate: select user0_.id as id0_0_, user0_.first_name as first2_0_0_, user0_.last_name as last3_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
hibernatetest.Name@e0cc23
put:1
hit:1
miss:1
3
两次的结果不一样。这跟主键的生成方式有关。
分析:
session的save(这个方法不适合native生成方式的主键 )
对于native的主键生成方式而言,在程序中保存对象时,并没有把数据放到二级缓存中去。hibernate在查询数据时,先到session中,即一级缓存中查找,如果有就返回。如果没有找到,则到二级缓存中去找,如果找到则返回。如果还是没有找到,才会建立一条连接到数据库中去查询。在第一次查询时,两个缓存中都没有,所以会有一次miss。在通过建立连接获得数据时会在一级和二级中都进行缓存数据。所以打印出put:1 ,然后把session关闭后,一级缓存没有了。再次查询,虽然一级缓存没有相应的数据,但能够最终在二级缓存中获得数据,所以达到了一次命中,所以打印出了hit:1.
不过使用高低键的主键生成方式,会在save的时候,就会把数据放到缓存中去了。所以会有一次put。所以在每次查询时都能够在二级缓存中找到数据,所以命中了2次,打印出hit:2,miss:0。
结果也显示连接的次数为3次,我们的程序中获得3次session。
总结:
session的save(这个方法不适合native生成方式的主键)
update,saveOrUpdate,list,iterator,get,load以及Query,Criteria都会填充二级缓存,但只有(没打开查询缓存时)Session的iterator,get,load会从二级缓存中取数据(iterator可能存在N+1次查询)。
Query,Criteria(查询缓存)由于命中率较低,因为要查询语句一样,才会命中,这一般不常见。所以hibernate缺省是关闭;修改cache.use_query_cache为true打开对查询的缓存,并且调用query.setCacheable(true)或criteria。setCacheable(true)。
SessionFactory中提供了evictXXX()方法用来清除缓存中的内容。
统计信息打开generate_statistics,用sessionFactory.getStatistics()获取统计信息。
有时间可以去研究一下分布式缓存和中央缓存。
使用缓存的条件
1,读取大于修改
2,数据量不能超过内存容量
3,对数据要有独享的控制
4,可以容忍出现无效数据