1. 程式人生 > >hibernate--一級和二級快取(使用Ehcache)以及查詢快取

hibernate--一級和二級快取(使用Ehcache)以及查詢快取

有一下幾點需要理清才行:

一級快取是session快取 session關閉就小時

二級快取是sessionFactory級別的快取 一個應用程式只有一個 多個執行緒共享  不要把經常修改的物件放到二級快取中 二級快取中放一些查詢的物件

1 首先是在hibernate,cfg.xml檔案中進行配置:

新增下列配置項

                <property name="hibernate.cache.use_second_level_cache">true</property>
		<!-- 使用哪種快取提供的類 哪種快取 -->
		<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
		<!-- 使用查詢快取-->
		<property name="hibernate.cache.use_query_cache">true</property>
		<!-- ehcache.xml的配置檔案路徑 -->
		<property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
在需要使用二級快取的物件配置檔案上:
	<class name="Student" table="t_stu">
		<!--  <cache usage="read-only" /> -->
		<id name="id">
			<generator class="native"></generator>
		</id>
		<!-- 注意:version 一定要加在ID後面 property前面 -->
		<version name="version" />
		<property name="name" />
		<property name="sex" />
		<many-to-one name="classroom" column="cid" fetch="join" />
	</class>
ehcache.xml中的配置:
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

<!-- 每一個獨立的cache可以單獨為不同的物件進行設定 如果找不到就用預設的cache-->
    <cache name="com.itany.model.Student"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />



2 N+1問題

 如果使用iterator返回列表 對於hibernate而言 
         它僅僅是查詢了id列表的SQL 在進行iterator迭代的時候 
         再會根據id一個個去資料庫查詢具體物件 因此發出多條SQL 這就是典型的N+1問題 避免它
         就是不使用iterator iterator存在的原因 
         使用的地方:
         因為有可能需要查詢2次 第一次list全部查詢出來 存在二級快取中 第二次 用Iterator資料庫查id,再根據ID從二級快取的物件中查詢更快

            session = HibernateUtil.openSession();
            Iterator<Student> it = session.createQuery("from Student").setFirstResult(0).setMaxResults(12).iterate();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
3  重要:二級快取快取的是物件  查詢快取快取的是ID 這是兩種不同的快取  誰也不依賴誰   查詢快取也是sessionFactory級別的快取

     查詢快取是針對HQL語句的快取  查詢緩只快取ID 而不會快取物件
     1 但是使用查詢快取最好開啟二級快取 因為在查詢快取中查到ID後可以直接去
     二級快取中查詢 不然的話又是N+1問題 多次根據查詢快取中的ID去資料庫查詢
     2 若打開了二級快取 但是沒有開啟查詢快取(HQL不一致、引數不一致、查詢快取沒開、
     setCacheable(true)沒寫等原因)那麼還是會直接從資料庫中查詢一次、
     因為需要藉助查詢快取查ID再到二級快取中查物件
     3 注意:load方式可以直接從二級快取中查物件 不必藉助查詢快取
     4如果取得只是某些屬性(而不是完整物件) 那麼不會進行二級快取見test04
     5 查詢快取 工作順序:如果正常開啟了查詢快取(HQL完全一致 且set(True)),先去查詢快取中查ID
          再根據查詢到的ID去二級快取中查物件 若此時二級快取打開了 那麼不會發SQL
         若二級快取沒開啟  那麼此時會再次根據查到的ID去資料庫中查詢

4 程式碼中使用查詢快取

 List<Object[]> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%張%").list();//開啟查詢快取  查詢快取也是sessionFactory級別的快取
5 此時關閉了二級快取(存物件)
    打開了查詢快取(存ID) 第二個人查詢快取只查到ID 會根據ID 去二級快取查物件  但是沒有  所以去資料庫查  發出大量SQL

6 查詢快取很少使用 因為很少HQL完全一樣
          因為只要HQL語句不一樣 就不會開啟查詢快取 
          只有兩個完全一樣的 並且引數都一樣的 才能使用查詢快取

public class TestSecondCache
{
    /*
     * 重要:二級快取快取的是物件  查詢快取快取的是ID 這是兩種不同的快取
     * 誰也不依賴誰  
     * 1 但是使用查詢快取最好開啟二級快取 因為在查詢快取中查到ID後可以直接去
     * 二級快取中查詢 不然的話又是N+1問題 多次根據查詢快取中的ID去資料庫查詢
     * 2 若打開了二級快取 但是沒有開啟查詢快取(HQL不一致、引數不一致、查詢快取沒開、
     * setCacheable(true)沒寫等原因)那麼還是會直接從資料庫中查詢一次、
     * 因為需要藉助查詢快取查ID再到二級快取中查物件
     * 3 注意:load方式可以直接從二級快取中查物件 不必藉助查詢快取
     * 
     * 如果取得只是某些屬性(而不是完整物件) 那麼不會進行二級快取見test04
     */
    
    @Test
    public void test01()
    {
        /*
         * 不可以快取 Ehcache是看SQL是否一樣
         */
        Session session = null;
        try
        {
            session = HibernateUtil.openSession();
            Student s=(Student)session.load(Student.class,1);
            System.out.println(s.getName());
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        Session session2 = null;
        /*
         * 此時session已經關閉 但再次查詢 仍然可以不發SQL  二級快取起作用了
         */
        try
        {
            session2 = HibernateUtil.openSession();
            Student s2=(Student)session2.load(Student.class,1);//這種寫法 根據ID去二級快取中查詢
            System.out.println(s2.getName());
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session2)
                HibernateUtil.closeSession(session2);
        }
    }
    @Test
    public void test02()
    {
        /*
         * 
         */
        Session session = null;
        try
        {
            session = HibernateUtil.openSession();
            Student s=(Student)session.load(Student.class,1);
            System.out.println(s.getName());
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        Session session2 = null;
        Transaction trans=null;
        /*
         * 會報錯 因為二級快取 在Student上設定的是 read-only 因此不能寫入
         * 最好不要設定 read write因為hibernate要頻繁的加鎖 解鎖 影響效率 還不如不用二級快取
         */
        try
        {
            session2 = HibernateUtil.openSession();
            trans=session2.beginTransaction();
            Student s2=(Student)session2.load(Student.class,1);
            s2.setName("zhangasnsss");
            System.out.println(s2.getName());
            trans.commit();
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session2)
                HibernateUtil.closeSession(session2);
        }
    }
    @Test
    public void test03()
    {
        /*
         * 
         */
        Session session = null;
        Transaction trans = null;
        try
        {
            session = HibernateUtil.openSession();
            List<Student> ls = session.createQuery("from Student").list();
            Iterator<Student> it = ls.iterator();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        
        try
        {
            session = HibernateUtil.openSession();
            Student s=(Student)session.load(Student.class,1);
            System.out.println(s.getName());
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
    @Test
    public void test04()
    {
        /*
         * 
         */
        Session session = null;
        Transaction trans = null;
        try
        {
            session = HibernateUtil.openSession();
            List<Object[]> ls = session.createQuery("select stu.id,stu.name from Student stu").list();
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        /*
         * 以上程式碼僅僅去了id和name 而二級快取是快取物件的 所以上一段程式碼不會使用二級快取
         * 此時就會發出相應的SQL
         */
        try
        {
            session = HibernateUtil.openSession();
            Student s=(Student)session.load(Student.class,1);
            System.out.println(s.getName());
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
    @Test
    public void test05()
    {
        /*
         * iterator 作用就是配合二級快取使用 最好
         */
        Session session = null;
        Transaction trans = null;
        try
        {   //一條SQL
            session = HibernateUtil.openSession();
            List<Object[]> ls = session.createQuery("select stu  from Student stu").list();
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        /* 1條SQL
         * 由於學生的物件經過上面查詢物件之後 已經存在於二級快取之中
         * 此時正好直接使用iterator  首先會查詢通過1條取ID的語句  然後再獲取物件時候去二級快取中查詢
         * 有的話 直接用 不會產生N+1問題
         * 
         *    若沒二級快取 我們直接使用iterator會首先產生一條查ID的語句
         *    再在獲取物件的時候再去資料庫中取 那麼這是就會產生N+1問題
         */
        try
        {
            session = HibernateUtil.openSession();
            Iterator<Student> it = session.createQuery("from Student").iterate();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
    @Test
    public void test06()
    {
        /*
         * 與上面一個幾乎一樣 區別在於iterator換成list() 
         * 雖然第一次已經二級快取了  但是不能決定我再去不去資料庫中查詢
         * 
         * 重要 :如果希望第二次不發SQL 那就要使用查詢快取
         * 查詢快取是針對HQL語句的快取  查詢緩只快取ID 而不會快取物件
         */
        Session session = null;
        
        Transaction trans = null;
        try
        {   //一條SQL
            session = HibernateUtil.openSession();
            List<Object[]> ls = session.createQuery("select stu  from Student stu").list();
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        /* 1條SQL  和上一條一模一樣
         * 
         */
        try
        {
            session = HibernateUtil.openSession();
            List<Student> ls = session.createQuery("select stu  from Student stu").list();
            Iterator<Student> it=ls.iterator();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
    @Test
    public void test07()
    {
        /*
         * 設定了查詢快取為true
         */
        Session session = null;
        Transaction trans = null;
        try
        {   //一條SQL
            session = HibernateUtil.openSession();
            List<Object[]> ls = session.createQuery("select stu  from Student stu")
                .setCacheable(true).list();//開啟查詢快取  查詢快取也是sessionFactory級別的快取
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        /*0條SQL 
         * 
         */
        try
        {
            session = HibernateUtil.openSession();
            List<Student> ls = session.createQuery("select stu  from Student stu")
                .setCacheable(true).list();
            Iterator<Student> it=ls.iterator();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
    @Test
    public void test08()
    {
        /*
         * 此時無法使用查詢快取 查詢快取很少使用 因為很少HQL完全一樣
         * 因為只要HQL語句不一樣 就不會開啟查詢快取 
         * 只有兩個完全一樣的 並且引數都一樣的 才能使用查詢快取
         */
        Session session = null;
        Transaction trans = null;
        try
        {   //一條SQL
            session = HibernateUtil.openSession();
            List<Object[]> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%張%").list();//開啟查詢快取  查詢快取也是sessionFactory級別的快取
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        /*
         * 
         */
        try
        {
          //一條SQL
            session = HibernateUtil.openSession();
            List<Student> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%王%").list();
            Iterator<Student> it=ls.iterator();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
    @Test
    public void test09()
    {
        /*
         * 此時關閉了二級快取(存物件)
         * 打開了查詢快取(存ID)
         * !!!工作順序:如果正常開啟了查詢快取(HQL完全一致 且set(True)),先去查詢快取中查ID
         * 再根據查詢到的ID去二級快取中查物件 若此時二級快取打開了 那麼不會發SQL
         * 若二級快取沒開啟  那麼此時會再次根據查到的ID去資料庫中查詢
         */
        Session session = null;
        Transaction trans = null;
        try
        {   //一條SQL
            session = HibernateUtil.openSession();
            List<Object[]> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%張%").list();//開啟查詢快取  查詢快取也是sessionFactory級別的快取
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        /*
         * 
         */
        try
        {
          //此時關閉了student的二級快取 打開了查詢快取 發出大量SQL
            session = HibernateUtil.openSession();
            List<Student> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%張%").list();
            Iterator<Student> it=ls.iterator();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
    @Test
    public void test10()
    {
        /*
         * 此時打開了二級快取(存物件)
         * 打開了查詢快取(存ID)
         * !!!!工作順序:如果正常開啟了查詢快取,先去查詢快取中查ID
         * 再根據查詢到的ID去二級快取中查物件 若此時二級快取打開了 那麼不會發SQL
         * 若二級快取沒開啟  那麼此時會再次根據查到的ID去資料庫中查詢
         */
        Session session = null;
        Transaction trans = null;
        try
        {   //一條SQL
            session = HibernateUtil.openSession();
            List<Object[]> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%張%").list();//開啟查詢快取  查詢快取也是sessionFactory級別的快取
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        /*
         * 
         */
        try
        {
          //此時打開了student的二級快取 打開了查詢快取 不發SQL
            session = HibernateUtil.openSession();
            List<Student> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%張%").list();
            Iterator<Student> it=ls.iterator();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
    @Test
    public void test11()
    {
        /*
         * 此時打開了二級快取(存物件)
         * 打開了查詢快取(存ID)
         * !!!!工作順序:如果正常開啟了查詢快取,先去查詢快取中查ID
         * 再根據查詢到的ID去二級快取中查物件 若此時二級快取打開了 那麼不會發SQL
         * 若二級快取沒開啟  那麼此時會再次根據查到的ID去資料庫中查詢
         */
        Session session = null;
        Transaction trans = null;
        try
        {   //一條SQL
            session = HibernateUtil.openSession();
            List<Object[]> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%張%").list();//開啟查詢快取  查詢快取也是sessionFactory級別的快取
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
        /*
         * 
         */
        try
        {
          //此時雖然打開了student的二級快取 但是不能使用查詢快取(HQL不一致)導致沒法通過查詢快取查ID去
           //二級快取中查物件
          //所以仍然發出一條SQL 從資料庫中查詢
            session = HibernateUtil.openSession();
            List<Student> ls = session.createQuery("select stu  from Student stu where name like ?")
                .setCacheable(true).setParameter(0, "%張三%").list();
            Iterator<Student> it=ls.iterator();
            while (it.hasNext())
            {
                Student stu = it.next();
                System.out.println(stu.getName());
            }
        }
        catch (HibernateException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != session)
                HibernateUtil.closeSession(session);
        }
    }
}