hibernate緩存的使用

緩存的使用
我們先來模擬一個緩存的機制
以查詢學生爲示例:
public class Test {

public static void main(String[] args) throws Exception {
   MyClassDao myClassDao = new MyClassDao();
   StudentDao studentDao = new StudentDao();
  
   Student student = studentDao.findById("4028810027d8be080127d8be0d790002");
   System.out.println(student.getName());
   Student student2 = studentDao.findById("4028810027d8be080127d8be0d790002");
   System.out.println(student2.getName());
}

可以看到控制檯輸出:

hibernate緩存的使用 - ayoov_design - ayoov_design的博客

很明顯的看到執行了兩條SQL語句,感覺有點浪費資源,如果我們把第一次查詢的結果保存起來,當第二次查詢的時候,先看保存了沒有,如果有,直接用,如果沒有,則再查詢數據庫.這樣就更好一些.

修改StudentDao層,給Dao類增加一個模擬的緩存集合
public class StudentDao {
public static Map<String, Student> cache = new HashMap<String, Student>();

public void create(Student student) throws Exception {
   Session session = null;
   Transaction transaction = null;
   try {
    session = HibernateUtil.getSession();
    transaction = session.getTransaction();
    transaction.begin();
    session.save(student);
    transaction.commit();
   } catch (Exception e) {
    transaction.rollback();
    throw e;
   }
}

public void delete(Student student) throws Exception {
   Session session = null;
   Transaction transaction = null;
   try {
    session = HibernateUtil.getSession();
    transaction = session.getTransaction();
    transaction.begin();
    session.delete(student);
    transaction.commit();
   } catch (Exception e) {
    transaction.rollback();
    throw e;
   }
}

public Student findById(Serializable id) throws Exception {
   Session session = null;
   Transaction transaction = null;
   Student student = null;

   String key = Student.class.getName() + id;
   student = cache.get(key);
   if (student != null) {
    return student;
   }
   try {
    session = HibernateUtil.getSession();
    transaction = session.getTransaction();
    transaction.begin();
    student = (Student) session.get(Student.class, id);
    cache.put(key, student);
    transaction.commit();
   } catch (Exception e) {
    transaction.rollback();
    throw e;
   }
   return student;
}

再一次執行Test的main方法,可以看到結果如下:

hibernate緩存的使用 - ayoov_design - ayoov_design的博客

只執行了一次查詢,這就是緩存的一個作用,但是緩存機制遠遠不會這麼簡單,想一下,如果在兩次查詢之間,對象更新了怎麼辦,這就涉及到緩存的更新.我們暫時只需要瞭解一下緩存的機制與簡單的實現.因爲這一塊已經有現成的包和類直接用,不需要我們再從頭開發.

緩存的作用主要是用來提高性能,可以簡單的理解成一個Map,使用緩存涉及到三個操作:
把數據放入緩存
從緩存中獲取數據
刪除緩存中無效數據

一級緩存: Session級共享,此緩存只能在session關閉之前使用.
save, update, saveOrUpdate, get, list, iterate, lock這些方法都會將對象放在一級緩存中, 一級緩存不能控制緩存的數量, 所以要注意大批量操作數據時可能造成內在溢出, 可以用evict, clear方法清除緩存中的內容.後面講到Hibernate批量增加數據時再演示.
看下面的代碼:
public Student findById(Serializable id) throws Exception {
   Session session = null;
   Transaction transaction = null;
   Student student = null;

   try {
    session = HibernateUtil.getSession();
    transaction = session.getTransaction();
    transaction.begin();
    student = (Student) session.get(Student.class, id);
    System.out.println(student.getName());
    Student student1 = (Student) session.get(Student.class, id);
    System.out.println(student1.getName());
    transaction.commit();
   } catch (Exception e) {
    transaction.rollback();
    throw e;
   }
   return student;

可以看到上面的代碼中,只查詢了一次數據庫,但是緩存的作用域太小,沒有太大的作用.

二級緩存: SessionFactory級共享
實現爲可插拔,通過修改兩個開關來改變.
告訴Hibernate,我現在要使用緩存機制:
<property name="hibernate.cache.use_second_level_cache">true</property>
配置二級緩存的提供者,也就是具體廠商提供的實現類<property name="cache.provider_class">

緩存的配置,可以在Hibernate源碼包下查找hibernate.properties這個文件,找到Second-level Cache這一塊,可以看到所有的配置項

可以看到緩存的實現有很多種,那麼怎麼選擇呢?
#hibernate.cache.provider_class org.hibernate.cache.EhCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.EmptyCacheProvider
hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.TreeCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.OSCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider

這需要看你的jar包中有哪個對應的緩存實現包,我們可以看到我們的Hibernate庫下

hibernate緩存的使用 - ayoov_design - ayoov_design的博客

可以看到有一個ehcache-1.2.3.jar包,那麼這個時候,我們可以選擇使用org.hibernate.cache.EhCacheProvider這個實現類,
我們可以在我們的Hibernate.cfg.xml中加入下面的配置項.
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>

接下來,我們還需要告訴Hibernate哪些類需要緩存,還需要在Hibernate.cfg.xml中加入下面的配置項.
<class-cache usage="read-only" class="chapter1.model.Student"/> 
usage="read-only"指緩存的策略,有以下幾種策略:

hibernate緩存的使用 - ayoov_design - ayoov_design的博客

read-only是不允許修改緩存,效率最高,通常用到像新聞的分類啊,省市聯動等等一些不會改變的信息.
像經常需要改動的,可以使用read-write策略,此策略是線程同步
nonstrict-read-write策略和上面一樣,但是線程不同步.
transactional事務策略,如果產生錯誤,緩存會回滾,實現非常複雜.

還有一種方式配置告訴Hibernate哪些類需要緩存, 不需要在Hibernate.cfg.xml配置,直接在Student.hbm.xml中配置即可(推薦方法)
<class name="chapter1.model.Student" table="students">
   <cache usage="read-only" />
   <id name="id" type="java.lang.String">
    <column name="id" length="32" />
    <generator class="uuid.hex" />
   </id>
   <property name="name" type="java.lang.String">
    <column name="name" length="20" />
   </property>
   <many-to-one name="myClass" class="chapter1.model.MyClass">
    <column name="student_id" length="32" />
   </many-to-one>
</class> 
在id前面加上<cache usage="read-only" />, 這就不需要告訴類名了.再次執行Test類的main方法,可以看到只執行了一條SQL語句

Query的查詢緩存
首先打開
<property name="hibernate.cache.use_query_cache">true</property>

修改StudentDao層,添加一個findAll()方法
public List<Student> findAll() throws Exception {
   Session session = null;
   Transaction transaction = null;
   List<Student> students = null;

   try {
    session = HibernateUtil.getSession();
    transaction = session.getTransaction();
    transaction.begin();
    Query query = session.createQuery("from Student");
    query.setCacheable(true);
    students = query.list();
    transaction.commit();
   } catch (Exception e) {
    transaction.rollback();
    throw e;
   }
   return students;

注意這裏有一句query.setCacheable(true);//設置緩存開關,使其起作用
再來執行Test的main方法:
public class Test {
public static void main(String[] args) throws Exception {
   MyClassDao myClassDao = new MyClassDao();
   StudentDao studentDao = new StudentDao(); 
   List<Student> students1 = studentDao.findAll();
   List<Student> students2 = studentDao.findAll();  
}

可以看到只執行了一句select語句.

查詢的擊中
執行下面的代碼:
List<Student> students1 = studentDao.findAll();
System.out.println("查詢所有學生");
for (Student s : students1) {
System.out.println(s.getName());
}
  
Student student = studentDao.findById("4028810027d8be080127d8be0d790003");
System.out.println(student.getName()); 
可以看到只執行了一條SQL語句, studentDao.findById("4028810027d8be080127d8be0d790003");這句代碼的查詢並沒有執行.當Hibernate執行完了Query query = session.createQuery("from Student")類似於這種查詢以後,會將查詢結果存放起來,Hibernate存放的時候會這樣存放map.put(“複雜的key值”, 對象的Id),所以當你執行按ID查詢時,就會先從緩存中查詢,這時就會被命中了.

使用緩存時需要注意的幾點地方:
讀取次數大於修改次數,即不是經常修改的數據進行緩存
緩存的容量不能大於內存
對數據要有獨享的控制權,即只能通過我這裏操作數據,不能從其它通道操作數據
可以容忍無效的數據出現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章