ITEEDU

Hibernate Gossip: 简介快取(Session Level)

数据库每一次的查询都是一次不小的开销,例如连结的开启、执行查询指令,当数据库与应用服务器不在同一个服务器上时,还必须有远程调用、Socket的建立等开销,在Hibernate这样的ORM框架中,还有数据的封装等开销必须考虑进去。

快取(Cache)是数据库在内存中的临时容器,从数据库中读取的数据在快取中会有一份临时拷贝,当您查询某个数据时,会先在快取中寻找是否有相对应的 拷贝,如果有的话就直接返回数据,而无需连接数据库进行查询,只有在快取中找不到数据时,才从数据库中查询数据,藉由快取,可以提升应用程序读取数据时的 效能。

对于Hibernate这样的ORM框架来说,快取的机制更形重要,在Hibernate中快取分作两个层级:Session level与SessionFactory level(又称Second level快取)。

这边先介绍Session level的快取,在Hibernate中Session level快取会在使用主键加载数据或是延迟初始(Lazy Initialization) 时作用,Session level的快取随着Session建立时建立,而Session销毁时销毁。

Session会维护一个Map容器,并保留与目前Session发生关系的资料,当您透过主键来加载数据时,Session会先依据所要加载的类别与所给定的主键,看看Map中是否已有数据,如果有的话就返回,若没有就对数据库进行查询,并在加载数据后在Map中维护。

可以透过==来比较两个名称是否参考至同一个对象,以检验这个事实:
Session session = sessionFactory.openSession();
User user1 = (User) session.load(User.class, new Integer(1));
User user2 = (User) session.load(User.class, new Integer(1));
System.out.println(user1 == user2);
session.close();
第二次查询数据时,由于在快取中找到数据对象,于是直接返回,这与第一次查询到的数据对象是同一个实例,所以会显示true的结果。

可以透过evict()将某个对象从快取中移去,例如:
Session session = sessionFactory.openSession();
      User user1 = (User) session.load(User.class, new Integer(1));
      session.evict(user1);
      User user2 = (User) session.load(User.class, new Integer(1));
      System.out.println(user1 == user2);
      session.close();
由于user1所参考的对象被从快取中移去了,在下一次查询时,Session在Map容器中找不到对应的数据,于是重新查询数据库并再封装一个对象,所以user1与user2参考的是不同的对象,结果会显示false。

也可以使用clear()清除快取中的所有对象,例如:
Session session = sessionFactory.openSession();
      User user1 = (User) session.load(User.class, new Integer(1));
      session.clear();
      User user2 = (User) session.load(User.class, new Integer(1));
      System.out.println(user1 == user2);
      session.close();
同样的道理,这次也会显示false。

Session level的快取随着Session建立与销毁,看看下面这个程序片段:
Session session1 = sessionFactory.openSession(); 
      User user1 = (User) session1.load(User.class, new new Integer(1)); 
      session1.close(); 
      Session session2 =sessionFactory.openSession();
      User user2 = (User)session2.load(User.class, new Integer(1)); 
      session2.close(); 
      System.out.println(user1 == user2);
第一个Session在关闭后,快取也关闭了,在第二个Session的查询中并无法用到第一个Session的快取,两个Session阶段所查询到的并不是同一个对象,结果会显示false。

在加载大量数据时,Session level 快取的内容会太多,记得要自行执行clear()清除快取或是用evict()移去不使用对象,以释放快取所占据的资源。

Session在使用save()储存对象时,会将要储存的对象纳入Session level快取管理,在进行大量数据储存时,快取中的实例大量增加,最后会导致OutOfMemoryError,可以每隔一段时间使用Session的 flush()强制储存对象,并使用clear()清除快取,例如:
Session session = sessionFactory.openSession();
      Transaction tx = session.beginTransaction();
      while(....) { // 大量加载对象时的循环示意
          ....
          session.save(someObject);
          if(count % 100 == 0) { // 每100笔资料
              session.flush(); // 送入数据库
              session.clear(); // 清除快取
          }
      }
      tx.commit();
      session.close();
在SQL Server、Oracle等数据库中,可以在Hibernate设定文件中设定属性hibernate.jdbc.batch_size来控制每多少笔数据就送至数据库,例如:
....
      <hibernate-configuration>
          <session-factory>
              ....
              <property name="hibernate.jdbc.batch_size">100</property>
              ....
          </session-factory>
      <hibernate-configuration>
    
在MySQL中则不支持这个功能。