1. 程式人生 > >MyBatis -- 整合Redis二級快取

MyBatis -- 整合Redis二級快取

一。MyBatis一級二級快取 

    級快取:

    MyBatis一級快取為SqlSession級別的快取,預設開啟,相同的SqlSession物件查詢相同條件的結果時,如果存在一級快取,那麼只會訪問資料庫一次,一級快取在sqlSession關閉後失效,呼叫cleanCache後會被清除,執行過增刪改後快取也會被清除。注意:一級快取不能跨session

以一個根據Student Id查詢Student為例:

    @Test
    public void query1() throws IOException{

            SqlSession session = getSession();

            // 獲取對映介面例項
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            Student stu1_1 = mapper.queryStudentById("1");
            Student stu1_2 = mapper.queryStudentById("1");
            System.out.println(stu1_1 == stu1_2);
    }

輸出結果:

還可以看出SQL語句只執行了一次,這就是MyBatis的一級快取。記住這個概念:

同一個SqlSession查詢相同條件的結果時,存在一級快取只會查詢一次。第一次查詢

獲取到資料後會通過session設定到一級快取中,第二次查詢時通過session一級快取

判斷是否存在相同主鍵的資料,存在則直接返回引用,否則查詢資料庫。

二級快取:

二級快取為SqlSessionFactory級別的快取,預設不開啟,要開啟的話走下面這個步驟:

1.首先要在核心配置檔案中配置,這裡要注意順序,要寫在properties聲明後:

    MyBatis官方文件上的說明:

也就是說預設是開啟的,直接用就OK了。不過這個開關只能控制二級快取,一級快取是不受影響的。

2.到需要使用二級快取的namespace中定義:

屬性詳解:
eviction:回收策略,預設使用LRU演算法。
也可以自己改,這裡就改成了FIFO(佇列),會把舊的資料清除掉,最先加入的資料最先
清除,不過這麼做是不行的,如果最先加入的資料是熱點資料,訪問量大,清除掉了又要
重新去資料庫中拉取,所以又有了LRU演算法。

官方文件上的解釋:

可用的收回策略有:

  • LRU – 最近最少使用的:移除最長時間不被使用的物件。
  • FIFO – 先進先出:按物件進入快取的順序來移除它們。
  • SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的物件。
  • WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱
    引用規則的物件。

預設的是 LRU。

flushInterval(重新整理間隔)可以被設定為任意的正整數,而且它們代表一個合理的毫秒 形式的時間段。預設情況是不設定,也就是沒有重新整理間隔,快取僅僅呼叫語句時重新整理。

size(引用數目)可以被設定為任意正整數,要記住你快取的物件數目和你執行環境的 可用記憶體資源數目。預設值是 1024。

readOnly(只讀)屬性可以被設定為 true 或 false。只讀的快取會給所有呼叫者返回緩 存物件的相同例項。因此這些物件不能被修改。這提供了很重要的效能優勢。可讀寫的快取 會返回快取物件的拷貝(通過序列化) 。這會慢一些,但是安全,因此預設是 false。

測試方法:

  @Test

    public void cacheLevel2() throws IOException {

            // 獲取工廠

            SqlSessionFactory factory = getFactory();

            

            // 從同一個工廠中獲取session

            SqlSession session1 = factory.openSession();

            SqlSession session2 = factory.openSession();

            

            StudentMapper mapper1 = session1.getMapper(StudentMapper.class);

            StudentMapper mapper2 = session2.getMapper(StudentMapper.class);

            

            Student stu1 = mapper1.queryStudentById("1");

              session1.close();

            

            Student stu2 = mapper2.queryStudentById("1");
            

            System.out.println(stu1 == stu2);

    }

注意:如果不把session關掉,查詢結果是不會設定到快取中的,一個快取一次只允許一個會話操作。因為考慮到如果兩個會話同時操作一份快取,會引起執行緒併發修改的問題,所以要關掉最先查詢的那個session,查詢結果才會被寫到二級快取中。 


執行結果:

二。整合Redis

    上面提到的FIFO   LRU之類的快取策略預設都有實現類來支援他們,這些策略都是實現自

    Cache介面的,我們要通過MyBatis整合Redis實現自己的快取策略,也是自定義一個實現類

    來實現。不多BB,先上程式碼。

 準備一個JavaBean:

實現Serializable的原因是我打算將Bean序列化成位元組陣列快取到Redis中,取出來之後再反序列化成實體類 。既然要序列化和反序列化,還需要準備兩個方法:

    import java.io.ByteArrayInputStream;

    import java.io.ByteArrayOutputStream;

    import java.io.IOException;

    import java.io.ObjectInputStream;

    import java.io.ObjectOutputStream;

    public class SerializableTools {

            /**         

            * 反序列化         

            *          

            * @param bt         

            * @return         

            * @throws IOException         

            * @throws Exception         

            */

            public static Object byteArrayToObj(byte[] bt) throws Exception {

                    ByteArrayInputStream bais = new ByteArrayInputStream(bt);

                    ObjectInputStream ois = new ObjectInputStream(bais);

                    return ois.readObject();

            }


            /**         

            * 物件序列化         

            *          

            * @param obj         

            * @return         

            * @throws IOException         

            */

            public static byte[] ObjToByteArray(Object obj) throws IOException {

                    ByteArrayOutputStream bos = new ByteArrayOutputStream();

                    ObjectOutputStream oos = new ObjectOutputStream(bos);

                    oos.writeObject(obj);

                    return bos.toByteArray();

            }

    }

OK,現在再定義個快取策略實現類,實現Cache介面,並重寫該類的一系列方法:

    import java.io.IOException;

    import java.util.concurrent.locks.ReadWriteLock;

    import java.util.concurrent.locks.ReentrantReadWriteLock;

    import org.apache.ibatis.cache.Cache;

    import redis.clients.jedis.Jedis;

    public class RedisCache implements Cache {

            

            // 初始化Jedis

            private Jedis jedis = new Jedis("127.0.0.1", 6379);

            

            /*         

            *  MyBatis會把對映檔案的名稱空間作為         

            *  唯一標識cacheId,標識這個快取策略屬於哪個namespace         

            *  這裡定義好,並提供一個構造器,初始化這個cacheId即可         

            */

            private String cacheId;

            

            public RedisCache (String cacheId){

                    this.cacheId = cacheId;

            }


            /**         

            * 清空快取         

            */

            @Override

            public void clear() {

                    // 但這方法不建議實現

            }


            @Override

            public String getId() {

                    return cacheId;

            }


            /**         

            * MyBatis會自動呼叫這個方法檢測快取         

            * 中是否存在該物件。既然是自己實現的快取         

            * ,那麼當然是到Redis中找了。         

            */

            @Override

            public Object getObject(Object arg0) {

                    // arg0 在這裡是鍵

                    try {

                            byte [] bt = jedis.get(SerializableTools.ObjToByteArray(arg0));

                            if (bt == null) {        // 如果沒有這個物件,直接返回null

                                    return null;

                            }

                            return SerializableTools.byteArrayToObj(bt);

                    } catch (Exception e) {

                            e.printStackTrace();

                    }

                    return null;

            }


            @Override

            public ReadWriteLock getReadWriteLock() {

                    return new ReentrantReadWriteLock();

            }


            @Override

            public int getSize() {

                    return Integer.parseInt(Long.toString(jedis.dbSize()));

            }


            /**         

            * MyBatis在讀取資料時,會自動呼叫此方法         

            * 將資料設定到快取中。這裡就寫入Redis         

            */

            @Override

            public void putObject(Object arg0, Object arg1) {

                    /*                 

                    *  arg0是key , arg1是值                 

                    *  MyBatis會把查詢條件當做鍵,查詢結果當做值。                 

                    */

                    try {

                            jedis.set(SerializableTools.ObjToByteArray(arg0), SerializableTools.ObjToByteArray(arg1));

                    } catch (IOException e) {

                            e.printStackTrace();

                    }

            }


            /**         

            * MyBatis快取策略會自動檢測記憶體的大小,由此         

            * 決定是否刪除快取中的某些資料         

            */

            @Override

            public Object removeObject(Object arg0) {

                    Object object = getObject(arg0);

                    try {

                            jedis.del(SerializableTools.ObjToByteArray(arg0));

                    } catch (IOException e) {

                            e.printStackTrace();

                    }

                    return object;

            }

    }

最後在配置檔案中寫上快取實現類的全類名

以及在需要使用該快取類的SQL語句上宣告:

至此,自實現的二級快取就可以用了。