MyBatis學習總結 + 【手寫MyBatis底層機制核心】

来源:https://www.cnblogs.com/zydevelop/p/18156219/zy_mybatis
-Advertisement-
Play Games

MyBatis筆記 MyBatis介紹 MyBatis 是一個持久層框架 前身是ibatis, 在ibatis3.x 時,更名為MyBatis MyBatis 在java 和sql 之間提供更靈活的映射方案 mybatis 可以將對數據表的操作(sql,方法)等等直接剝離,寫到xml 配置文件,實現 ...


MyBatis筆記

MyBatis介紹

MyBatis 是一個持久層框架

  1. 前身是ibatis, 在ibatis3.x 時,更名為MyBatis
  2. MyBatis 在java 和sql 之間提供更靈活的映射方案
  3. mybatis 可以將對數據表的操作(sql,方法)等等直接剝離,寫到xml 配置文件,實現和java
    代碼的解耦
  4. mybatis 通過SQL 操作DB, 建庫建表的工作需要程式員完成

相關文檔

MyBatis 中文手冊、

https://mybatis.org/mybatis-3/zh/index.html
https://mybatis.net.cn/

為什麼需要MyBatis?

  1. 傳統jdbc連接資料庫需要自己編寫,不統一標準
  2. 程式不是OOP的方式編寫的
  3. SQL語句是寫在程式中,屬於硬編碼,沒有解耦
  4. mybatis 可以將對數據表的操作(sql,方法)等等直接剝離,寫到xml 配置文件,實現和java
    代碼的解耦
  5. MyBatis 是一個持久層框架所以它統一
  6. 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介面

  1. 只是一個介面
  2. 該介面用於聲明操作monster表的方法
  3. 這些方法可以通過註解或者xml文件實現
public interface MonkMapper {

    void addMonk(Monk monk);
}

mapper.xml

  1. 這是一個mapper xml 文件
  2. 該文件可以去實現對應的介面的方法
  3. 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 別名處理器

  1. 別名是為Java 類型命名一個短名字。它只和XML 配置有關,用來減少類名重覆的部分

  2. 如果指定了別名,我們的MappperXxxx.xml 文件就可以做相應的簡化處理

  3. 註意指定別名後,還是可以使用全名的

  4. 可以直接指向包,這樣包下的所有類都是取類名作為別名

<typeAliases>
    <!--<typeAlias type="com.code_study.entity.Monk" alias="Monk"/>-->
    <package name="com.code_study.entity"/>
 </typeAliases>

typeHandlers 類型處理器

  1. MyBatis 在設置預處理語句(PreparedStatement)中的參數或從結果集中取出一個值時,都會用類型處理器將獲取到的值以合適的方式轉換成 Java 類型。

  2. 用於java 類型和jdbc 類型映射

  3. Mybatis 的映射基本已經滿足,不太需要重新定義

  4. 這個我們使用預設即可,也就是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 映射文件常用的幾個頂級元素

  1. cache – 該命名空間的緩存配置。
  2. cache-ref – 引用其它命名空間的緩存配置。
  3. resultMap – 描述如何從資料庫結果集中載入對象,是最複雜也是最強大的元素。
  4. parameterType - 將會傳入這條語句的參數的類全限定名或別名
  5. sql – 可被其它語句引用的可重用語句塊。
  6. insert – 映射插入語句。
  7. update – 映射更新語句。
  8. delete – 映射刪除語句。
  9. select – 映射查詢語句。

parameterType(輸入參數類型)

  1. 傳入簡單類型,比如按照id 查Monster
  2. 傳入POJO 類型,查詢時需要有多個篩選條件
  3. 當有多個條件時,傳入的參數就是Pojo 類型的Java 對象
  4. 當傳入的參數類是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語句

基本介紹

  1. 在一個實際的項目中,sql 語句往往是比較複雜的
  2. 為了滿足更加複雜的業務需求,MyBatis 的設計者,提供了動態生成SQL 的功能。
  3. 動態SQL 是MyBatis 的強大特性之一
  4. 使用JDBC 或其它類似的框架,根據不同條件拼接SQL 語句非常麻煩,例如拼接時要
  5. 確保不能忘記添加必要的空格,還要註意去掉列表最後一個列名的逗號等
  6. SQL 映射語句中的強大的動態SQL 語言, 可以很好的解決這個問題.
  7. 最重要的是解決一些普通的SQL無法處理的帶有複雜邏輯的語句

動態SQL 常用標簽

動態SQL 提供瞭如下幾種常用的標簽,類似我們Java 的控制語句:

  1. if [判斷]
  2. where [拼接where 子句]
  3. choose/when/otherwise [類似java 的switch 語句, 註意是單分支]
  4. foreach [類似in ]
  5. trim [替換關鍵字/定製元素的功能]
  6. 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(身份證

映射方式

  1. 通過配置XxxMapper.xml 實現1 對1 [配置方式]

  2. 通過註解的方式實現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, 也可以理解成是1 對多.
  2. User --- Pet: 一個用戶可以養多只寵物
  3. Dep ---Emp : 一個部門可以有多個員工

映射方式

  1. 通過配置XxxMapper.xml 實現1 對1 [配置方式]

  2. 通過註解的方式實現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>
說明:
  1. 多對一時,resultMap中 collection 標簽表示集合
  2. 在 collection 標簽中 屬性 ofType 表示集合中存放的數據類型
  3. 雙向多對一時,toString應該去掉,避免棧溢出
  4. 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 內置了一個強大的事務性查詢緩存機制,它可以非常方便地配置和定製。

相關文檔

一級緩存

  1. 預設情況下,mybatis 是啟用一級緩存的/本地緩存/local Cache,它是SqlSession 級別的。

  2. 同一個SqlSession 介面對象調用了相同的select 語句,會直接從緩存裡面獲取,而不是再
    去查詢資料庫

一級緩存原理圖

一級緩存失效的情況

  1. 關閉sqlSession 會話後, 再次查詢,會到資料庫查詢
  2. 當執行sqlSession.clearCache() 會使一級緩存失效
  3. 當對同一個monster 修改,該對象在一級緩存會失效

二級緩存

  1. 二級緩存和一級緩存都是為了提高檢索效率的技術
  2. 最大的區別就是作用域的範圍不一樣,一級緩存的作用域是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,只讀

四大策略

  1. LRU – 最近最少使用:移除最長時間不被使用的對象。
  2. FIFO – 先進先出:按對象進入緩存的順序來移除它們。
  3. SOFT – 軟引用:基於垃圾回收器狀態和軟引用規則移除對象。
  4. 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

基本介紹

  1. EhCache 是一個純Java 的緩存框架,具有快速、精幹等特點
  2. 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 緩存的關係

  1. MyBatis 提供了一個介面Cacheorg.apache.ibatis.cache.Cache

  2. 只要實現了該Cache 介面,就可以作為二級緩存產品和MyBatis 整合使用,Ehcache 就
    是實現了該介面

  3. MyBatis 預設情況(即一級緩存)是使用的PerpetualCache 類實現Cache 介面的,是核心類

  4. 當我們使用了Ehcahce 後,就是EhcacheCache 類實現Cache 介面的,是核心類.

  5. 緩存的本質就是Map<Object,Object>

手寫MyBatis底層機制

讀取配置文件,得到資料庫連接

思路

  1. 引入必要的依賴
  2. 需要寫一個自己的config.xml文件,在裡面配置一些信息,driver,url ,password,username
  3. 需要編寫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&amp;useUnicode=true&amp;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語句,完成操作

思路

  1. 需要寫一個實體類,對應monster表
  2. 編寫介面executor
  3. 實現介面,編寫自己的執行器
  4. 需要一個 自己的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封裝到執行器

思路

  1. 需要寫自己的Sqlsession類,它是搭建連接和執行器之間的橋梁,裡面封裝有 執行器 和 配置文件 以及 操作DB 的具體方法
  2. 寫一個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

思路

  1. 編寫MonsterMapper介面,裡面有方法getMonsterById(Integer id)根據id返回一個monster對象
  2. 在resources下編寫對應的monsterMapper.xml(簡化:因為在resources 編譯時會在類路徑下比較好寫)
  3. 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介面相映射

思路

  1. 開發 Function類 ,用於記錄對應的Mapper的方法信息,比如sql類型,方法名,執行的sql語句,返回類型,入參類型
  2. 開發 MapperBean類,記錄介面信息和介面下的所有方法
  3. Function類 對應 monsterMapper.xml中的信息
  4. 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對象

思路

  1. 在Configuration 添加方法readMapper(String path)
  2. 通過 path 讀取介面對應的Mapper方法
  3. 保存介面下所有的方法信息
  4. 封裝成 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方法

思路

  1. 在SqlSession中添加方法 getMapper 輸入一個Class類型,返回mapper的動態代理對象
  2. 編寫動態代理類 實現 InvocationHandler 介面
  3. 取出mapperBean的functions 遍歷
  4. 判斷 當前要執行的方法和function.getFunctionName是否一致
  5. 調用方法返回 動態代理對象
  6. 編寫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);
}

本文學習內容來自韓順平老師的課程

僅供個人參考學習


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • /******************************************************************************************************** * * file name: Zqh_順序表.c * author : keyword2 ...
  • 一、定義 在class的聲明裡頭,真正有用的兩樣東西是data members 和 member functions: Data members:表示根據這個class所產生的object裡頭會有些什麼東西,它事實上也是占據object記憶體的唯一東西(除非引入虛擬機制)。通常為數據的封裝性,我們把d ...
  • title: Django高級表單處理與驗證實戰 date: 2024/5/6 20:47:15 updated: 2024/5/6 20:47:15 categories: 後端開發 tags: Django表單 驗證邏輯 模板渲染 安全措施 表單測試 重定向管理 最佳實踐 引言: 在Web應用開 ...
  • NumPy 創建數組 NumPy 中的核心數據結構是 ndarray,它代表多維數組。NumPy 提供了多種方法來創建 ndarray 對象,包括: 使用 array() 函數 array() 函數是最常用的方法之一,它可以將 Python 列表、元組甚至其他數組轉換為 ndarray 對象。 語法 ...
  • 刪除A中與B相同的元素 目錄刪除A中與B相同的元素程式驗證輸出結果 程式 #include <stdio.h> /******************************************************************* * * file name: 刪除A中與B相同的元 ...
  • OutOfMemoryError是Java程式中常見的異常,通常出現在記憶體不足時,導致程式無法運行。藉助MAT記憶體分析工具分析可能的記憶體泄漏代碼問題定位。 ...
  • 具有查看日期時間、天氣、工作日、記錄喝水、查詢微博熱搜、60s讀世界和各種小工具的桌面工具,可以在托盤區單擊隱藏和彈出,或使用快捷鍵Alt+1 ...
  • 變數、指針和關鍵字 兩個口訣: 變數變數,能變,就是能讀能寫,必定在記憶體(RAM)里 指針指針,保存的是地址,32 位處理器中的地址都是 32 位的,無論是什麼類型的指針變數,都是 4 位元組 指針 對於 32 位處理器裡面,地址是 32 位的,所以指針的大小為 4 位元組,sizeof(p) = 4 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...