1. 程式人生 > >mybatis 使用快取策略

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