Mybatis延遲載入的實現以及使用場景
首先我們先思考一個問題,假設:在一對多中,我們有一個使用者,他有100個賬戶。
問題1:在查詢使用者的時候,要不要把關聯的賬戶查出來?
問題2:在查詢賬戶的時候,要不要把關聯的使用者查出來?
解答:在查詢使用者的時候,使用者下的賬戶資訊應該是我們什麼時候使用,什麼時候去查詢。
在查詢賬戶的時候,賬戶的所屬使用者資訊應該是隨著賬戶查詢時一起查詢出來。
搞清楚這兩個簡單的問題後,我們就可以引出延遲載入和立即載入的特性。
延遲載入:在真正使用資料的時候才發起查詢,不用的時候不查詢關聯的資料,延遲載入又叫按需查詢(懶載入)
立即載入:不管用不用,只要一呼叫方法,馬上發起查詢。
使用場景:在對應的四種表關係中,一對多、多對多通常情況下采用延遲載入,多對一、一對一通常情況下采用立即載入。
理解了延遲載入的特性以後再看Mybatis中如何實現查詢方法的延遲載入,在MyBatis 的配置檔案中通過設定settings的lazyLoadingEnabled屬性為true進行開啟全域性的延遲載入,通過aggressiveLazyLoading屬性開啟立即載入。看一下官網的介紹,然後通過一個例項來實現Mybatis的延遲載入,在例子中我們展現一對多表關係情況下,通過實現查詢使用者資訊同時查詢出該使用者所擁有的賬戶資訊的功能展示一下延遲載入的實現方式以及延遲載入和立即載入的結果的不同之處。
lazyLoadingEnabled | 延遲載入的全域性開關。當開啟時,所有關聯物件都會延遲載入。 特定關聯關係中可通過設定 fetchType 屬性來覆蓋該項的開關狀態。 | true | false | false |
aggressiveLazyLoading | 當開啟時,任何方法的呼叫都會載入該物件的所有屬性。 否則,每個屬性會按需載入(參考 lazyLoadTriggerMethods)。 | true | false | false (在 3.4.1 及之前的版本預設值為 true) |
1.使用者類以及賬戶類
public class User implements Serializable{ private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Account> accountList; get和set方法省略..... } public class Account implements Serializable{ private Integer id; private Integer uid; private Double money; get和set方法省略..... }
注意因為我們是查詢使用者的同時查找出其所擁有的賬戶所以我們需要在使用者類中增加賬戶的集合的屬性,用來封裝返回的結果。
2.在UserDao介面中宣告findAll方法
/** * 查詢所有的使用者 * * @return */ List<User> findAll();
3.在UserDao.xml中配置findAll方法的對映
<resultMap id="userAccountMap" type="com.example.domain.User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="birthday" column="birthday"/> <result property="sex" column="sex"/> <result property="address" column="address"/> <collection property="accountList" ofType="com.example.domain.Account" column="id" select="com.example.dao.AccountDao.findAllByUid"/> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT * FROM USER; </select>
主要的功能實現位於 <collection property="accountList" ofType="com.example.domain.Account" column="id" select="com.example.dao.AccountDao.findAllByUid"/>中,對於賬戶列表的資訊通過collection集合來對映,通過select指定集合中的每個元素如何查詢,在本例中select的屬性值為AccountDao.xml檔案的namespace com.example.dao.AccountDao路徑以及指定該對映檔案下的findAllByUid方法,通過這個唯一標識指定集合中元素的查詢方式。因為在這裡需要用到根據使用者ID查詢賬戶,所以需要同時配置一下findAllByUid方法的實現。
4.配置collection中select屬性所使用的方法 findAllByUid
AccountDao介面中新增 /** * 根據使用者ID查詢賬戶資訊 * @return */ List<Account> findAllByUid(Integer uid); AccountDao.xml檔案中配置 <select id="findAllByUid" resultType="com.example.domain.Account"> SELECT * FROM account WHERE uid = #{uid}; </select>
5.在Mybatis的配置檔案中開啟全域性延遲載入
configuration> <settings> <!--開啟全域性的懶載入--> <setting name="lazyLoadingEnabled" value="true"/> <!--關閉立即載入,其實不用配置,預設為false--> <setting name="aggressiveLazyLoading" value="false"/> <!--開啟Mybatis的sql執行相關資訊列印--> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> <typeAliases> <typeAlias type="com.example.domain.Account" alias="account"/> <typeAlias type="com.example.domain.User" alias="user"/> <package name="com.example.domain"/> </typeAliases> <environments default="test"> <environment id="test"> <!--配置事務--> <transactionManager type="jdbc"></transactionManager> <!--配置連線池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test1"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--配置對映檔案的路徑--> <mappers> <mapper resource="com/example/dao/UserDao.xml"/> <mapper resource="com/example/dao/AccountDao.xml"/> </mappers> </configuration>
6.測試方法
private InputStream in; private SqlSession session; private UserDao userDao; private AccountDao accountDao; private SqlSessionFactory factory; @Before public void init()throws Exception{ //獲取配置檔案 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //獲取工廠 factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(UserDao.class); accountDao = session.getMapper(AccountDao.class); } @After public void destory()throws Exception{ session.commit(); session.close(); in.close(); } @Test public void findAllTest(){ List<User> userList = userDao.findAll(); // for (User user: userList){ // System.out.println("每個使用者的資訊"); // System.out.println(user); // System.out.println(user.getAccountList()); // } }
測試說明:當我們註釋了findAllTest()方法中的for迴圈列印的時候,我們將不會需要使用者的賬戶資訊,按照延遲載入的特性程式只會查詢使用者的資訊,而不會查詢賬戶的資訊。當我我們放開for迴圈列印的時候我們使用到了使用者和賬戶的資訊,程式會同時將使用者以及對應的賬戶資訊打印出來。
7.測試結果
(1)註釋for迴圈,不使用資料,這時候不需要查詢賬戶資訊,我們能在控制檯中看到sql執行結果是因為在Mybatis配置檔案中添加了logImpl的配置,具體參考第5步中的配置資訊
(2)通過for迴圈列印查詢的資料,使用資料,這時候因為使用了資料所以將查詢賬戶資訊,我們發現使用者和賬戶查詢都進行了執行