mybatis 使用快取策略
mybatis中預設開啟快取
1、mybatis中,預設是開啟快取的,快取的是一個statement物件。
不同情況下是否會使用快取
同一個SqlSession物件,重複呼叫同一個id的<select>(id必須相同)的時候,快取才會生效,兩者缺一不可,而是會執行兩次sql,並不會使用快取。
下面舉一個例子,首先看一個PersonMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="lixin.gan.mapper.PersonMapper"> <select id="selectAll1" resultType="Person"> select * from person </select> <select id="selectAll2" resultType="Person"> select * from person </select> </mapper>
針對上面這一個xml來進行測試
不同SqlSession呼叫同一個id的方法
public class Test { public static void main(String[] args) throws IOException { InputStream config = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); SqlSession session1 = factory.openSession(); PersonMapper personMapper1 = session1.getMapper(PersonMapper.class); personMapper1.selectAll1(); SqlSession session2 = factory.openSession(); PersonMapper personMapper2 = session2.getMapper(PersonMapper.class); personMapper2.selectAll1(); } }
上面這個程式碼中,建立了兩個不同的sqlsession,雖然呼叫的是同一個id對應的方法,但是其實sql語句是執行了兩次,利用log4j列印的日誌如下:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Preparing: select * from person org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Parameters: org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - <== Total: 4 org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Preparing: select * from person org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Parameters: org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - <== Total: 4
同一個SqlSession呼叫不同id,但是sql相同的方法
public class Test { public static void main(String[] args) throws IOException { InputStream config = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); SqlSession session1 = factory.openSession(); PersonMapper personMapper1 = session1.getMapper(PersonMapper.class); personMapper1.selectAll1(); personMapper1.selectAll2(); } }
上面這個程式碼,雖然建立的一個SqlSession,並且呢,使用同一個SqlSession去呼叫相同sql的方法,但是由於方法對應的id不同,一個是selectAll1,另一個是selectAll2,所以sql語句仍然會執行了兩次,利用log4j列印的日誌依舊依舊是:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Preparing: select * from person org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Parameters: org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - <== Total: 4 org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Preparing: select * from person org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Parameters: org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - <== Total: 4
同一個SqlSession,呼叫同一個id的方法。
public class Test { public static void main(String[] args) throws IOException { InputStream config = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); SqlSession session1 = factory.openSession(); PersonMapper personMapper1 = session1.getMapper(PersonMapper.class); personMapper1.selectAll1(); personMapper1.selectAll1(); } }
雖然上面這段程式碼,進了兩次selectAll1()操作,但是因為是一個SqlSession,並且方法id相同,所以,會使用快取,使用log4j列印的日誌顯示,只進行了一次查詢:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Preparing: select * from person org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Parameters: org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - <== Total: 4
如何做到不同SqlSession對呼叫同一個方法id,仍使用快取
前面已經看到了,使用快取的兩個必要條件:1、同一個SqlSession;2、呼叫同一個id的方法。
但是有時候,情況是這樣的,多個使用者請求某個資料,其實執行的sql都是一樣的,但是因為是不同使用者,所以,就會產生多個SqlSession,多個SqlSession呼叫同一個id的方法,仍然不會使用快取。
這個時候,其實是存在資源浪費的問題,既然他們需要的資料是一樣的,那麼何必再重複查詢一次呢,如果資料量很大,重複查詢一次,豈不是耗費更多的資源?
mybatis提供瞭解決辦法---二級快取。方法就是:修改mapper檔案,增加cache標籤。
前面說到的快取其實一級快取,另外還有二級快取:
1、一級快取就是每一個SqlSession自己在記憶體中的那一段儲存空間。
2、二級快取是其實factory快取,資料存在SqlSessionFactory中的。
當一個SqlSession要取資料,會先檢視一級快取中是不是存在快取,如果存在快取,則直接從當前SqlSession的一級快取中讀取資料。如果不存在快取,那麼就會執行sql命令,去資料庫中查詢資料,並且將查詢出的資料,儲存到當前SqlSession對應的一級快取中。可以呼叫clearCache()來清除當前SqlSession的一級快取資料。
各個SqlSession的一級快取是不能共享的。但是二級快取是可以共享的,於是乎,可以將一級快取中的資料,放到二級快取中,然後其他SqlSession在查詢之前,首先到一級快取中檢視是否有資料,如果一級快取有資料,就從一級快取中取資料;如果一級快取中沒有資料,如果在mapper.xml中設定了cache,那麼可以繼續查詢二級快取,如果二級快取中存在要查詢到資料,那麼就從二級快取中讀取資料,否則就從資料庫中讀取資料。
二級快取共享資料的一個前提是:一個SqlSession呼叫close()之後,才會將一級快取中的資料放到二級快取中,如果不關閉SqlSession,資料只會存在於一級快取中,資料不會發生共享。
修改後的PersonMapper.xml如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="lixin.gan.mapper.PersonMapper"> <cache readOnly="true"></cache> <select id="selectAll1" resultType="Person"> select * from person </select> <select id="selectAll2" resultType="Person"> select * from person </select> </mapper>
正確、正常的測試程式碼
public class Test { public static void main(String[] args) throws IOException { InputStream config = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); SqlSession session1 = factory.openSession(); PersonMapper personMapper1 = session1.getMapper(PersonMapper.class); personMapper1.selectAll1(); // 可以呼叫clearCache來清空當前SqlSession的快取 //session1.clearCache(); // 需要將session關閉之後,才會將資料放進二級快取,否則資料不會放進二級快取 session1.close(); SqlSession session2 = factory.openSession(); PersonMapper personMapper2 = session2.getMapper(PersonMapper.class); personMapper2.selectAll1(); } }
log4j記錄的日誌如下:
org.apache.ibatis.cache.decorators.LoggingCache - line:62 - Cache Hit Ratio [lixin.gan.mapper.PersonMapper]: 0.0 org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Preparing: select * from person org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Parameters: org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - <== Total: 4 org.apache.ibatis.cache.decorators.LoggingCache - line:62 - Cache Hit Ratio [lixin.gan.mapper.PersonMapper]: 0.5
不關閉SqlSession,沒有發生共享的測試
public class Test { public static void main(String[] args) throws IOException { InputStream config = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); SqlSession session1 = factory.openSession(); PersonMapper personMapper1 = session1.getMapper(PersonMapper.class); personMapper1.selectAll1(); // 可以呼叫clearCache來清空當前SqlSession的快取 //session1.clearCache(); // 需要將session關閉之後,才會將資料放進二級快取,否則資料不會放進二級快取 //session1.close(); // 此處沒有關閉session1,所以資料不會放進二級快取中 SqlSession session2 = factory.openSession(); PersonMapper personMapper2 = session2.getMapper(PersonMapper.class); personMapper2.selectAll1(); } }
log4j列印的日誌很清楚的記錄了上面的程式碼,執行了兩次sql,因為資料沒有放進二級快取,在session2在一級快取和二級快取中都沒有發現快取,所以也要執行一次sql。
org.apache.ibatis.cache.decorators.LoggingCache - line:62 - Cache Hit Ratio [lixin.gan.mapper.PersonMapper]: 0.0 org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Preparing: select * from person org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Parameters: org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - <== Total: 4 org.apache.ibatis.cache.decorators.LoggingCache - line:62 - Cache Hit Ratio [lixin.gan.mapper.PersonMapper]: 0.0 org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Preparing: select * from person org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - ==> Parameters: org.apache.ibatis.logging.jdbc.BaseJdbcLogger - line:139 - <== Total: 4