MyBatis筆記 MyBatis介紹 MyBatis 是一個持久層框架 前身是ibatis, 在ibatis3.x 時,更名為MyBatis MyBatis 在java 和sql 之間提供更靈活的映射方案 mybatis 可以將對數據表的操作(sql,方法)等等直接剝離,寫到xml 配置文件,實現 ...
MyBatis筆記
MyBatis介紹
MyBatis 是一個持久層框架
- 前身是ibatis, 在ibatis3.x 時,更名為MyBatis
- MyBatis 在java 和sql 之間提供更靈活的映射方案
- mybatis 可以將對數據表的操作(sql,方法)等等直接剝離,寫到xml 配置文件,實現和java
代碼的解耦 - mybatis 通過SQL 操作DB, 建庫建表的工作需要程式員完成
相關文檔
MyBatis 中文手冊、
https://mybatis.org/mybatis-3/zh/index.html
https://mybatis.net.cn/
為什麼需要MyBatis?
- 傳統jdbc連接資料庫需要自己編寫,不統一標準
- 程式不是OOP的方式編寫的
- SQL語句是寫在程式中,屬於硬編碼,沒有解耦
- mybatis 可以將對數據表的操作(sql,方法)等等直接剝離,寫到xml 配置文件,實現和java
代碼的解耦 - MyBatis 是一個持久層框架所以它統一
- MyBatis 是OOP方式操作資料庫
MyBatis案例
實體類
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Monk {
private Integer id;
private String nickname;
private String skill;
private String grade;
private Double salary;
private String birthTime;
private Date entry;
}
mapper介面
- 只是一個介面
- 該介面用於聲明操作monster表的方法
- 這些方法可以通過註解或者xml文件實現
public interface MonkMapper {
void addMonk(Monk monk);
}
mapper.xml
- 這是一個mapper xml 文件
- 該文件可以去實現對應的介面的方法
- namespace 指定該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="com.code_study.mapper.MonkMapper">
<!-- 配置addMonk
1. id="addMonk" 就是介面的方法名
2. parameterType="com.code_study.entity.Monk" 放入的形參
3. parameterType="com.code_study.entity.Monk" 可以簡寫
4. (#{nickname},#{skill},#{grade},#{salary},#{birthTime},#{entry})是從傳入的monk對象屬性來的
5. #{nickname}對應 monk對象的 private String nickname;
-->
<insert id="addMonk" parameterType="Monk">
INSERT INTO `monk`
(`nickname`,`skill`,`grade`,`salary`,`birthTime`,`entry`)
VALUES(#{nickname},#{skill},#{grade},#{salary},#{birthTime},#{entry})
</insert>
</mapper>
原生的API和註解的方式
Mybatis原生的API調用
添加
@Test
public void myBatisNativeCrud(){
Monk monk = new Monk();
monk.setBirthTime("1999-9-9 10:11:02");
Calendar instance = Calendar.getInstance();
instance.set(Calendar.YEAR,2020);
instance.set(Calendar.MONTH,Calendar.OCTOBER);
instance.set(Calendar.DAY_OF_MONTH,15);
Date time = instance.getTime();
monk.setEntry(time);
monk.setGrade("大宗師");
monk.setNickname("法海");
monk.setSalary(200.5);
monk.setSkill("大威天龍");
int insert = sqlSession.insert("com.code_study.mapper.MonkMapper.addMonk", monk);
System.out.println("insert~~" + insert);
System.out.println("操作成功");
}
刪除
@Test
public void myBatisNativeCrud(){
int delete = sqlSession.delete("com.code_study.mapper.MonkMapper.deleteMonk", 6);
System.out.println("delete~~" + delete);
System.out.println("操作成功");
}
修改
@Test
public void myBatisNativeCrud(){
Monk monk = new Monk();
monk.setBirthTime("1999-9-9 10:11:02");
Calendar instance = Calendar.getInstance();
instance.set(Calendar.YEAR,2020);
instance.set(Calendar.MONTH,Calendar.OCTOBER);
instance.set(Calendar.DAY_OF_MONTH,15);
Date time = instance.getTime();
monk.setEntry(time);
monk.setGrade("大宗師");
monk.setNickname("法海");
monk.setSalary(200.5);
monk.setSkill("乾坤大挪移");
monk.setId(8);
int update = sqlSession.update("com.code_study.mapper.MonkMapper.updateMonk", monk);
System.out.println("update~~" + update);
System.out.println("操作成功");
}
查詢
@Test
public void myBatisNativeCrud(){
List<Monk> monks =
sqlSession.selectList("com.code_study.mapper.MonkMapper.findAllMonk");
for (Monk monk : monks) {
System.out.println("monk= "+ monk);
}
if (sqlSession != null){
sqlSession.commit();
sqlSession.close();
}
}
Mybatis註解的方式操作
添加
/*
useGeneratedKeys = true : 表示可以返回自增長的值
keyProperty = "id" : 表示 自增值對應對象的哪個屬性
keyColumn = "id" : 表示 自增值對應表的哪個欄位
如果 keyProperty = "id" 和 keyColumn = "id" 一致,可以只保留 keyProperty = "id"
*/
@Insert(value = "INSERT INTO `monk` " +
" (`nickname`,`skill`,`grade`,`salary`,`birthTime`,`entry`) " +
" VALUES(#{nickname},#{skill},#{grade},#{salary},#{birthTime},#{entry})")
@Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")
void addMonk(Monk monk);
刪除
@Delete(value = " DELETE FROM `monk` WHERE id = #{id}")
void deleteMonk(Integer id);
修改
@Update(value = " UPDATE `monk` " +
" SET `nickname`=#{nickname}, " +
" `skill`=#{skill}, " +
" `grade`=#{grade}, " +
" `salary`=#{salary}, " +
" `birthTime`=#{birthTime}, " +
" `entry`=#{entry} " +
" WHERE id = #{id}")
void updateMonk(Monk monk);
查詢單個
//查詢-根據id
@Select(value = " select * from `monk` where id = #{id}")
Monk getMonkById(Integer id);
查詢多個
//查詢所有的Monster
@Select(value = " select * from `monk`")
List<Monk> findAllMonk();
mybatis-config.xml配置文件詳解
基本介紹
- mybatis 的核心配置文件(mybatis-config.xml),比如配置jdbc 連接信息,註冊mapper
等等
properties 屬性
- 通過該屬性,可以指定一個外部的jdbc.properties 文件,引入我們的jdbc 連接信息
<!--引入 外部 jdbc.properties-->
<properties resource="jdbc.properties"/>
settings 全局參數定義
- 是MyBatis 中極為重要的調整設置,它們會改變 MyBatis 的運行時行為。
typeAliases 別名處理器
-
別名是為Java 類型命名一個短名字。它只和XML 配置有關,用來減少類名重覆的部分
-
如果指定了別名,我們的MappperXxxx.xml 文件就可以做相應的簡化處理
-
註意指定別名後,還是可以使用全名的
-
可以直接指向包,這樣包下的所有類都是取類名作為別名
<typeAliases>
<!--<typeAlias type="com.code_study.entity.Monk" alias="Monk"/>-->
<package name="com.code_study.entity"/>
</typeAliases>
typeHandlers 類型處理器
-
MyBatis 在設置預處理語句(PreparedStatement)中的參數或從結果集中取出一個值時,都會用類型處理器將獲取到的值以合適的方式轉換成 Java 類型。
-
用於java 類型和jdbc 類型映射
-
Mybatis 的映射基本已經滿足,不太需要重新定義
-
這個我們使用預設即可,也就是mybatis 會自動的將java 和jdbc 類型進行轉換.
environments 環境
resource 註冊Mapper 文件:
<!--配置需要管理的 Mapper.xml文件註意路徑resource="" 是斜杠-->
<mappers>
<mapper resource="com/code_study/mapper/MonsterMapper.xml"/>
</mappers>
class:介面註解實現
<mappers>
<mapper class="com.code_study.mapper.MonkAnnotation"/>
</mappers>
url:外部路徑,使用很少,不推薦
<mappers>
<mapper url="file:///D:\yy\kk\yy\MOnsterMapper.xml" />
</mappers>
package 方式註冊
<mappers>
<package name="com.cody_study.mapper"/>
</mappers>
XxxMapper.xml SQL映射文件
基本介紹
MyBatis 的真正強大在於它的語句映射(在XxxMapper.xml 配置), 由於它的異常強大, 如
果拿它跟具有相同功能的JDBC 代碼進行對比,你會立即發現省掉了將近95% 的代碼。
MyBatis 致力於減少使用成本,讓用戶能更專註於SQL 代碼。
SQL 映射文件常用的幾個頂級元素
- cache – 該命名空間的緩存配置。
- cache-ref – 引用其它命名空間的緩存配置。
- resultMap – 描述如何從資料庫結果集中載入對象,是最複雜也是最強大的元素。
- parameterType - 將會傳入這條語句的參數的類全限定名或別名
- sql – 可被其它語句引用的可重用語句塊。
- insert – 映射插入語句。
- update – 映射更新語句。
- delete – 映射刪除語句。
- select – 映射查詢語句。
parameterType(輸入參數類型)
- 傳入簡單類型,比如按照id 查Monster
- 傳入POJO 類型,查詢時需要有多個篩選條件
- 當有多個條件時,傳入的參數就是Pojo 類型的Java 對象
- 當傳入的參數類是String 時,也可以使用${} 來接收參數
<!--形參是 String 用 ${ } 不再用 #{ }-->
<!--實現 public List<Monk> findMonsterByName(String name);-->
<select id="findMonsterByName" parameterType="String" resultType="Monk">
SELECT * FROM `monk` WHERE `nickname` LIKE '%${name}%'
</select>
- 傳入的是List
時,resultType=“T”類型
<!-- 實現 public List<Monster> findMonsterByNameORId(Monster monster); -->
<select id="findMonsterByNameORId" parameterType="Monk" resultType="Monk">
SELECT * FROM `monk` WHERE `id` = #{id} OR `nickname` = #{nickname}
</select>
傳入HashMap
- HashMap 傳入參數更加靈活,比如可以靈活的增加查詢的屬性,而不受限於Monster 這
個Pojo 屬性本身 - parameterType="map"
<select id="findMonsterByIdAndSalary_PrameterHashMap" parameterType="map" resultType="Monk">
SELECT * FROM `monk` WHERE `id` > #{id} AND `salary` > #{salary}
</select>
傳入和返回HashMap
- parameterType="map"
- resultType="map"
<select id="findMonkByIdAndSalary_ParameterHashMap_ReturnHashMap"
parameterType="map" resultType="map">
SELECT * FROM `monk` WHERE `id` > #{id} AND `salary` > #{salary}
</select>
resultMap(結果集映射)
基本介紹
當實體類的屬性和表的欄位名字不一致時,我們可以通過resultMap 進行映射,從而屏蔽
實體類屬性名和表的欄位名的不同.(作用)
案例
public interface UserMapper {
//查詢所有的User
public List<User> findAllUser();
}
<!--
說明:
resultMap:表示 定義一個 resultMap
id="findAllUserMap" :表示 程式員指定的 resultMap id,之後通過id可以使用它
type="User": 表示需要返回的對象類型
<result column="user_email" property="useremail"/> :表示 表欄位 user_email 和 對象屬性 useremail 之間的映射關係
-->
<!--查詢所有的User-->
<!--public List<User> findAllUser();-->
<resultMap id="findAllUserMap" type="User">
<result column="user_email" property="useremail"/>
<result column="user_name" property="username"/>
</resultMap>
<!--使用的是resultMap屬性而不是resultType或其他屬性。
resultMap="findAllUserMap" 表示 使用我們定義的 resultMap , 通過id="findAllUserMap" 關聯
-->
<select id="findAllUser" resultMap="findAllUserMap" >
SELECT * FROM `user`
</select>
動態SQL語句
基本介紹
- 在一個實際的項目中,sql 語句往往是比較複雜的
- 為了滿足更加複雜的業務需求,MyBatis 的設計者,提供了動態生成SQL 的功能。
- 動態SQL 是MyBatis 的強大特性之一
- 使用JDBC 或其它類似的框架,根據不同條件拼接SQL 語句非常麻煩,例如拼接時要
- 確保不能忘記添加必要的空格,還要註意去掉列表最後一個列名的逗號等
- SQL 映射語句中的強大的動態SQL 語言, 可以很好的解決這個問題.
- 最重要的是解決一些普通的SQL無法處理的帶有複雜邏輯的語句
動態SQL 常用標簽
動態SQL 提供瞭如下幾種常用的標簽,類似我們Java 的控制語句:
- if [判斷]
- where [拼接where 子句]
- choose/when/otherwise [類似java 的switch 語句, 註意是單分支]
- foreach [類似in ]
- trim [替換關鍵字/定製元素的功能]
- set [在update 的set 中,可以保證進入set 標簽的屬性被修改,而沒有進入set 的,保
持原來的值]
if [判斷]
<!-- 配置方法 public List<Monk> findMonkByAage(Integer age);-->
<select id="findMonkById" parameterType="Integer" resultType="Monk">
SELECT * FROM `monk` WHERE 1 = 1
<if test="id >= 0">
AND id > #{id}
</if>
</select>
where [拼接where 子句]
<!-- 如果入參是對象,test表達式中 直接使用屬性名即可 例如: test="id>=0" -->
<!-- where標簽 會在組織動態sql時,加上where,並去掉多餘的 AND-->
<select id="findMonkByIdAndName" parameterType="Monk" resultType="Monk">
SELECT * FROM `monk`
<where>
<if test="id>=0">
AND id > #{id}
</if>
<if test="nickname != null and nickname != ''">
AND `nickname` = #{nic kname}
</if>
</where>
</select>
choose/when/otherwise [類似java 的switch 語句, 註意是單分支]
<!-- 配置方法 List<Monk> findMonsterByIdAndName_choose(Map<String, Object> map);-->
<select id="findMonkByIdOrName_choose" parameterType="map" resultType="Monk">
select * from `monk`
<choose >
<when test="nickname != null and nickname !='' ">
where nickname = #{nickname}
</when>
<when test="id > 0 ">
where id = #{id}
</when>
<otherwise>
where salary > 10000
</otherwise>
</choose>
</select>
foreach [類似in ]
<select id="findMonkById_forEach" parameterType="map" resultType="Monk">
SELECT * FROM `monk`
<if test="ids != null and ids != ''">
<where>
id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</if>
</select>
trim [替換關鍵字/定製元素的功能]
<!-- 配置方法 List<Monk> findMonkByName_Trim(Map<String, Object> map);-->
<select id="findMonkByName_Trim" parameterType="map" resultType="Monk">
SELECT * FROM `monk`
<trim prefix="WHERE" prefixOverrides="AND|OR|ZY">
<if test="id>=0">
ZY id > #{id}
</if>
<if test="nickname != null and nickname != ''">
AND `nickname` = #{nickname}
</if>
</trim>
</select>
set [在update 的set 中,可以保證進入set 標簽的屬性被修改,而沒有進入set 的,保
持原來的值]
<!-- 配置方法 void updateMonster_set(Map<String, Object> map);-->
<update id="updateMonster_set" parameterType="map">
UPDATE `monk`
<set>
<if test="grade != null and grade != ''">
`grade` = #{grade},
</if>
<if test="skill != null and skill != ''">
`skill` = #{skill},
</if>
<if test="nickname != null and nickname != ''">
`nickname` = #{nickname},
</if>
<if test="grade != null and grade != ''">
`grade` = #{grade},
</if>
<if test="salary != null and salary != ''">
`salary` = #{salary},
</if>
<if test="birthTime != null and birthTime != ''">
`birthTime` = #{birthTime},
</if>
<if test="entry != null and entry != ''">
`entry` = #{entry},
</if>
</set>
WHERE id = #{id}
</update>
映射關係一對一
基本介紹
- 項目中1 對1 的關係是一個基本的映射關係,比如:Person(人) --- IDCard(身份證
映射方式
-
通過配置XxxMapper.xml 實現1 對1 [配置方式]
-
通過註解的方式實現1 對1 [註解方式]
映射方式一:配置Mapper.xml實現
- 通過配置XxxMapper.xml 的方式,實現級聯查詢,通過person 可以獲取到對應的idencard 信息
介面:
public interface PersonMapper {
//通過Person 的id 獲取到Person,包括這個Person 關聯的IdenCard 對象
Person getPersonById(Integer id);
//通過Person 的id 獲取到Person,包括這個Person 管理的IdenCard 對象,方式2
Person getPersonById2(Integer id);
//通過Person 的id 獲取到Person
Person getPerson(Integer id);
}
public interface IdenCardMapper {
//根據id 獲取到身份證序列號
IdenCard getIdenCardById(Integer id);
//通過IdenCard 的id 獲取到Person
IdenCard getIdenCardAndPerson(Integer id);
}
mapper.xml:
通過person 可以獲取到對應的idencard 信息
方式一 :多表聯查
<!-- 通過Person 的id 獲取到Person,包括這個Person 關聯的IdenCard 對象-->
<resultMap id="PersonResultMap" type="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--
property="card" 表示 person對象的card屬性
javaType="IdenCard" 表示 card屬性 的類型
column="id" 是這條語句
select *
from `person` join `idencard` on person.card_id = idencard.id
where person.id = #{id}
查詢返回的欄位
-->
<association property="card" javaType="IdenCard">
<!--標記出作為id的結果 可以幫助提高整體性能-->
<!--property="id"表示 person的屬性 id ;column="id" 表示 對應表的欄位 id ,通常是主鍵-->
<id property="id" column="id"/>
<result property="card_sn" column="card_sn"/>
</association>
</resultMap>
<select id="getPersonById" parameterType="Integer" resultMap="PersonResultMap">
select *
from `person` join `idencard` on person.card_id = idencard.id
where person.id = #{id}
</select>
方式二:分解成單表操作
<resultMap id="PersonResultMap2" type="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="card" column="card_id"
select="com.code_study.mapper.IdenCardMapper.getIdenCardById"/>
<!-- column="card_id"是 下麵 SELECT * FROM `person` where `id` = #{id} 返回的欄位
card_id 會作為getIdenCardById 入參,執行
-->
</resultMap>
<select id="getPersonById2" parameterType="Integer" resultMap="PersonResultMap2">
SELECT * FROM `person` where `id` = #{id}
</select>
映射方式二:註解實現
public interface PersonMapperAnnotation {
@Select(value = "SELECT * FROM `person` where `id` = #{id}")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "name",column = "name"),
@Result(property = "card",column = "card_id",
one = @One(select = "com.code_study.mapper.IdenCardMapperAnnotation.getIdenCardById"))
})
Person getPersonById(Integer id);
}
映射關係多對一
基本介紹
- 項目中多對1 的關係是一個基本的映射關係, 多對1, 也可以理解成是1 對多.
- User --- Pet: 一個用戶可以養多只寵物
- Dep ---Emp : 一個部門可以有多個員工
映射方式
-
通過配置XxxMapper.xml 實現1 對1 [配置方式]
-
通過註解的方式實現1 對1 [註解方式]
映射方式一:配置Mapper.xml實現
介面:
public interface UserMapper {
//通過id 獲取User 對象
public User getUserById(Integer id);
}
public interface PetMapper {
//通過User 的id 來獲取pet 對象,可能有多個,因此使用List 接收
public List<Pet> getPetByUserId(Integer userId);
//通過pet 的id 獲取Pet 對象
public Pet getPetById(Integer id);
}
mapper.xml
<mapper namespace="com.code_study.mapper.PetMapper">
<!--通過User 的id 來獲取pet 對象,可能有多個,因此使用List 接收
public List<Pet> getPetByUserId(Integer userId);
-->
<resultMap id="PetResultMap" type="Pet">
<id property="id" column="id"/>
<result property="nickname" column="nickname"/>
<association property="user" column="user_id"
select="com.code_study.mapper.UserMapper.getUserById"/>
</resultMap>
<select id="getPetByUserId" parameterType="Integer" resultMap="PetResultMap">
SELECT * FROM `mybatis_pet` WHERE user_id = #{userId}
</select>
<!--通過pet 的id 獲取Pet 對象-->
<!--public Pet getPetById(Integer id);-->
<select id="getPetById" parameterType="Integer" resultMap="PetResultMap">
select * from `mybatis_pet` where id = #{id}
</select>
</mapper>
<mapper namespace="com.code_study.mapper.UserMapper">
<!--ofType="Pet" : 表示 返回的集合中存放的數據類型是 Pet-->
<resultMap id="PetsResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="pets" column="id"
ofType="Pet" select="com.code_study.mapper.PetMapper.getPetByUserId" />
</resultMap>
<select id="getUserById" parameterType="Integer" resultMap="PetsResultMap">
SELECT * from `mybatis_user` WHERE id = #{id}
</select>
</mapper>
說明:
- 多對一時,resultMap中 collection 標簽表示集合
- 在 collection 標簽中 屬性 ofType 表示集合中存放的數據類型
- 雙向多對一時,toString應該去掉,避免棧溢出
- resultMap中 select 標簽表示覆用這個目標方法,推薦使用,這樣提高代碼的復用性,將多表拆解成單表
映射方式二:註解實現
public interface UserMapperAnnotation {
//通過id 獲取User 對象
@Select(value = "SELECT * from `mybatis_user` WHERE id = #{id}")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "name",column = "name"),
@Result(property = "pets",column = "id",
many = @Many(select = "com.code_study.mapper.PetMapperAnnotation.getPetByUserId"))
})
public User getUserById(Integer id);
}
public interface PetMapperAnnotation {
//通過User 的id 來獲取pet 對象,可能有多個,因此使用List 接收
@Select(value = "SELECT * FROM `mybatis_pet` WHERE user_id = #{userId}")
@Results(id = "PetResultMap",value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "nickname",column = "nickname"),
@Result(property = "user",column = "user_id",
one = @One(select = "com.code_study.mapper.UserMapperAnnotation.getUserById"))
})
List<Pet> getPetByUserId(Integer userId);
//通過pet 的id 獲取Pet 對象
@Select(value = " select * from `mybatis_pet` where id = #{id}")
@ResultMap(value = "PetResultMap")
Pet getPetById(Integer id);
}
- @Results(id = "PetResultMap"...)可以給@Results設置一個 id 用來複用代碼
- 在下麵就進行了@Results 的復用
//通過pet 的id 獲取Pet 對象
@Select(value = " select * from `mybatis_pet` where id = #{id}")
@ResultMap(value = "PetResultMap")
Pet getPetById(Integer id);
MyBatis緩存
基本介紹
- MyBatis 內置了一個強大的事務性查詢緩存機制,它可以非常方便地配置和定製。
相關文檔
一級緩存
-
預設情況下,mybatis 是啟用一級緩存的/本地緩存/local Cache,它是SqlSession 級別的。
-
同一個SqlSession 介面對象調用了相同的select 語句,會直接從緩存裡面獲取,而不是再
去查詢資料庫
一級緩存原理圖
一級緩存失效的情況
- 關閉sqlSession 會話後, 再次查詢,會到資料庫查詢
- 當執行sqlSession.clearCache() 會使一級緩存失效
- 當對同一個monster 修改,該對象在一級緩存會失效
二級緩存
- 二級緩存和一級緩存都是為了提高檢索效率的技術
- 最大的區別就是作用域的範圍不一樣,一級緩存的作用域是sqlSession 會話級別,在一次
會話有效,而二級緩存作用域是全局範圍,針對不同的會話都有效
二級緩存原理圖
二級緩存配置
1.在mybatis-config.xml中 全局性的開啟緩存
2.使用二級緩存時entity 類實現序列化介面(serializable),因為二級緩存可能使用到序列化技術
*大部分情況不需要這樣配置,只是某些第三方緩存庫需要序列化到磁碟!
3.再對應的XxxMapper.xml中設置二級緩存的策略
<mapper namespace="com.code_study.mapper.MonsterMapper">
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!--查詢-根據id-->
<select id="getMonsterById" resultType="Monster">
SELECT * FROM `monster` WHERE id = #{id}
</select>
</mapper>
註意事項
-
表示創建了FIFO 的策略,每隔30 秒刷新一次,最多存放360 個對象而且返回的對象被認為是只讀的。
-
eviction:緩存的回收策略
-
flushInterval:時間間隔,單位是毫秒,
-
size:引用數目,記憶體大就多配置點,要記住你緩存的對象數目和你運行環境的可用記憶體
-
資源數目。預設值是1024
-
readOnly:true,只讀
四大策略
- LRU – 最近最少使用:移除最長時間不被使用的對象。
- FIFO – 先進先出:按對象進入緩存的順序來移除它們。
- SOFT – 軟引用:基於垃圾回收器狀態和軟引用規則移除對象。
- WEAK – 弱引用:更積極地基於垃圾收集器狀態和弱引用規則移除對象
如何禁用二級緩存
方式一
<settings>
<!--全局性的開啟緩存-->
<setting name="cacheEnabled" value="false"/>
</settings>
方式二
在XxxMapper.xml文件中不配做
方式三
直接在XxxMapper.xml文件對應的方法上 設置屬性 useCache=false
<!--查詢所有的Monster-->
<select id="findAllMonster" resultType="Monster" useCache="false">
SELECT * FROM `monster`
</select>
一級緩存和二級緩存的執行順序
- 緩存執行順序是:二級緩存-->一級緩存-->資料庫
- 當我們關閉了一級緩存的時候,如果配置了二級緩存,那麼一級緩存的數據會放入二級緩存中
- 不會出現一級緩存和二級緩存中有同一個數據。因為二級緩存(數據)是在一級緩存關閉之後才有的
第三方緩存框架---EhCache
基本介紹
- EhCache 是一個純Java 的緩存框架,具有快速、精幹等特點
- MyBatis 有自己預設的二級緩存(前面我們已經使用過了),但是在實際項目中,往往使用
的是更加專業的第三方緩存產品作為MyBatis 的二級緩存,EhCache 就是非常優秀的緩存
產品
配置使用第三方緩存框架
添加依賴
<dependencies>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
在XxxMapper.xml文件中啟用EhCache
<mapper namespace="com.code_study.mapper.MonsterMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
<!--查詢-根據id-->
<select id="getMonsterById" resultType="Monster">
SELECT * FROM `monster` WHERE id = #{id}
</select>
</mapper>
如何理解EhCache 和MyBatis 緩存的關係
-
MyBatis 提供了一個介面Cacheorg.apache.ibatis.cache.Cache
-
只要實現了該Cache 介面,就可以作為二級緩存產品和MyBatis 整合使用,Ehcache 就
是實現了該介面 -
MyBatis 預設情況(即一級緩存)是使用的PerpetualCache 類實現Cache 介面的,是核心類
-
當我們使用了Ehcahce 後,就是EhcacheCache 類實現Cache 介面的,是核心類.
-
緩存的本質就是Map<Object,Object>
手寫MyBatis底層機制
讀取配置文件,得到資料庫連接
思路
- 引入必要的依賴
- 需要寫一個自己的config.xml文件,在裡面配置一些信息,driver,url ,password,username
- 需要編寫Configuration類,對 自己的config.xml文件 進行解析,得到一個資料庫連接
實現
- 引入必要的依賴
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
- 需要寫一個自己的config.xml文件,在裡面配置一些信息,driver,url ,password,username
<?xml version="1.0" encoding="UTF-8" ?>
<database>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/hsp_mybatis?
useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="zy"/>
</database>
- 需要編寫Configuration類,對 自己的config.xml文件 進行解析,得到一個資料庫連接
public class ZyConfiguration {
//屬性 類載入器
private ClassLoader classLoader =
ClassLoader.getSystemClassLoader();
//讀取xml文件信息並處理
public Connection build(String resource) {
Connection connection = null;
//載入配置文件,獲取對應的InputStream流
InputStream resourceAsStream =
classLoader.getResourceAsStream(resource);
//解析xml文件
SAXReader reader = new SAXReader();
try {
Document document = reader.read(resourceAsStream);
Element root = document.getRootElement();
//解析rootElement
System.out.println("root= "+root);
return evalDataSource(root);
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
//解析xml文件 並返回一個連接
private Connection evalDataSource(Element node) {
Iterator property = node.elementIterator("property");
String driverClassName = null;
String url = null;
String username = null;
String password = null;
//遍歷node子節點 獲取屬性值
while(property.hasNext()){
Element pro = (Element)property.next();
String name = pro.attributeValue("name");
String value = pro.attributeValue("value");
//判斷是否得到了name 和 value
if (name == null || value == null){
throw new RuntimeException("property 節點沒有設置name 或 value屬性");
}
switch (name){
case "driverClassName":
driverClassName = value;
break;
case "url":
url = value;
break;
case "username":
username = value;
break;
case "password":
password = value;
break;
default:
throw new RuntimeException("屬性名沒有匹配到");
}
}
Connection connection = null;
try {
Class.forName(driverClassName);
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
throw new RuntimeException(e);
}
return connection;
}
}
編寫執行器,輸入SQL語句,完成操作
思路
- 需要寫一個實體類,對應monster表
- 編寫介面executor
- 實現介面,編寫自己的執行器
- 需要一個 自己的Configuration類 返回連接,通過連接對資料庫進行操作
實現
- 需要寫一個實體類,對應monster表
@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Monster {
private Integer id;
private Integer age;
private String name;
private String email;
private Date birthday;
private double salary;
private Integer gender;
}
- 編寫介面executor
public interface Executor {
public <T> T query(String statement,Object parameter);
}
- 實現介面,編寫自己的執行器
public class ZyExecutor implements Executor{
private ZyConfiguration zyConfiguration = new ZyConfiguration();
@Override
public <T> T query(String sql, Object parameter) {
Connection connection = getConnection();
//查詢返回的結果集
ResultSet set = null;
PreparedStatement pre = null;
try {
pre = connection.prepareStatement(sql);
//設置參數,如果參數多,用數組處理
pre.setString(1, parameter.toString());
set = pre.executeQuery();
//把set數據封裝到對象 -- monster
Monster monster = new Monster();//簡化處理 認為返回的結果就是一個monster記錄
//遍歷結果集
while(set.next()){
monster.setId(set.getInt("id"));
monster.setName(set.getString("name"));
monster.setEmail(set.getString("email"));
monster.setAge(set.getInt("age"));
monster.setGender(set.getInt("gender"));
monster.setBirthday(set.getDate("birthday"));
monster.setSalary(set.getDouble("salary"));
}
return (T)monster;
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
try {
if (set != null) {
set.close();
}
if (pre != null) {
pre.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public Connection getConnection(){//Configuration類 返回連接,通過連接對資料庫進行操作
return zyConfiguration.build("zy_mybatis.xml");
}
}
- 需要一個 自己的Configuration類 返回連接,通過連接對資料庫進行操作
將Sqlsession封裝到執行器
思路
- 需要寫自己的Sqlsession類,它是搭建連接和執行器之間的橋梁,裡面封裝有 執行器 和 配置文件 以及 操作DB 的具體方法
- 寫一個selectOne方法 ,SelectOne() 返回一條記錄,一條記錄對應一個Monster對象
實現
- 需要寫自己的Sqlsession類,它是搭建連接和執行器之間的橋梁,裡面封裝有 執行器 和 配置文件 以及 操作DB 的具體方法
public class ZySqlSession {//搭建連接和執行器之間的橋梁
//執行器
private Executor executor = new ZyExecutor();
//配置
private ZyConfiguration zyConfiguration = new ZyConfiguration();
//操作DB 的具體方法
//SelectOne 返回一條記錄-對象
public <T> T selectOne(String statement,Object parameter){
return executor.query(statement,parameter);
}
}
- 寫一個selectOne方法 ,SelectOne() 返回一條記錄,一條記錄對應一個Monster對象
//操作DB 的具體方法
//SelectOne 返回一條記錄-對象
public <T> T selectOne(String statement,Object parameter){
return executor.query(statement,parameter);
}
開發Mapper介面和Mapper.xml
思路
- 編寫MonsterMapper介面,裡面有方法getMonsterById(Integer id)根據id返回一個monster對象
- 在resources下編寫對應的monsterMapper.xml(簡化:因為在resources 編譯時會在類路徑下比較好寫)
- monsterMapper.xml 編寫具體的sql語句,並指定語句類型,id,resultType(和原生Mybatis一樣)
實現
- 編寫MonsterMapper介面,裡面有方法getMonsterById(Integer id)根據id返回一個monster對象
public interface MonsterMapper {
public Monster getMonsterById(Integer id);
}
- 在resources下編寫對應的monsterMapper.xml(簡化:因為在resources 編譯時會在類路徑下比較好寫)
- monsterMapper.xml 編寫具體的sql語句,並指定語句類型,id,resultType(和原生Mybatis一樣)
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.code_study.mapper.MonsterMapper">
<!-- 實現配置介面方法 getMonsterById-->
<select id="getMonsterById" resultType="com.code_study.entity.Monster">
SELECT * FROM monster WHERE id = ?
</select>
</mapper>
開發MapperBean,可以和Mapper介面相映射
思路
- 開發 Function類 ,用於記錄對應的Mapper的方法信息,比如sql類型,方法名,執行的sql語句,返回類型,入參類型
- 開發 MapperBean類,記錄介面信息和介面下的所有方法
- Function類 對應 monsterMapper.xml中的信息
- MapperBean類 對應 MonsterMapper 介面中的信息
實現
- 開發 Function類 ,用於記錄對應的Mapper的方法信息,比如sql類型,方法名,執行的sql語句,返回類型,入參類型
//對應 monsterMapper.xml中的信息
public class Function {
private String sqlType;//sql類型,比如select,insert,update,delete
private String funcName;//方法名
private String sql;//執行的sql語句
private Object resultType;//返回類型
private String parameterType;//入參類型
}
- 開發 MapperBean類,記錄介面信息和介面下的所有方法
//對應 MonsterMapper 介面中的信息
public class MapperBean {
private String interfaceName;//介面名
// 介面下的所有方法
private List<Function> functions;
}
- Function類 對應 monsterMapper.xml中的信息
- MapperBean類 對應 MonsterMapper 介面中的信息
在Configuration中解析MapperXML獲取MapperBean對象
思路
- 在Configuration 添加方法readMapper(String path)
- 通過 path 讀取介面對應的Mapper方法
- 保存介面下所有的方法信息
- 封裝成 MapperBean對象
實現
- 在Configuration 添加方法readMapper(String path)
- 通過 path 讀取介面對應的Mapper方法
- 保存介面下所有的方法信息
- 封裝成 MapperBean對象
//解析MapperXML獲取MapperBean對象
//path = xml的路徑+文件名 是從類的載入路徑計算的(如果放在resource目錄下 之間傳xml文件名即可)
public MapperBean readMapper(String path) {
MapperBean mapperBean = new MapperBean();
InputStream resourceAsStream = classLoader.getResourceAsStream(path);
SAXReader reader = new SAXReader();
try {
Document document = reader.read(resourceAsStream);
Element root = document.getRootElement();
String namespace = root.attributeValue("namespace");
mapperBean.setInterfaceName(namespace);
List<Function> list = new ArrayList<>();//保存介面下所有的方法信息
//得到root的迭代器
Iterator iterator = root.elementIterator();
while(iterator.hasNext()){
Element e = (Element)iterator.next();
String sqlType = e.getName().trim();
String sql = e.getText().trim();
String funcName = e.attributeValue("id");
String resultType = e.attributeValue("resultType");
//ResultType 返回的是一個Object對象 ->反射
Object instance = Class.forName(resultType).newInstance();
//封裝 function 對象
Function function = new Function();
function.setSql(sql);
function.setSqlType(sqlType);
function.setFuncName(funcName);
function.setResultType(instance);
//將封裝好的function對象 放入 list中
list.add(function);
mapperBean.setFunctions(list);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return mapperBean;
}
動態代理Mapper方法
思路
- 在SqlSession中添加方法 getMapper 輸入一個Class類型,返回mapper的動態代理對象
- 編寫動態代理類 實現 InvocationHandler 介面
- 取出mapperBean的functions 遍歷
- 判斷 當前要執行的方法和function.getFunctionName是否一致
- 調用方法返回 動態代理對象
- 編寫SqlSessionFactory 會話工廠,可以返回SqlSession
實現
-
編寫動態代理類 實現 InvocationHandler 介面
-
在SqlSession中添加方法 getMapper 輸入一個Class類型,返回mapper的動態代理對象
//返回mapper的動態代理對象
public <T> T getMapper(Class<T> clazz){
return (T) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[]{clazz},
new ZyMapperProxy(zyConfiguration,clazz,this));
}
- 取出mapperBean的functions 遍歷
- 判斷 當前要執行的方法和function.getFunctionName是否一致
- 調用方法返回 動態代理對象
public class ZyMapperProxy implements InvocationHandler {
private ZySqlSession zySqlSession;
private String mapperFile;
private ZyConfiguration zyConfiguration;
public ZyMapperProxy(ZySqlSession zySqlSession, Class clazz, ZyConfiguration zyConfiguration) {
this.zySqlSession = zySqlSession;
this.zyConfiguration = zyConfiguration;
this.mapperFile = clazz.getSimpleName() + ".xml";
}
//當執行Mapper介面的代理對象方法時,會執行到invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperBean mapperBean = zyConfiguration.readMapper(this.mapperFile);
//判斷是否為當前xml文件對應的介面
if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())){
return null;
}
//取出mapperBean的functions
List<Function> functions = mapperBean.getFunctions();
//判斷當前的mapperBean 解析對應的MapperXML後,有方法
if (null != functions || 0 != functions.size()){
for (Function function : functions) {
//當前要執行的方法和function.getFunctionName
if (method.getName().equals(function.getFuncName())){
if ("SELECT".equalsIgnoreCase(function.getSqlType())){
return zySqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
}
}
}
}
return null;
}
}
- 編寫SqlSessionFactory 會話工廠,可以返回SqlSession
public class ZySqlSessionFactory {
public static ZySqlSession open(){
return new ZySqlSession();
}
}
測試
@Test
public void openSession(){
ZySqlSession zySqlSession = ZySqlSessionFactory.openSession();
System.out.println("zySqlSession= "+zySqlSession);
MonsterMapper mapper = zySqlSession.getMapper(MonsterMapper.class);
Monster monster = mapper.getMonsterById(1);
System.out.println("monster= "+monster);
}
本文學習內容來自韓順平老師的課程
僅供個人參考學習