mybatis探究之延遲載入和快取
- 2020 年 3 月 18 日
- 筆記
mybatis探究之延遲載入和快取
一、什麼是延遲載入
1.延遲載入的概念
在mybatis進行多表查詢時,並非所有的查詢都需要立即進行。例如在查詢帶有賬戶資訊的用戶資訊時,我們們並不需要總是在載入用戶資訊時就一定要載入他的賬戶資訊。這時就要用到延遲載入,所謂延遲載入就是在需要用到數據時才進行載入,不需要用到數據時就不載入數據。延遲載入也稱懶載入。
2.延遲載入的好處和壞處
好處:先從單表查詢,需要時再從關聯表去關聯查詢,大大提高資料庫性能,因為查詢單表要比關聯查詢多張錶速度要快。
壞處:因為只有當需要用到數據時,才會進行資料庫查詢,這樣在大批量數據查詢時,因為查詢工作也要消耗
時間,所以可能造成用戶等待時間變長,造成用戶體驗下降。
3.什麼時候使用延遲載入
在對應的四種表關係(一對多,多對一,一對一,多對多)中:
一對多,多對多:通常情況下我們都是採用延遲載入。
多對一,一對一:通常情況下我們都是採用立即載入。
二、一對一實現延遲載入
在進階案例的基礎上,進行如下修改:
1.添加延遲載入的配置
在主配置文件SqlMapConfig.xml文件中,添加settings標籤,可以參考mybatis官方文檔
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
2.修改主從表對應關係的配置
將映射配置文件IAccountDao.xml文件中的resultMap標籤中的association標籤修改為如下:
<association property="user" javaType="domain.User" select="dao.IUserDao.findById" column="uid"/>
select的內容是: 要調用的IUserDao中對應的 select 方法的 id ,column的內容是 : 要傳遞給 select 方法的參數在account數據表中對應的列名。
3.修改從表查詢的SQL語句
在原來的findAllAccountsWithUser方法中,SQL語句直接將兩個表進行笛卡爾積,如果不修改SQL語句,就會進行立即查詢。因為是延遲載入,所以此處只需查詢account資訊即可。在映射配置文件IAccountDao.xml中進行如下修改:
<!-- 以延遲載入的方式配置查詢帶有用戶資訊的賬戶資訊 --> <select id="findAllAccountsWithUser" resultMap="accountUserMap"> select* from account </select>
4.測試運行
1.採用立即載入方式的查詢
過程:直接執行一條SQL語句就可以獲得帶有用戶資訊的賬戶資訊
2.不修改測試函數進行查詢
當我們完成上述配置之後,直接運行測試函數,會發現並沒有實現延遲載入。每次查詢賬戶時依舊對用戶進行了查詢:這是因為在測試函數當中,對查詢到的account對象和user對象進行列印,相當每次查詢都需要用到數據。而延遲載入是在需要用到數據時才進行載入,不需要用到數據時就不載入數據。所以每次都會查詢用戶資訊。
/** * 測試以延遲載入的方式查詢帶有用戶資訊的賬戶資訊 * * @throws IOException */ @Test public void testFindAllAccountsWithUser() throws IOException { List<Account> accounts = accountDao.findAllAccountsWithUser(); for (Account account : accounts ) { System.out.println(account);//這裡表明每次查詢都需要載入完整數據 System.out.println(account.getUser()); } }
注意:即便是注釋掉System.out.println(account.getUser()); 每次查詢賬戶資訊時,也會查詢出用戶資訊。這是為什麼呢?因為account對象中包含對user對象的引用,直接列印account對象,需要構造完整的account對象,也就需要從資料庫查出user對象的屬性,並通過反射賦值給account對象中的user。
3.修改測試函數進行查詢
@Test public void testFindAllAccountsWithUser() throws IOException { List<Account> accounts = accountDao.findAllAccountsWithUser(); int i = 0; for (Account account : accounts ) { System.out.println(account.getMoney()); if(i == 1) System.out.println(account.getUser()); i++; } }
當修改測試函數之後,每次遍歷只需要列印出賬戶的金額資訊,不需要完整的account對象,所以並沒有對user表進行查詢,而當計數變數i == 1時,需要列印user資訊,這才對user表進行查詢。
三、一對多實現延遲載入
1.修改主從表對應關係配置
將映射配置文件IUserDao.xml文件中的resultMap標籤中的collection標籤修改為如下:
<collection property="accounts" ofType="com.whu.cs.domain.Account" select="com.whu.cs.dao.IAccountDao.findAccountsByUid" column="id"/>
2.添加查詢方法
在IAccountDao介面中添加根據用戶id查詢賬戶資訊的方法:
/** * 根據用戶id查詢賬戶資訊 * @param uid * @return */ List<Account> findAccountsByUid(Integer uid);
3.配置查詢方法
在映射配置文件IAccountDao.xml介面中配置根據用戶id查詢賬戶資訊的查詢方法:
<!-- 配置根據用戶id查詢賬戶資訊 --> <select id="findAccountsByUid" parameterType="Integer" resultType="com.whu.cs.domain.Account"> select * from account where uid = #{uid} </select>
4.修改sql語句
在映射配置文件IUserDao.xml文件中進行如下修改:
<!-- 配置以延遲載入的方式查詢帶有賬戶資訊的用戶資訊 --> <select id="findUserWithAccounts" resultMap="userAccountsMap"> select * from user </select>
5.測試運行
在測試類MybatisTest中修改 testFindUserWithAccounts方法為:
@Test public void testFindUserWithAccounts() { //6.執行操作 List<User> users = userDao.findUserWithAccounts(); int i = 0; for(User user : users) { System.out.println(user.getUsername()); if(i == 3) System.out.println(user.getAccounts()); i++; } }
四、什麼是快取
1.快取的概念
快取就是在記憶體中存儲的數據備份,當數據沒有發生本質改變的時候,我們就不讓數據的查詢去資料庫進行操作,而去記憶體中取數據,這樣就大大降低了資料庫的讀寫次數,而且從記憶體中讀數據的速度比去資料庫查詢要快一些,這樣同時又提高了效率。
2.快取如何使用
在資料庫中適用於快取機制的數據包括:經常查詢並且不經常改變的數據和結果的正確與否對最終結果影響不大的數據。相應地,不適用於快取機制的數據包括:經常改變的數據和結果的正確與否對最終結果影響很大的數據,例如:商品的庫存,銀行的匯率,股市的牌價。
3.mybatis中的快取機制
mybatis中的快取根據快取的生命周期,可分為一級快取和二級快取。Mybatis默認開啟一級快取而關閉二級快取。
五、mybatis中的一級快取
1.什麼是一級快取
一級快取指的是Mybatis中SqlSession對象的快取,當我們執行查詢之後,查詢的結果會同時存入到SqlSession為我們提供一塊區域中。該區域的結構是一個Map。當我們再次查詢同樣的數據,Mybatis首先會去sqlsession中查詢是否,如果改快取中有這個數據,就直接從快取中讀取數據,否則就去資料庫進行查詢。當SqlSession對象消失(close)時,mybatis的一級快取也就消失了。
2.一級快取的測試
在測試類MybatisTest中添加如下測試方法:
/** * 測試一級快取 */ @Test public void testL1Cache() { User user1 = userDao.findById(41); System.out.println("第一次查詢的用戶:" + user1); User user2 = userDao.findById(41); System.out.println("第二次查詢用戶:" + user2); System.out.print("第一次和第二次是否是同一對象:"); System.out.println(user1 == user2); sqlSession.clearCache();//清空快取 User user3 = userDao.findById(41); System.out.println("第三次查詢用戶:" + user3); System.out.print("第二次和第三次是否是同一對象:"); System.out.println(user2 == user3); sqlSession.close();//close操作會清空快取 //再次獲取 SqlSession 對象 sqlSession = factory.openSession(); userDao = sqlSession.getMapper(IUserDao.class); User user4 = userDao.findById(41); System.out.println("第四次查詢用戶:" + user4); System.out.print("第三次和第四次是否是同一對象:"); System.out.println(user3 == user4); }
為了更清楚地看到結果,在User類的toString方法返回的字元串中添加super.toString()方法。運行結果如下:
可以看到第二次查詢時,是直接從快取獲取,並非查詢資料庫。因此第一次和第二次都是同一對象。而第三次查詢由於清空快取,所以是從資料庫中查詢,所以不是同一對象。第四次由於關閉sqlSession對象,快取消失,所以也是從資料庫中查詢。
3.一級快取的分析
一級快取是 SqlSession 範圍的快取,當調用 SqlSession 的修改,添加,刪除,commit(),close()等方法時,就會清空一級快取。
第一次發起查詢用戶id為1的用戶資訊,先去找快取中是否有id為1的用戶資訊,如果沒有,從資料庫查詢用戶資訊。得到用戶資訊後,將用戶資訊存儲到一級快取中。如果 sqlSession 去執行 commit 操作(對資料庫執行插入、更新、刪除),就會清空 SqlSession 中的一級快取,這樣做的目的為了讓快取中存儲的是最新的資訊,避免臟讀。(換句話說,只要對資料庫進行更新、刪除、插入等操作,就會清空一級快取,避免快取中數據和資料庫中數據不一致。)
第二次發起查詢用戶 id 為 1 的用戶資訊,先去找快取中是否有id為1的用戶資訊,快取中有,直接從快取中獲取用戶資訊。
六、mybatis中的二級快取
1.什麼是二級快取
它指的是Mybatis中SqlSessionFactory對象的快取。由同一個SqlSessionFactory對象創建的SqlSession共享其快取。多個 SqlSession 去操作同一個 Mapper 映射的 sql 語句,多個SqlSession 可以共用二級快取,二級快取是跨 SqlSession 的。
2.二級快取實例
1.在主配置文件中開啟全局二級快取
二級快取默認不開啟,需要在主配置文件SqlMapConfig.xml文件中開啟二級快取的支援。
<settings> <!-- 開啟二級快取的支援 --> <setting name="cacheEnabled" value="true"/> </settings>
2.指定要開啟二級快取的映射配置文件
在映射配置文件IUserDao.xml文件中添加:
<!-- cache標籤僅用於指定要開啟二級快取的映射配置文件 --> <cache></cache>
3.修改查詢方法的配置
將 IUserDao.xml 映射配置文件中的select標籤中設置 useCache=」true」代表當前這個 statement 要使用
二級快取,如果不使用二級快取可以設置為 false。
<!-- 配置根據id查詢用戶 --> <select id="findById" resultType="com.whu.cs.domain.User" useCache="true"> select * from user where id = #{id}; </select>
注意:針對每次查詢都需要最新的數據 sql,要設置成 useCache=false,禁用二級快取。
4.測試
在測試類MybatisTest中添加測試方法:
/** * 測試二級快取 */ @Test public void testL2Cache() { User user1 = userDao.findById(41); System.out.println("第一次查詢的用戶:" + user1); sqlSession.close(); //關閉一級快取 SqlSession sqlSession2 = factory.openSession(); IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class); User user2 = userDao2.findById(41); System.out.println("第二次查詢用戶:" + user2); System.out.print("第一次和第二次是否是同一對象:"); System.out.println(user1 == user2); sqlSession2.close(); }
可以看到雖然只查詢了一次,第二次確實是從二級快取中讀取數據,但是第一次查詢得到的對象和第二次查詢得到的對象並不一致。
3.二級快取分析
二級快取中存放的對象的屬性數據,而非對象數據。因此,即便從二級快取讀取數據,得到的對象也是不相同的。
在開啟mybatis的二級快取之後。如果sqlSession1去查詢用戶id為1的用戶資訊,查詢到用戶資訊會將查詢數據存儲到二級快取中。sqlSession2去查詢用戶id為1的用戶資訊,去快取中找是否存在數據,如果存在直接從快取中取出數據。如果SqlSession3去執行相同 mapper下sql,執行commit提交,清空該 mapper下的二級快取區域的數據。
注意:當我們在使用二級快取時,所快取的類一定要實現 java.io.Serializable 介面,這種就可以使用序列化方式來保存對象。
————恢復內容結束————