MyBatis框架:延迟加载策策略、一级缓存、二级缓存
MyBatis框架:延遲加載策略和緩存
- Mybatis 延遲加載策略
- 1.1 何為延遲加載?
- 1.2 實現需求
- 1.3 使用association實現延遲加載
- 1.3.1 賬戶的持久層DAO接口
- 1.3.2 賬戶的持久層映射文件
- 1.3.3 用戶的持久層接口和映射文件
- 1.3.4 開啟MyBatis的延遲加載策略
- 1.3.5 編寫測試只查賬戶信息不查用戶信息
- 1.4 使用Collection實現延遲加載
- 1.4.1 在 User 實體類中加入 List屬性
- 1.4.2 編寫用戶和賬戶持久層接口的方法
- 1.4.3 編寫用戶持久層映射配置
- 1.4.4 編寫賬戶持久層映射配置
- 1.4.5 測試只加載用戶信息
- Mybatis 緩存
- 2.1 Mybatis 一級緩存
- 2.1.1 證明一級緩存的存在
- 2.1.1.1 編寫用戶持久層 Dao 接口
- 2.1.1.2 編寫用戶持久層映射文件
- 2.1.1.3 編寫測試方法
- 2.1.2 一級緩存的分析
- 2.1.3 測試一級緩存的清空
- 2.2 Mybatis 二級緩存
- 2.2.1 二級緩存結構圖
- 2.2.2 二級緩存的開啟與關閉
- 2.2.2.1 第一步:在 SqlMapConfig.xml 文件開啟二級緩存
- 2.2.2.2 第二步:配置相關的 Mapper 映射文件
- 2.2.2.3 第三步:配置 statement 上面的 useCache 屬性
- 2.2.3 二級緩存測試
- 2.2.4 二級緩存注意事項
實際開發過程中很多時候我們并不需要總是在加載用戶信息時就一定要加載他的賬戶信息。此時就是我們所說的延遲加載。
Mybatis 延遲加載策略
1.1 何為延遲加載?
延遲加載:就是在需要用到數據時才進行加載,不需要用到數據時就不加載數據。延遲加載也稱為懶加載。
好處:先從單表查詢,需要時再從關聯表去關聯查詢,大大提高數據庫性能,因為查詢單表要比關聯查詢多張表速度快。
壞處:因為只有當需要用到數據時,才會進行數據庫查詢,這樣在大批量數據查詢時,因為查詢工作也要消耗時間,所以可能造成用戶等待時間變長,造成用戶體驗下降。
1.2 實現需求
需求:查詢賬戶(Account)信息并且關聯查詢用戶(User)信息。如果先查詢賬戶(Account)信息就可以滿足要求,當需要查詢用戶(User)信息時,再去查詢用戶(User)信息。把對用戶(User)信息的按需去查詢就是延遲加載。
主要是通過association、collection實現一對一和一對多映射,association、collection具備延遲加載功能。
1.3 使用association實現延遲加載
1.3.1 賬戶的持久層DAO接口
public interface IAccountDao { /** * 查詢所有賬戶,同時獲取賬戶的所屬用戶名稱以及它的地址信息 * @return */ List<Account> findAll(); }1.3.2 賬戶的持久層映射文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.dao.IAccountDao"><!--定義封裝account和user的resultMap--><resultMap id="accountUserMap" type="account"><id property="id" column="id"/><result property="uid" column="uid"/><result property="money" column="money"/><!--一對一的關系映射:配置封裝user的內容select屬性指定的內容:查詢用戶的唯一標識column屬性指定的內容:用戶根據id查詢時,所需要的參數的值--><association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById"/></resultMap><!--查詢所有--><select id="findAll" resultMap="accountUserMap">select *from account</select> </mapper>1.3.3 用戶的持久層接口和映射文件
package com.itheima.dao;import com.itheima.domain.User;import java.util.List;public interface IUserDao {User findById(Integer userId); } <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.dao.IUserDao"><select id="findById" parameterType="Integer" resultType="com.itheima.domain.User">select * from user where id = #{uid}</select> </mapper>1.3.4 開啟MyBatis的延遲加載策略
<!--配置參數--><settings><!--開啟Mybatis支持延遲加載--><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/></settings>1.3.5 編寫測試只查賬戶信息不查用戶信息
public class IAccountDaoTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;private SqlSession sqlSession = null;private IAccountDao accountDao = null;@Before //在測試方法執行之前執行public void setUp() throws Exception {//1.讀取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.創建構建者對象builder = new SqlSessionFactoryBuilder();//3.創建 SqlSession 工廠對象factory = builder.build(in);sqlSession = factory.openSession();accountDao = sqlSession.getMapper(IAccountDao.class);}@Afterpublic void tearDown() throws Exception {sqlSession.commit();sqlSession.close();in.close();}@Testpublic void findAll() {List<Account> accounts = accountDao.findAll();for (Account account : accounts) {System.out.println(account); // System.out.println(account.getUser());}} }測試結果如下:
我們發現,因為本次只是將 Account對象查詢出來放入 List 集合中,并沒有涉及到 User對象,所以就沒有發出 SQL 語句查詢賬戶所關聯的 User 對象的查詢。
1.4 使用Collection實現延遲加載
同樣我們也可以在一對多關系配置的<collection>結點中配置延遲加載策略。
<collection>結點中也有select屬性,column屬性。
需求:
完成加載用戶對象時,查詢該用戶所擁有的賬戶信息。
1.4.1 在 User 實體類中加入 List屬性
public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;//一對多關系映射,主表實體應該包含從表實體的集合引用private List<Account> accounts;}1.4.2 編寫用戶和賬戶持久層接口的方法
package com.itheima.dao;import com.itheima.domain.User;import java.util.List;public interface IUserDao {List<User> findAll();User findById(Integer userId); } package com.itheima.dao;import com.itheima.domain.Account;import java.util.List;public interface IAccountDao {/*** 查詢所有賬戶,同時還要獲取當前賬戶的所屬用戶信息** @return*/List<Account> findAll();/*** 根據用戶id查詢賬戶信息** @param uid* @return*/List<Account> findAccountByUid(Integer uid); }1.4.3 編寫用戶持久層映射配置
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.dao.IUserDao"><!--定義User的resultMap信息--><resultMap id="userAccountMap" type="user"><id property="id" column="id"/><result property="username" column="username"/><result property="address" column="address"/><result property="sex" column="sex"/><result property="birthday" column="birthday"/><!--配置user對象中accounts集合的映射--><collection property="accounts" ofType="account"select="com.itheima.dao.IAccountDao.findAccountByUid" column="id"/></resultMap><select id="findAll" resultMap="userAccountMap">select * from user u left outer join account a on u.id = a.uid</select><select id="findById" parameterType="Integer" resultType="com.itheima.domain.User">select * from user where id = #{uid}</select> </mapper>1.4.4 編寫賬戶持久層映射配置
<!--根據用戶id查詢賬戶列表--><select id="findAccountByUid" resultType="account" parameterType="int">select * from account where uid=#{uid}</select>1.4.5 測試只加載用戶信息
public class IUserDaoTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;private SqlSession sqlSession = null;private IUserDao userDao = null;@Beforepublic void setUp() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");builder = new SqlSessionFactoryBuilder();factory = builder.build(in);sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);}@Afterpublic void tearDown() throws Exception {sqlSession.commit();sqlSession.close();in.close();}@Testpublic void findAll() {List<User> userList = userDao.findAll();for (User user : userList) {System.out.println(user); // System.out.println(user.getAccounts());}} }測試結果如下:我們發現并沒有加載 Account 賬戶信息。
Mybatis 緩存
像大多數的持久化框架一樣,Mybatis 也提供了緩存策略,通過緩存策略來減少數據庫的查詢次數,從而提高性能。
Mybatis 中緩存分為一級緩存,二級緩存。
2.1 Mybatis 一級緩存
2.1.1 證明一級緩存的存在
一級緩存是 SqlSession 級別的緩存,只要 SqlSession 沒有 flush 或 close,它就存在。
2.1.1.1 編寫用戶持久層 Dao 接口
public interface IUserDao {User findById(Integer userId); }2.1.1.2 編寫用戶持久層映射文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.dao.IUserDao"><select id="findById" parameterType="Integer" resultType="com.itheima.domain.User">select * from user where id = #{uid}</select> </mapper>2.1.1.3 編寫測試方法
public class IUserDaoTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;private SqlSession sqlSession = null;private IUserDao userDao = null;@Beforepublic void setUp() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");builder = new SqlSessionFactoryBuilder();factory = builder.build(in);sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);}@Afterpublic void tearDown() throws Exception {sqlSession.commit();sqlSession.close();in.close();}@Testpublic void findAll() {List<User> userList = userDao.findAll();for (User user : userList) {System.out.println(user);}}/*** 測試一級緩存*/@Testpublic void testFirstLevelCache() {User u1 = userDao.findById(41);System.out.println(u1);User u2 = userDao.findById(41);System.out.println(u2);System.out.println(u1 == u2);//true} }測試結果如下:
我們可以發現,雖然在上面的代碼中我們查詢了兩次,但最后只執行了一次數據庫操作,這就是 Mybatis 提供給我們的一級緩存在起作用了。因為一級緩存的存在,導致第二次查詢 id 為 41 的記錄時,并沒有發出 sql 語句從數據庫中查詢數據,而是從一級緩存中查詢。
2.1.2 一級緩存的分析
一級緩存是 SqlSession 范圍的緩存,當調用 SqlSession 的修改,添加,刪除,commit(),close()等方法時,就會清空一級緩存。
第一次發起查詢用戶 id 為 1 的用戶信息,先去找緩存中是否有 id 為 1 的用戶信息,如果沒有,從數據庫查詢用戶信息。
得到用戶信息,將用戶信息存儲到一級緩存中。
如果 sqlSession 去執行 commit 操作(執行插入、更新、刪除),清空 SqlSession 中的一級緩存,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀。
第二次發起查詢用戶 id 為 1 的用戶信息,先去找緩存中是否有 id 為 1 的用戶信息,緩存中有,直接從緩存中獲取用戶信息。
2.1.3 測試一級緩存的清空
public class IUserDaoTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;private SqlSession sqlSession = null;private IUserDao userDao = null;@Beforepublic void setUp() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");builder = new SqlSessionFactoryBuilder();factory = builder.build(in);sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);}@Afterpublic void tearDown() throws Exception {sqlSession.commit();sqlSession.close();in.close();}/*** 測試一級緩存*/@Testpublic void testFirstLevelCache() {User u1 = userDao.findById(41);System.out.println(u1);User u2 = userDao.findById(41);System.out.println(u2);System.out.println(u1 == u2);//true}@Testpublic void testFirstLevelCache1() {User u1 = userDao.findById(41);System.out.println(u1);sqlSession.close();//再次獲取session對象sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);User u2 = userDao.findById(41);System.out.println(u2);System.out.println(u1 == u2);//false}@Testpublic void testFirstLevelCache2() {User u1 = userDao.findById(41);System.out.println(u1);sqlSession.clearCache();User u2 = userDao.findById(41);System.out.println(u2);System.out.println(u1 == u2);//false}@Testpublic void testClearCache() {//1.根據id查詢用戶User user1 = userDao.findById(41);System.out.println(user1);//2.更新用戶信息user1.setUsername("update user clear cache");user1.setAddress("北京海淀");userDao.updateUser(user1);//3.再次查詢id為41的用戶User user2 = userDao.findById(41);System.out.println(user2);System.out.println(user1 == user2);} }當執行sqlSession.close()后,再次獲取sqlSession并查詢id=41的User對象時,又重新執行了sql語句,從數據庫進行了查詢操作。
2.2 Mybatis 二級緩存
二級緩存是 mapper 映射級別的緩存,多個 SqlSession 去操作同一個 Mapper 映射的 sql 語句,多個 SqlSession 可以共用二級緩存,二級緩存是跨 SqlSession 的。
2.2.1 二級緩存結構圖
首先開啟 mybatis 的二級緩存。
sqlSession1 去查詢用戶信息,查詢到用戶信息會將查詢數據存儲到二級緩存中。
如果 SqlSession3 去執行相同 mapper 映射下 sql,執行 commit 提交,將會清空該 mapper 映射下的二級緩存區域的數據。
sqlSession2 去查詢與 sqlSession1 相同的用戶信息,首先會去緩存中找是否存在數據,如果存在直接從緩存中取出數據。
2.2.2 二級緩存的開啟與關閉
2.2.2.1 第一步:在 SqlMapConfig.xml 文件開啟二級緩存
<!--配置參數--><settings><setting name="cacheEnabled" value="true"/></settings>因為 cacheEnabled 的取值默認就為 true,所以這一步可以省略不配置。為 true 代表開啟二級緩存;為 false 代表不開啟二級緩存。
2.2.2.2 第二步:配置相關的 Mapper 映射文件
標簽表示當前這個 mapper 映射將使用二級緩存,區分的標準就看 mapper 的 namespace 值。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.dao.IUserDao"><!--開啟user支持二級緩存--><cache/> </mapper>2.2.2.3 第三步:配置 statement 上面的 useCache 屬性
<select id="findById" parameterType="Integer" resultType="user" useCache="true">select *from userwhere id = #{uid}</select>將 UserDao.xml 映射文件中的標簽中設置 useCache=”true”代表當前這個 statement 要使用二級緩存,如果不使用二級緩存可以設置為 false。
注意:針對每次查詢都需要最新的數據 sql,要設置成 useCache=false,禁用二級緩存。
2.2.3 二級緩存測試
public class secondLevelCacheTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;@Beforepublic void setUp() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");builder = new SqlSessionFactoryBuilder();factory = builder.build(in);}@Afterpublic void tearDown() throws Exception {in.close();}/*** 測試一級緩存*/@Testpublic void testSecondLevelCache() {SqlSession sqlSession1 = factory.openSession();IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);User u1 = userDao1.findById(41);System.out.println(u1);sqlSession1.close();//一級緩存消失SqlSession sqlSession2 = factory.openSession();IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);User u2 = userDao2.findById(41);System.out.println(u2);sqlSession2.close();System.out.println(u1 == u2);//false} }經過上面的測試,我們發現執行了兩次查詢,并且在執行第一次查詢后,我們關閉了一級緩存,再去執行第二次查詢時,我們發現并沒有對數據庫發出 sql 語句,所以此時的數據就只能是來自于我們所說的二級緩存。
2.2.4 二級緩存注意事項
當我們在使用二級緩存時,所緩存的類一定要實現 java.io.Serializable 接口,這種就可以使用序列化方式來保存對象。
public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address; }總結
以上是生活随笔為你收集整理的MyBatis框架:延迟加载策策略、一级缓存、二级缓存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: @JsonIgnorePropertie
- 下一篇: 普通莫队--洛谷P1997 【faebd