1. 延遲加載
1.1 概念
- 在需要用到數據時才進行加載,不需要用到數據時就不加載數據。也稱作懶加載
- 好處:先從單表查詢,需要時再從關聯表去關聯查詢,大大提高數據庫性能
- 缺點:在大批量數據查詢時,由於查詢會耗時,可能導致用戶等待時間變長,影響用戶體驗
其中:mybatis的association、collection標籤具備延遲加載功能
及時加載:一次加載所有數據。
1.2 一對一實現延時加載
以賬戶表和用戶表一對一關係爲例
需求:查詢賬戶時只查詢到賬戶,只有使用賬戶查詢對應的用戶時才查詢用戶
1.2.1 創建項目和創建實體類
按照“mybatis-CERD操作”裏的架構創建項目,添加pom依賴、添加SqlMapConfig.xml、log4j.properties、jdbc.properties配置文件。引入“mybatis-多表查詢”中的User類、Account類
1.2.2 創建IUserDao接口和IUserDao.xml
IUserDao接口
public interface IUserDao {
/*根據用戶id查詢用戶,一個賬戶只對應一個用戶*/
User findUserById(int id);
}
IUserDao.xml映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namesppace名稱空間,用於定義是哪個類的映射文件,這裏需要寫映射接口的類全名-->
<mapper namespace="com.azure.dao.IUserDao">
<select id="findUserById" parameterType="int" resultType="user">
select * from user where id=#{id}
</select>
</mapper>
1.2.3 創建IAccount接口和映射配置文件
在一對一關係中使用延遲加載,需要利用到association標籤的select屬性和column屬性調用延遲加載的方法
- select屬性:格式:接口類全名.方法名,用於設置需要查詢一對一關係對應的表數據時執行哪個接口的哪個方法
- column屬性:如果使用select標籤,則column用於將指定的數據傳遞給select方法作爲參數。如果參數有多個,則使用集合形式,如column="{參數1=accountsMap的某column字段,參數2=accountsMap的某column字段,…}"
- fetchType=“lazy”:局部開啓延遲加載,會覆蓋全局延遲加載設置。默認不開啓。如果全局延遲加載已設置,這裏可省略不設置
<?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="com.azure.dao.IAccountDao">
<!--返回集的類型是Account類型,裏面需封裝account表數據和User對象-->
<resultMap id="accountsMap" type="account">
<!--先建立account對象屬性與account表字段的映射關係-->
<id property="accountId" column="accountId"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--建立user對象與User表字段的映射關係-->
<!--
使用association標籤標示一對一關係
- property:一對一關係的對應對象屬性名(本例是指賬戶對象對應的user對象屬性);
- JavaType:對應對象屬性類型
- column:外鍵字段
配置延遲加載
- select:格式:接口類全名.方法名,用於設置需要查詢一對一關係對應的表數據時執行哪個接口的哪個方法
- column:如果使用select標籤,則column用於將指定的數據傳遞給select方法作爲參數
- fetchType="lazy":局部開啓延遲加載,會覆蓋全局延遲加載設置。默認不開啓。如果全局延遲加載已設置,這裏可省略不設置
-->
<association property="user" javaType="user" column="uid" select="com.azure.dao.IUserDao.findUserById" fetchType="lazy"></association>
</resultMap>
<!--使用resultMap明確一對一關係-->
<select id="findAll" resultMap="accountsMap">
select * from account
</select>
</mapper>
1.2.4 測試類
public class AccountDaoTest {
private InputStream is;
private SqlSession session;
private IAccountDao accountDao;
/**
* 每次執行Junit前都會執行
* @throws IOException
*/
@Before
public void before() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
session = new SqlSessionFactoryBuilder().build(is).openSession();
accountDao = session.getMapper(IAccountDao.class);
}
/**
* 每次執行Junit後都會執行,提交事務和關閉資源
* @throws IOException
*/
@After
public void close() throws IOException {
//手動提交事務
session.commit();
//關閉資源
session.close();
is.close();
}
@Test
public void findAll() {
List<Account> list = accountDao.findAll();
for (Account account : list) {
System.out.println(account.getAccountId() +"--" + account.getMoney());
//使用賬戶查詢用戶信息,此時纔會查詢用戶表,實現延遲加載
System.out.println(account.getUser().getUsername());
}
}
}
注意:如果測試類直接打印list,由於Account類中包含User對象,此時系統會自動查詢user,無法顯示延遲加載的效果。實際上是有實現延遲加載。
1.2.5 延遲加載支持的開啓
1.2.5.1 使用延遲加載全局開關進行開啓
開啓方式:在主配置文件中設置settings標籤,將lazyLoadingEnabled的屬性值設置爲true
1.2.5.2 使用局部延遲加載
開啓方式:在dao映射配置文件的<association>
或<collection>
中設置fetchType屬性值爲lazy
1.2.5.3 兩種延遲加載方式的取捨
一般只要開啓全局延遲加載即可。如果只是局部需要延遲加載,那可以只配局部延遲加載。
1.3 一對多實現延遲加載
以User與Account爲一對多關係爲例
需求:查詢用戶時候,只查詢用戶信息; 使用用戶的賬戶信息時候才查詢賬戶信息
1.3.1 dao接口
1.3.1.1 IAccountDao接口
/*根據uid獲得賬戶*/
List<Account> findByUid(int uid);
1.3.1.2 IUserDao接口
/*查詢所有用戶*/
List<User> findAll();
1.3.2 dao映射文件
1.3.2.1 IAccountDao.xml
<!--根據用戶id查詢賬戶信息-->
<select id="findByUid" resultType="Account">
select * from account where UID=#{uid}
</select>
1.3.2.2 IUserDao.xml
在一對多關係中使用延遲加載,需要利用到collection標籤的select屬性和column屬性調用延遲加載的方法
- select屬性:格式:接口類全名.方法名,用於設置需要查詢一對多關係對應的表數據時執行哪個接口的哪個方法
- column屬性:如果使用select標籤,則column用於將指定的數據傳遞給select方法作爲參數。如果參數有多個,則使用集合形式,如column="{參數1=accountsMap的某column字段,參數2=accountsMap的某column字段,…}"
- fetchType=“lazy”:局部開啓延遲加載,會覆蓋全局延遲加載設置。默認不開啓。如果全局延遲加載已設置,這裏可省略不設置
<resultMap id="userMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<!--
select:用於指定延遲加載的數據進行調用的方法
column:在select標籤存在的情況下,column用於傳遞方法所需參數,參數是userMap中的column值。如果參數有多個,採用集合方式
-->
<collection property="accounts" ofType="account" column="id" select="com.azure.dao.IAccountDao.findByUid" fetchType="lazy"></collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user
</select>
1.3.3 測試類
@Test
public void findAll() {
List<User> list = userDao.findAll();
for (User user : list) {
System.out.println(user.getId() +"--"+ user.getUsername());
//利用用戶獲得賬戶信息,此時纔會加載對應的方法查詢,實現延遲加載
System.out.println(user.getAccounts());
}
}
注意:如果測試類直接打印list,由於User類中包含List<Account>
對象,此時系統會自動查詢account表,無法顯示延遲加載的效果。實際上是有實現延遲加載。
2. mybatis緩存機制
Mybatis 提供了緩存策略,通過緩存策略來減少數據庫的查詢次數,從而提高性能。Mybatis 中緩存分爲一級緩存,二級緩存。
2.1 一級、二級緩存
-
一級緩存
- 基於SqlSession的緩存,不能跨sqlSession;
- mybatis自動維護,無法認爲影響哪些數據會存入一級緩存;
- 關閉sqlSession後一級緩存會失效;
- 效果:第一次查詢會到數據庫查詢,然後將查詢結果存儲到一級緩存中。之後的查詢會先到緩存中查找,找到就不查詢數據庫,找不到再到數據庫查詢。
-
二級緩存
-
基於映射文件的緩存,緩存範圍比一級緩存更大。不同的sqlsession可以操作同一個 Mapper 映射的 sql 語句、訪問二級緩存的內容。可以跨sqlSession
-
哪些數據放入二級緩存需要自行指定。一般存入二級緩存的數據不會經常修改;
-
步驟:
-
開啓mybatis的二級緩存:在主配置文件中的settings中配置cacheEnabled屬性爲true(默認已經開啓)
<settings> <!--配置二級緩存,默認開啓,可選配置--> <setting name="cacheEnabled" value="true"></setting> </settings>
-
哪些映射文件中的SQL查詢的結果需要放入二級緩存,需要在映射文件中配置
<cache/>
,並在需要使用二級緩存的方法中配置userCache=true
-
實體類實現可序列化接口(Serializable)
-
-
2.2 Redis和Mybatis的緩存使用場景
Redis可以集羣緩存;Mybatis只適合單機緩存,且緩存不會持久化。故可以使用Mybatis操作redis
3. Mybatis註解開發
3.1 註解說明
@Insert:實現新增
@Update:實現更新
@Delete:實現刪除
@Select:實現查詢
@Result:實現結果集封裝
@Results:可以與@Result 一起使用,封裝多個結果集
@One:實現一對一結果集封裝
@Many:實現一對多結果集封裝
@Param 當方法參數有多個時候,建立sql語句中的佔位符參數值與方法參數的映射關係。
3.2使用註解實現CRUD操作
以IUserdao實現CRUD操作爲例
3.2.1 dao接口
- 直接使用註釋配置sql語句
public interface IUserDao {
/*實現單表查詢功能*/
@Select("select * from user where id=#{id}")
User findById(int id);
/*實現修改功能,裏面的參數需要對應實體類的屬性,mybatis會自動將參數賦值到參數中*/
@Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}")
void update(User user);
/*實現新增功能,mybatis會自動封裝User*/
@Insert("insert into user values (null,#{username},#{birthday},#{sex},#{address})")
void save(User user);
/*實現刪除功能*/
@Delete("delete from user where id=#{id}")
void delete(int id);
3.2.2 測試類
public class UserDaoTest {
private InputStream is;
private SqlSession session;
private IUserDao userDao;
/**
* 每次執行Junit前都會執行
* @throws IOException
*/
@Before
public void before() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
session = new SqlSessionFactoryBuilder().build(is).openSession();
userDao = session.getMapper(IUserDao.class);
}
/**
* 每次執行Junit後都會執行,提交事務和關閉資源
* @throws IOException
*/
@After
public void close() throws IOException {
//手動提交事務
session.commit();
//關閉資源
session.close();
is.close();
}
@Test
public void find() {
User user = userDao.findById(48);
System.out.println(user);
}
@Test
public void update(){
User user = new User();
user.setId(51);
user.setUsername("大狗");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("北極");
userDao.update(user);
}
@Test
public void insert(){
User user = new User();
user.setUsername("大狗");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("北極");
userDao.save(user);
}
@Test
public void delete() {
userDao.delete(51);
}
}
3.2.3 多參數的CRUD操作
-
sql語句中需要傳遞兩個及以上參數時,需要使用多參數CRUD操作
-
以查詢爲例,其餘操作的寫法與此雷同
/*多參數實現單表查詢功能*/
//報錯org.apache.ibatis.exceptions.PersistenceException:
// Parameter 'start' not found. Available parameters are [arg1, arg0, param1, param2]
@Select("select * from user limit #{start},#{length}")
List<User> findByPage (int start, int length);
上述代碼報錯的原因:代碼沒有定義start和length,mybatis不會識別出#{start}是要傳入start還是length,所以要指定佔位符要傳入哪個參數
正確寫法1
雖然代碼沒有定義start和length,但是根據報錯可知,mybatis有默認的參數定義,arg0、arg1…,表明第一個參數、第二個參數…那麼可以通過這種已定義的參數名進行參數值傳入
/*多參數解決方法1*/
@Select("select * from user limit #{arg0},#{arg1}")
List<User> findByPage (int start, int length);
正確寫法2
既然原來的代碼報錯的原因是沒有定義參數名,如果我不改sql語句,如何實現功能。mybatis可以通過註解@Param
定義參數,注意:註解定義的參數名要與sql語句佔位符的名稱保持一致
/*多參數解決方法2,使用註解@Param定義方法參數
* 注意,註解定義的參數名要與sql語句佔位符的名稱保持一致*/
@Select("select * from user limit #{start},#{length}")
List<User> findByPage2 (@Param("start") int start,@Param("length") int length);
3.3 註解實現一對一映射及延遲加載
以Account與User的一對一關係爲例
3.3.1 dao層接口
3.3.1.1 IUserDao
public interface IUserDao {
/*實現查詢用戶功能*/
@Select("select * from user where id=#{id}")
User findById(int id);
}
3.3.1.2 IAccountDao
配置一對一關係的註解標籤
public interface IAccountDao {
/**
* 使用註釋方法實現一對一和一對多關係
* @Results 建立多個查詢列與對象屬性的映射關係,格式:@Results({@Result(...),@Result(...),...})。如果查詢列只有一個,大括號可以省略
* @Result 建立每一個查詢列與對象屬性的映射關係
* - id 標記主鍵字段,默認爲false.如果是主鍵字段,手動設置爲true
* - property 對象屬性
* - column 對象屬性對應的查詢列
* - javaType 對象屬性類型,注意寫法:類名.class。與配置文件寫法不同。可以省略
* - one 此屬性表示一對一關係
* @One one屬性的值的類型,就是一個註解類型,表示一對一
* - select 延遲加載查詢。對應用戶接口中,根據用戶id查詢用戶的方法,格式:接口類全名.方法名。
* - fetchType 是否開啓延遲加載支持
*/
@Select("select * from account")
@Results({
@Result(id = true,property = "accountId",column = "accountId"),
@Result(property = "uid",column = "uid"),
@Result(property = "money",column = "money"),
//配置一對一關係
@Result(property = "user",column = "uid",javaType = User.class,
one = @One(select = "com.azure.dao.IUserDao.findById",fetchType= FetchType.LAZY))
})
List<Account> findAll();
}
注意事項
- 註解寫法與配置文件寫法有細微的差別,必須要留意寫法的不同。例如,註解寫法只有javaType屬性,而配置文件有javaType和ofType兩種屬性。等等。
3.3.2 小結
3.4 註解實現一對多映射及延遲加載
以用戶與賬戶一對多爲例
3.4.1 dao接口
3.4.1.1 IAccountDao
/*
根據用戶id查詢賬戶
*/
@Select("select * from account where uid=#{uid}")
List<Account> findByUserId(int uid);
3.4.1.2 IUserDao
配置一對多關係
/**
* 使用註釋方法實現一對多關係
* @Results 建立多個查詢列與對象屬性的映射關係,格式:@Results({@Result(...),@Result(...),...})。如果查詢列只有一個,大括號可以省略
* @Result 建立每一個查詢列與對象屬性的映射關係
* - id 標記主鍵字段,默認爲false.如果是主鍵字段,手動設置爲true
* - property 對象屬性
* - column 對象屬性對應的查詢列
* - javaType 對象屬性類型。與配置文件寫法不同,沒有ofType屬性。且屬性類型是List,javaType的值爲List.class,而不是User!可以省略
* - many 此屬性表示一對多關係
* @Many many屬性的值的類型,就是一個註解類型,表示一對多
* - select 延遲加載查詢。對應用戶接口中,根據用戶id查詢用戶的方法,格式:接口類全名.方法名。
* - fetchType 是否開啓延遲加載支持
*/
@Select("select * from user")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "sex",column = "sex"),
@Result(property = "address",column = "address"),
//配置一對多關係
@Result(property = "accounts",column = "id",javaType = List.class,
many = @Many(select = "com.azure.dao.IAccountDao.findByUserId",fetchType = FetchType.LAZY))
})
List<User> findAllUsers();
疑問
爲何配置一對一關係中,@Result(property = "user",column = "uid",javaType = User.classs,one = @One(...)
,column的值是uid?
解答:
1、column是傳入給@One中方法的參數,而One的方法是findById,所需要的參數是id。
2、但是要注意,findById查詢的是user表,user表自然有id列,而column是當前account表的查詢列,account表只有accountId、uid、money三個查詢列,沒有id列。如果此時column的值寫成id,mybatis沒有從account表中找到id列,就會報錯java.lang.NullPointerException。
3、留意到user表的id和account表的uid是主外鍵關係,可以通過uid傳入id值,所以這裏寫的是uid。
4、一對多關係的column值同理