1. 程式人生 > 實用技巧 >Mybatis入門

Mybatis入門

參考:https://www.bilibili.com/video/BV1Db411s7F5?p=74 黑馬老師的mybatis視訊,手寫mybatis入門,可以的
https://blog.csdn.net/a1092882580/article/details/104178707 同學筆記

入門案例

  • 實體類屬性和表的列名需要保持一致的原因是excutor中執行完sql語句後返回的列名需要和你接收的實體類的屬性一致才能成功給實體類賦值。
  • 由於在 Windows 環境下 MySQL 不區分大小,所以 userName 等同 username ,不過要注意在 Linux 環境下 MySQL 嚴格區別大小寫。
  • 為了解決實體類屬性名與資料庫表列名不一致,有以下解決方法:
  1. 在 SQL 語句中為所有列起別名,使別名與實體類屬性名一致(執行效率相對高,開發效率低)
<select id="listAllUsers" resultType="cn.ykf.pojo.User">
  1. 使用 resultMap 完成結果集到實體類的對映(執行效率相對低,開發效率高)
<mapper namespace="cn.ykf.mapper.UserMapper">
    <!-- 配置 resultMap ,完成實體類與資料庫表的對映 -->
    <resultMap id="userMap" type="cn.ykf.pojo.User">
        <id property="userId" column="id" />
        <result property="userName" column="username"/>
        <result property="userBirthday" column="birthday"/>
        <result property="userAddress" column="address"/>
        <result property="userSex" column="sex"/>
    </resultMap>
    <!-- 配置查詢所有使用者 -->
    <select id="listAllUsers" resultMap="userMap">
       SELECT * FROM user
    </select>
</mapper>

所用模式

Mybatis 在使用代理 Mapper 的方式(session.getMapper(xxx.class))實現增刪查改的時候只做了以下兩件事(劃重點)

  • 建立代理物件
  • 在代理物件中呼叫 selectList
自定義mybatis流程

  • sqlsessionfactoryBuilder的作用是將xml的解析部分與factory的建構函式拿出來,便於xml解析器維護和使得factory別太臃腫

CURD

  • 獲取引數時

  • parameterType = "與表對應的實體類"

  • {實體類屬性}: 讀取實體類的屬性

模糊查詢

  • {} 需要在測試類中自己家%等萬用字元

    public void testListUsersByName() {
    List<User> users = mapper.listUsersByName("%王%");
    // 使用 Stream 流 + 方法引用,需要至少jdk8
    users.forEach(System.out::println);
    }
    由於對映檔案中的 SQL 語句並沒有對引數進行模糊查詢處理,所以在呼叫方法的時候我們必須手動為查詢的關鍵字進行%拼接,這樣很不方便。如果想呼叫方法查詢時,只傳入查詢的關鍵字,那麼可以採用以下方法 :
    SELECT * FROM user WHERE username LIKE concat('%',#{name},'%');
    SELECT * FROM user WHERE username LIKE "%"#{name}"%";
    

parameterType

  • 簡單物件

  • 傳遞pojo物件

    首先介紹一下 OGNL 表示式
    全稱 Object Graphic Navigation Language ,即物件圖導航語言

    它是通過物件的取值方法來獲取資料。在寫法上把get給省略了。

  • 傳遞pojo包裝物件
    在開發中如果想實現複雜查詢 ,查詢條件不僅包括使用者查詢條件,還包括其它的查詢條件(比如將使用者購買商品資訊也作為查詢條件),這時可以使用 pojo 包裝物件傳遞輸入引數,即將多個需要操作的類放到一個總體類中

實體類屬性名與資料庫表列名不一致

在某些情況下,實體類的屬性和資料庫表的列名並不一致,那麼就會出現查詢結果無法封裝進實體類。因為select的返回的資料是依據列名與實體類屬性的對應關係進行賦值的,失敗便為空,修改實體類進行演示

  1. 在 SQL 語句中為所有列起別名,使別名與實體類屬性名一致(執行效率相對高,開發效率低)
<select id="listAllUsers" resultType="cn.ykf.pojo.User">
    SELECT id AS userId, username AS userName, birthday AS userBirthday, sex AS userSex, address AS userAddress FROM user
</select>
  1. 使用 resultMap 完成結果集到實體類的對映(執行效率相對低,開發效率高)
<mapper namespace="cn.ykf.mapper.UserMapper">
    <!-- 配置 resultMap ,完成實體類與資料庫表的對映 -->
    <resultMap id="userMap" type="cn.ykf.pojo.User">
        <id property="userId" column="id" />
        <result property="userName" column="username"/>
        <result property="userBirthday" column="birthday"/>
        <result property="userAddress" column="address"/>
        <result property="userSex" column="sex"/>
    </resultMap>
    <!-- 配置查詢所有使用者 -->
    <select id="listAllUsers" resultMap="userMap">
       SELECT * FROM user
    </select>
</mapper>
 

使用Dao實現類(劃重點->對比原始碼)

  • 若使用sqlsession(defaultSqlSession)的selectList等方法,就是對傳入的單個方法(“statement”,arg)進行相應executor(CachingExecutor->SimpleExecutor(extends BaseExecutor))操作
  • 接著進入執行SQL語句處理(MyBatis在第一次執行SQL操作時,在獲取Statement時,會去獲取資料庫連結,如果我們配置的資料來源為POOLED,這裡會使用PooledDataSource來獲取connection),statementHandler(RoutingStatementHandler->PreparedStatementHandler(extends BaseStatementHandler))
  • 接著封裝處理ResultSetHandler(DefaultResultSetHandler),接著呼叫excute方法進行JDBC的操作並進行封裝,返回結果集。

使用Dao代理類(劃重點->對比原始碼、自定義mybatis)

  • 若是用getmapper則是會在代理物件呼叫方法時呼叫動態代理的proxyhadler對像中的invoke方法進行增強,增強時對呼叫的方法進行switch case 選擇,繼而呼叫sqlsession的相應CRUD方法(即上面的流程),返回了一個整個增強的代理類例項,再呼叫例項方法時增強返回增強後的結果集。

propeties

  • 全域性變數

    第一種,採用全域性的內部配置。採用這種方式的話,如果需要配置多個數據庫環境,那麼像 username、password 等屬性就可以複用,提高開發效率。

    <?xml version="1.0" encoding="UTF-8" ?>
    
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!-- 全域性變數 -->
        <properties>
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/db_mybatis"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </properties>
    
    
        <!--配置環境-->
        <environments default="development">
            <environment id="development">
                <!-- 配置事務型別 -->
                <transactionManager type="JDBC"/>
                <!-- 配置資料來源(連線池) -->
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        
        <!-- 指定對映檔案 -->
        <mappers>
            <mapper resource="UserMapper.xml"/>
        </mappers>
    
    </configuration>
    
  • resource:

  • url

typeAliases 標籤
之前在編寫對映檔案的時候, resultType 這個屬性可以寫 int、INT 等,就是因為 Mybatis 給這些型別起了別名。Mybatis 內建的別名如表格所示:

如果我們也想給某個實體類指定別名的時候,就可以採用 typeAliases 標籤。用法如下:

<configuration>
    <!--配置別名-->
    <typeAliases>
        <typeAlias type="cn.ykf.pojo.User" alias="user"/>
    </typeAliases>
    <!-- 其他配置省略... -->
</configuration>

typeAlias 子標籤用於配置別名。其中 type 屬性用於指定要配置的類的全限定類名(該類只能是某個domain實體類), alias 屬性指定別名。一旦指定了別名,那麼別名就不再區分大小寫。
也就是說,此時我們可以在對映檔案中這樣寫 resultType="user" ,也可以寫 resultType="USER"。
當我們有多個實體類需要起別名的時候,那麼我們就可以使用 package 標籤。

typeAlias 子標籤用於配置別名。其中 type 屬性用於指定要配置的類的全限定類名(該類只能是某個domain實體類), alias 屬性指定別名。一旦指定了別名,那麼別名就不再區分大小寫。
也就是說,此時我們可以在對映檔案中這樣寫 resultType="user" ,也可以寫 resultType="USER"。
當我們有多個實體類需要起別名的時候,那麼我們就可以使用 package 標籤。

<typeAliases><!--在configuration中使用-->
    <!-- 包下所有實體類起別名 -->
    <package name="cn.ykf.pojo"/>
</typeAliases>

package 標籤指定要配置別名的包,當指定之後,該包下的所有實體類都會註冊別名,並且類名就是別名,不再區分大小寫
package 標籤還可以將某個包內的對映器介面實現全部註冊為對映器,如下所示

<!-- 指定對映檔案 -->
<mappers>
    <package name="cn.ykf.mapper"/>
</mappers>

這樣配置後,我們就無需一個一個地配置 Mapper 介面了。不過,這種配置方式的前提是對映配置檔案位置必須和dao介面的包結構相同

  • mapper的recourse載入,掃描對映檔案來查詢介面類,對映檔案命名為介面全類名(介面類名可以和對映檔名不同,因為對映檔案有namespace),但可以dao介面和mapper檔案不在同一路徑下,mybatis原始開發Dao.xml檔案與介面檔案不在同一路徑下,僅能用resource載入對映檔案
  • mapper的class載入,僅適用於類路徑下,介面檔案與對映檔案在同一路徑下,且介面名與對映檔名相同的情況.(掃描介面類來查詢對映檔案,註解的時候用的多)
  • package,適用於類路徑下,介面檔案與對映檔案在同一路徑下,且介面名與對映檔名相同的情況.(package 的優勢是若在同一路徑且介面名與對映檔名相同,則可以定位到包名就行,不要定位到每個類名)

package定位時,若包不同且寫的為xml所在的包,會報Type interface com.itheima.dao.IUserDao is not known to the MapperRegistry.

若寫的為介面的包,則直接找不到statement

Mybatis中介面和對應的mapper檔案不一定要放在同一個包下,放在一起的目的是為了Mybatis進行自動掃描,並且要注意此時java介面類的名稱和mapper檔案的名稱要相同,否則會報異常,由於此時Mybatis會自動解析對應的介面和相應的配置檔案(class和package),所以就不需要配置mapper檔案的位置了。因為通常的xml檔案的namespace、id需要到介面類的資訊,而此時mybatis最先開始的是 XML的解析,若是不同包,config.xml呼叫的將是mappers中mapper的包(和介面類不在同一級),尚未將其他的為用到的介面類的結構資訊通過類載入器載入到JVM的方法區中,因此會缺乏相關的資訊組成mappers的成員物件,呼叫失敗導致報錯。而若是在同一個包,編譯成.class到target包中時,recourse和java包會合並,則可以自動尋找同一級中是否有對應的同名介面資訊,便可省去配置的麻煩,resouce不用受此拘束肯定額外呼叫了別的方法,會造成更多的資源浪費,為了特殊需求設計的。

  1. 介面和檔案在同一個包中
    1.1 預設maven構建
    如果在工程中使用了maven構建工具,那麼就會出現一個問題:我們知道在典型的maven工程中,目錄結構有:src/main/java和src/main/resources,前者是用來存放java原始碼的,後者則是存放一些資原始檔,比如配置檔案等,在預設的情況下maven打包的時候,對於src/main/java目錄只打包原始碼,而不會打包其他檔案。所以此時如果把對應的mapper檔案放到src/main/java目錄下時,不會打包到最終的jar資料夾中,也不會輸出到target資料夾中,由於在進行單元測試的時候執行的是/target目錄下/test-classes下的程式碼,所以在測試的時候也不會成功。

    為了實現在maven預設環境下打包時,Mybatis的介面和mapper檔案在同一包中,可以通過將介面檔案放在src/main/java某個包中,而在src/main/resources目錄中建立同樣的包,這是一種約定優於配置的方式,這樣在maven打包的時候就會將src/main/java和src/main/resources相同包下的檔案合併到同一包中。

    在預設maven打包的環境下,不要將介面檔案和mapper檔案全部放到src/main/java,這樣也不會把mapper檔案打包進去

簡要的過程如下(與如下的例子無關):

src/main/java
    edu.zju.mapper
       UserMapper.java
src/main/resources
    config.xml
    edu.zju.mapper
        UserMapper.xml

如上這種方式在maven打包之後的目錄如下:

-src/main/java
    -config.xml
    -edu.zju.mapper
        -UserMapper.java
        -UserMapper.xml

2.1 更改maven構建配置

如果不想將介面和mapper檔案分別放到src/main/javasrc/main/resources中,而是全部放到src/main/java,那麼在構建的時候需要指定maven打包需要包括xml檔案,具體配置如下:

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

動態SQL

if
<!-- 根據查詢條件進行復合查詢 -->
<select id="listUsersByCondition" parameterType="cn.ykf.pojo.User" resultType="cn.ykf.pojo.User">
    SELECT * FROM user WHERE 1 = 1
    <if test="username != null and username != ''">
        AND username LIKE CONCAT('%',#{username},'%')
    </if>
    <if test="sex != null and sex != ''">
        AND sex = #{sex}
    </if>
    <if test="address != null and address != ''">
        AND address = #{address}
    </if>
    <if test="birthday != null">
        AND birthday = #{birthday}
    </if>
</select>
這裡加上 WHERE 1 =1 是防止所有條件都為空時拼接 SQL 語句出錯。因為不加上 1 = 1 這個恆等條件的話,如果後面查詢條件都沒拼接成功,那麼 SQL 語句最後會帶有一個 WHERE 關鍵字而沒有條件,不符合 SQL 語法。
<if></if> 標籤中 test 屬性是必須的,表示判斷的條件。其中有幾點需要注意:
如果 test 有多個條件,那麼必須使用 and 進行連線,而不能使用 Java 中的 && 運算子,mybatis自己設立的,需要符合他的XML解析規則。
test 中的引數名稱必須與實體類的屬性保持一致,也就是和 #{引數符號} 保持一致。
如果判斷條件為字串,那麼除了判斷是否為 null 外,最好也判斷一下是否為空字串,'' ,防止 SQL語句將其作為條件查詢。
where
<select id="listUsersByCondition" parameterType="cn.ykf.pojo.User" resultType="cn.ykf.pojo.User">
    SELECT * FROM user
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%',#{username},'%')
        </if>
        <if test="sex != null and sex != ''">
            AND sex = #{sex}
        </if>
        <if test="address != null and address != ''">
            AND address = #{address}
        </if>
        <if test="birthday != null">
            AND birthday = #{birthday}
        </if>
    </where>
</select>
可以發現,相比之前的 SQL 語句,我們少寫了 WHERE 1 = 1,而是使用 <where></where> 標籤來代替它。
<where></where> 標籤只會在至少有一個子元素的條件返回 SQL 子句的情況下才去插入 WHERE 子句。而且,若語句的開頭為 AND 或 OR,<where></where> 標籤也會將它們去除。
簡單來說,就是該標籤可以動態新增 WHERE 關鍵字,並且剔除掉 SQL 語句中多餘的 AND 或者 OR。
foreach

假如我們現在有一個新的需求,就是根據一個 id 集合,來查詢出 id 在該集合中的所有使用者,那麼又該怎麼實現呢?
如果使用普通 SQL 語句的話,那麼查詢語句應該這樣寫:SELECT * FROM user WHERE id IN(41,42,43);
因此,如果想使用動態 SQL 來完成的話,那麼我們就應該考慮如何拼接上 id IN(41,42,43) 這一串內容,這時候,我們的 標籤就出場了。

foreach元素的屬性主要有 item,index,collection,open,separator,close。

item表示集合中每一個元素進行迭代時的別名,
index指 定一個名字,用於表示在迭代過程中,每次迭代到的位置,
open表示該語句以什麼開始,
separator表示在每次進行迭代之間以什麼符號作為分隔 符,
close表示以什麼結束。12345

在使用foreach的時候最關鍵的也是最容易出錯的就是collection屬性,該屬性是必須指定的,但是在不同情況 下,該屬性的值是不一樣的,主要有一下3種情況:

1. 如果傳入的的是一個普通物件(沒有特殊的資料結構),而該類中有list等collection屬性的,需要用到該屬性並需要foreach,collection則為其中屬性的值,即用的為包裝類Vo時。
2. 如果傳入的是單引數且引數型別是一個List的時候,collection屬性值為list
3. 如果傳入的是單引數且引數型別是一個array陣列的時候,collection的屬性值為array
4. 如果傳入的引數是多個的時候,我們就需要把它們封裝成一個Map了,collection的屬性值為map的key屬性
<!-- 根據id集合查詢使用者 -->
<select id="listUsersByIds" parameterType="cn.ykf.pojo.QueryVo" resultType="cn.ykf.pojo.User">
    SELECT * FROM user
    <where>
        <if test="ids != null and ids.size > 0">
            <foreach collection="ids" open="AND id IN (" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </if>
    </where>
</select>
<foreach></foreach> 標籤用於遍歷集合,每個屬性的作用如下所示:
collection : 代表要遍歷的集合或陣列,這個屬性是必須的。如果是遍歷陣列,那麼該值只能為 array
open : 代表語句的開始部份。
close : 代表語句的結束部份。
item : 代表遍歷集合時的每個元素,相當於一個臨時變數。
separator : 代表拼接每個元素之間的分隔符。
你可以將任何可迭代物件(如 List、Set 等)、Map 物件或者陣列物件傳遞給 foreach 作為集合引數。當使用可迭代物件或者陣列時,index 是當前迭代的次數,item 的值是本次迭代獲取的元素。當使用 Map 物件(或者 Map.Entry 物件的集合)時,index 是鍵,item 是值。
注意,SQL 語句中的引數符號 #{id} 應該與 item="id" 保持一致,也就是說,item 屬性如果把臨時變數宣告為 uid 的話,那麼使用時就必須寫成 #{uid}。
定義 SQL 片段
  • 在上面的例子中,我們在每條 SQL 中都用到了 SELECT \* FROM user ,因此,我們可以把該語句定義為 SQL 片段,以供複用,減少工作量。
  • 首先在對映檔案中定義程式碼片段
<!-- 定義程式碼片段 -->
<sql id="defaultSql">
    SELECT * FROM user
</sql>

接著就可以在需要的地方引用該片段了

<!-- 根據id集合查詢使用者 -->
<select id="listUsersByIds" parameterType="cn.ykf.pojo.QueryVo" resultMap="userMap">
    <!-- 引用程式碼片段 -->
    <include refid="defaultSql"></include>
    <where>
        <if test="ids != null and ids.size > 0">
            <foreach collection="ids" open="AND id IN (" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </if>
    </where>
</select>