hibernate学习之第十四篇

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语句为:

Hibernate: select person0_.id as id8_0_, person0_.name as name8_0_, person0_.age as age8_0_, person0_.work_year as work1_9_0_, person0_.farm_name as farm1_10_0_, person0_.clazz_ as clazz_0_ from ( select null as work_year, id, age, name, null as farm_name, 0 as clazz_ from person union select work_year, id, age, name, null as farm_name, 1 as clazz_ from worker union select null as work_year, id, age, name, farm_name, 2 as clazz_ from farmer ) person0_ where person0_.id=?
 



执行了两次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,可以容忍出现无效数据

你可能感兴趣的:(sql,Hibernate,框架,cache)