溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Mybatis是什么及怎么使用

發(fā)布時(shí)間:2023-05-11 15:02:20 來源:億速云 閱讀:140 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“Mybatis是什么及怎么使用”,在日常操作中,相信很多人在Mybatis是什么及怎么使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Mybatis是什么及怎么使用”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

    一 數(shù)據(jù)庫操作框架的歷程

    1.1 JDBC

    JDBC(Java Data Base Connection,java數(shù)據(jù)庫連接)是一種用于執(zhí)行SQL語句的Java API,可以為多種關(guān)系數(shù)據(jù)庫提供統(tǒng)一訪問,它由一組用Java語言編寫的類和接口組成.JDBC提供了一種基準(zhǔn),據(jù)此可以構(gòu)建更高級的工具和接口,使數(shù)據(jù)庫開發(fā)人員能夠編寫數(shù)據(jù)庫應(yīng)用程序

    • 優(yōu)點(diǎn):運(yùn)行期:快捷、高效

    • 缺點(diǎn):編輯期:代碼量大、繁瑣異常處理、不支持?jǐn)?shù)據(jù)庫跨平臺

    Mybatis是什么及怎么使用

    jdbc核心api

    1. DriverManager 連接數(shù)據(jù)庫

    2. Connection 連接數(shù)據(jù)庫的抽象

    3. Statment 執(zhí)行SQL

    4. ResultSet 數(shù)據(jù)結(jié)果集

    1.2 DBUtils

    DBUtils是Java編程中的數(shù)據(jù)庫操作實(shí)用工具,小巧簡單實(shí)用。
    DBUtils封裝了對JDBC的操作,簡化了JDBC操作,可以少寫代碼。
    DBUtils三個(gè)核心功能介紹

    1. QueryRunner中提供對sql語句操作的API

    2. ResultSetHandler接口,用于定義select操作后,怎樣封裝結(jié)果集

    3. DBUtils類,它就是一個(gè)工具類,定義了關(guān)閉資源與事務(wù)處理的方法

    1.3 Hibernate

    ORM 對象關(guān)系映射

    1. object java對象

    2. relational 關(guān)系型數(shù)據(jù)

    3. mapping 映射

    • Hibernate 是由 Gavin King 于 2001 年創(chuàng)建的開放源代碼的對象關(guān)系框架。它強(qiáng)大且高效的構(gòu)建具有關(guān)系對象持久性和查詢服務(wù)的 Java 應(yīng)用程序。

    • Hibernate 將 Java 類映射到數(shù)據(jù)庫表中,從 Java 數(shù)據(jù)類型中映射到 SQL 數(shù)據(jù)類型中,并把開發(fā)人員從 95% 的公共數(shù)據(jù)持續(xù)性編程工作中解放出來。

    • Hibernate 是傳統(tǒng) Java 對象和數(shù)據(jù)庫服務(wù)器之間的橋梁,用來處理基于 O/R 映射機(jī)制和模式的那些對象。

    Mybatis是什么及怎么使用

    Hibernate 優(yōu)勢
    • Hibernate 使用 XML 文件來處理映射 Java 類別到數(shù)據(jù)庫表格中,并且不用編寫任何代碼。

    • 為在數(shù)據(jù)庫中直接儲存和檢索 Java 對象提供簡單的 APIs。

    • 如果在數(shù)據(jù)庫中或任何其它表格中出現(xiàn)變化,那么僅需要改變 XML 文件屬性。

    • 抽象不熟悉的 SQL 類型,并為我們提供工作中所熟悉的 Java 對象。

    • Hibernate 不需要應(yīng)用程序服務(wù)器來操作。

    • 操控你數(shù)據(jù)庫中對象復(fù)雜的關(guān)聯(lián)。

    • 最小化與訪問數(shù)據(jù)庫的智能提取策略。

    • 提供簡單的數(shù)據(jù)詢問。

    Hibernate劣勢
    • hibernate的完全封裝導(dǎo)致無法使用數(shù)據(jù)的一些功能。

    • Hibernate的緩存問題。

    • Hibernate對于代碼的耦合度太高。

    • Hibernate尋找bug困難。

    • Hibernate批量數(shù)據(jù)操作需要大量的內(nèi)存空間而且執(zhí)行過程中需要的對象太多

    1.4 JDBCTemplate

    JdbcTemplate針對數(shù)據(jù)查詢提供了多個(gè)重載的模板方法,你可以根據(jù)需要選用不同的模板方法.如果你的查詢很簡單,僅僅是傳入相應(yīng)SQL或者相關(guān)參數(shù),然后取得一個(gè)單一的結(jié)果,那么你可以選擇如下一組便利的模板方法。

    • 優(yōu)點(diǎn):運(yùn)行期:高效、內(nèi)嵌Spring框架中、支持基于AOP的聲明式事務(wù)

    • 缺點(diǎn):必須于Spring框架結(jié)合在一起使用、不支持?jǐn)?shù)據(jù)庫跨平臺、默認(rèn)沒有緩存

    1.5 Mybatis

    MyBatis 是一款優(yōu)秀的持久層框架/半自動(dòng)的ORM,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為數(shù)據(jù)庫中的記錄。

    優(yōu)點(diǎn)
    1、與JDBC相比,減少了50%的代碼量
    2、 最簡單的持久化框架,簡單易學(xué)
    3、SQL代碼從程序代碼中徹底分離出來,可以重用
    4、提供XML標(biāo)簽,支持編寫動(dòng)態(tài)SQL
    5、提供映射標(biāo)簽,支持對象與數(shù)據(jù)庫的ORM字段關(guān)系映射
    6、支持緩存、連接池、數(shù)據(jù)庫移植…

    缺點(diǎn)
    1、SQL語句編寫工作量大,熟練度要高
    2、數(shù)據(jù)庫移植性比較差,如果需要切換數(shù)據(jù)庫的話,SQL語句會有很大的差異

    二 MyBatis的配置文件詳解

    2.1 MyBatis日志配置

    導(dǎo)入pom

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>
    
     <dependency>
         <groupId>ch.qos.logback</groupId>
         <artifactId>logback-classic</artifactId>
         <version>1.2.3</version>
     </dependency>

    添加logback配置文件

    <configuration>
        <!--appender 追加器   日志以哪種方式進(jìn)行輸出
                name 取個(gè)名字
                class 不同實(shí)現(xiàn)類會輸出到不同地方
                    ch.qos.logback.core.ConsoleAppender 輸出到控制臺
        -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <!-- 格式 -->
                <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{100} - %msg%n</pattern>
            </encoder>
        </appender>
    <!--cn.tulingxueyuan.mapper-->
    <!--控制跟細(xì)粒度的日志級別  根據(jù)包\根據(jù)類-->
        <logger name="cn.tulingxueyuan.mapper" level="debug"></logger>
        org.apache.ibatis.transaction
        <!--控制所有的日志級別-->
        <root level="error">
            <!-- 將當(dāng)前日志級別輸出到哪個(gè)追加器上面 -->
            <appender-ref ref="STDOUT" />
        </root>
    </configuration>
    Logger LOGGER= LoggerFactory.getLogger(this.getClass());
    /**
     * 日志級別
     * TRACE < DEBUG < INFO < WARN < ERROR。
     * 1        2       3      4       5
     */
    @Test
    public  void test02(){
        LOGGER.trace("跟蹤級別");
        LOGGER.debug("調(diào)試級別");
        LOGGER.info("信息級別");
        LOGGER.warn("警告級別");
        LOGGER.error("異常級別");
    }

    2.2 mybatis-config.xml全局配置文件詳解

    在mybatis的項(xiàng)目中,我們發(fā)現(xiàn)了有一個(gè)mybatis-config.xml的配置文件,這個(gè)配置文件是mybatis的全局配置文件,用來進(jìn)行相關(guān)的全局配置,在任何操作下都生效的配置。下面我們要針對其中的屬性做詳細(xì)的解釋,方便大家在后續(xù)使用的時(shí)候更加熟練。

    官方說明:
    MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設(shè)置和屬性信息。 配置文檔的頂層結(jié)構(gòu)如下:

    • configuration(配置)

      • environments(環(huán)境配置)

      • environment(環(huán)境變量)

      • transactionManager(事務(wù)管理器)

      • properties(屬性)

      • settings(設(shè)置)

      • typeAliases(類型別名)

      • typeHandlers(類型處理器)

      • objectFactory(對象工廠)

      • plugins(插件)

      • dataSource(數(shù)據(jù)源)

      • databaseIdProvider(數(shù)據(jù)庫廠商標(biāo)識)

      • mappers(映射器)

    mybatis-config.xml

    <?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>
        <!--引入外部配置文件,類似于Spring中的property-placeholder
        resource:從類路徑引入
        url:從磁盤路徑或者網(wǎng)絡(luò)路徑引入
        -->
        <properties resource="db.properties"></properties>
        <!--用來控制mybatis運(yùn)行時(shí)的行為,是mybatis中的重要配置-->
        <settings>
            <!--設(shè)置列名映射的時(shí)候是否是駝峰標(biāo)識-->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
        <!--typeAliases表示為我們引用的實(shí)體類起別名,默認(rèn)情況下我們需要寫類的完全限定名
        如果在此處做了配置,那么可以直接寫類的名稱,在type中配置上類的完全限定名,在使用的時(shí)候可以忽略大小寫
        還可以通過alias屬性來表示類的別名
        -->
        <typeAliases>
    <!--        <typeAlias type="cn.tulingxueyuan.bean.Emp" alias="Emp"></typeAlias>-->
            <!--如果需要引用多個(gè)類,那么給每一個(gè)類起別名肯定會很麻煩,因此可以指定對應(yīng)的包名,那么默認(rèn)用的是類名-->
            <package name="cn.tulingxueyuan.bean"/>
        </typeAliases>
        <!--
        在實(shí)際的開發(fā)過程中,我們可能分為開發(fā)環(huán)境,生產(chǎn)環(huán)境,測試環(huán)境等等,每個(gè)環(huán)境的配置可以是不一樣的
        environment就用來表示不同環(huán)境的細(xì)節(jié)配置,每一個(gè)環(huán)境中都需要一個(gè)事務(wù)管理器以及數(shù)據(jù)源的配置
        我們在后續(xù)的項(xiàng)目開發(fā)中幾乎都是使用spring中配置的數(shù)據(jù)源和事務(wù)管理器來配置,此處不需要研究
        -->
        <!--default:用來選擇需要的環(huán)境-->
        <environments default="development">
            <!--id:表示不同環(huán)境的名稱-->
            <environment id="development">
                <transactionManager type="JDBC"/>
                <!--配置數(shù)據(jù)庫連接-->
                <dataSource type="POOLED">
                    <!--使用${}來引入外部變量-->
                    <property name="driver" value="${driverClassname}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        <!--
        在不同的數(shù)據(jù)庫中,可能sql語句的寫法是不一樣的,為了增強(qiáng)移植性,可以提供不同數(shù)據(jù)庫的操作實(shí)現(xiàn)
        在編寫不同的sql語句的時(shí)候,可以指定databaseId屬性來標(biāo)識當(dāng)前sql語句可以運(yùn)行在哪個(gè)數(shù)據(jù)庫中
        -->
        <databaseIdProvider type="DB_VENDOR">
            <property name="MySQL" value="mysql"/>
            <property name="SQL Server" value="sqlserver"/>
            <property name="Oracle" value="orcl"/>
        </databaseIdProvider>
        
        <!--將sql的映射文件適用mappers進(jìn)行映射-->
        <mappers>
            <!--
            指定具體的不同的配置文件
            class:直接引入接口的全類名,可以將xml文件放在dao的同級目錄下,并且設(shè)置相同的文件名稱,同時(shí)可以使用注解的方式來進(jìn)行相關(guān)的配置
            url:可以從磁盤或者網(wǎng)絡(luò)路徑查找sql映射文件
            resource:在類路徑下尋找sql映射文件
            -->
    <!--        <mapper resource="EmpDao.xml"/>
            <mapper resource="UserDao.xml"/>
            <mapper class="cn.tulingxueyuan.dao.EmpDaoAnnotation"></mapper>-->
            <!--
            當(dāng)包含多個(gè)配置文件或者配置類的時(shí)候,可以使用批量注冊的功能,也就是引入對應(yīng)的包,而不是具體的配置文件或者類
            但是需要注意的是,
            1、如果使用的配置文件的形式,必須要將配置文件跟dao類放在一起,這樣才能找到對應(yīng)的配置文件.
                如果是maven的項(xiàng)目的話,還需要添加以下配置,原因是maven在編譯的文件的時(shí)候只會編譯java文件
                    <build>
                        <resources>
                            <resource>
                                <directory>src/main/java</directory>
                            <includes>
                                <include>**/*.xml</include>
                            </includes>
                        </resource>
                        </resources>
                    </build>
    
            2、將配置文件在resources資源路徑下創(chuàng)建跟dao相同的包名
            -->
            <package name="cn.tulingxueyuan.dao"/>
        </mappers>
    </configuration>

    2.3 Mybatis SQL映射文件詳解

    MyBatis 的真正強(qiáng)大在于它的語句映射,這是它的魔力所在。由于它的異常強(qiáng)大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進(jìn)行對比,你會立即發(fā)現(xiàn)省掉了將近 95% 的代碼。MyBatis 致力于減少使用成本,讓用戶能更專注于 SQL 代碼。
    SQL 映射文件只有很少的幾個(gè)頂級元素(按照應(yīng)被定義的順序列出):

    • cache &ndash; 該命名空間的緩存配置。

    • cache-ref &ndash; 引用其它命名空間的緩存配置。

    • resultMap &ndash; 描述如何從數(shù)據(jù)庫結(jié)果集中加載對象,是最復(fù)雜也是最強(qiáng)大的元素。

    • parameterMap &ndash; 老式風(fēng)格的參數(shù)映射。此元素已被廢棄,并可能在將來被移除!請使用行內(nèi)參數(shù)映射。文檔中不會介紹此元素。

    • sql &ndash; 可被其它語句引用的可重用語句塊。

    • insert &ndash; 映射插入語句。

    • update &ndash; 映射更新語句。

    • delete &ndash; 映射刪除語句。

    • select &ndash; 映射查詢語句。

    在每個(gè)頂級元素標(biāo)簽中可以添加很多個(gè)屬性,下面我們開始詳細(xì)了解下具體的配置。

    insert、update、delete元素

    屬性描述
    id在命名空間中唯一的標(biāo)識符,可以被用來引用這條語句。
    parameterType將會傳入這條語句的參數(shù)的類全限定名或別名。這個(gè)屬性是可選的,因?yàn)?MyBatis 可以通過類型處理器(TypeHandler)推斷出具體傳入語句的參數(shù),默認(rèn)值為未設(shè)置(unset)。
    parameterMap用于引用外部 parameterMap 的屬性,目前已被廢棄。請使用行內(nèi)參數(shù)映射和 parameterType 屬性。
    flushCache將其設(shè)置為 true 后,只要語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存被清空,默認(rèn)值:(對 insert、update 和 delete 語句)true。
    timeout這個(gè)設(shè)置是在拋出異常之前,驅(qū)動(dòng)程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)。默認(rèn)值為未設(shè)置(unset)(依賴數(shù)據(jù)庫驅(qū)動(dòng))。
    statementType可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認(rèn)值:PREPARED。
    useGeneratedKeys(僅適用于 insert 和 update)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數(shù)據(jù)庫內(nèi)部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關(guān)系型數(shù)據(jù)庫管理系統(tǒng)的自動(dòng)遞增字段),默認(rèn)值:false。
    keyProperty(僅適用于 insert 和 update)指定能夠唯一識別對象的屬性,MyBatis 會使用 getGeneratedKeys 的返回值或 insert 語句的 selectKey 子元素設(shè)置它的值,默認(rèn)值:未設(shè)置(unset)。如果生成列不止一個(gè),可以用逗號分隔多個(gè)屬性名稱。
    keyColumn(僅適用于 insert 和 update)設(shè)置生成鍵值在表中的列名,在某些數(shù)據(jù)庫(像 PostgreSQL)中,當(dāng)主鍵列不是表中的第一列的時(shí)候,是必須設(shè)置的。如果生成列不止一個(gè),可以用逗號分隔多個(gè)屬性名稱。
    databaseId如果配置了數(shù)據(jù)庫廠商標(biāo)識(databaseIdProvider),MyBatis 會加載所有不帶 databaseId 或匹配當(dāng)前 databaseId 的語句;如果帶和不帶的語句都有,則不帶的會被忽略。
     <!--如果數(shù)據(jù)庫支持自增可以使用這樣的方式-->
     <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into user(user_name) values(#{userName})
     </insert>
     <!--如果數(shù)據(jù)庫不支持自增的話,那么可以使用如下的方式進(jìn)行賦值查詢-->
     <insert id="insertUser2" >
         <selectKey order="BEFORE" keyProperty="id" resultType="integer">
            select max(id)+1 from user
         </selectKey>
        insert into user(id,user_name) values(#{id},#{userName})
     </insert>

    更多詳細(xì)內(nèi)容見下邊的第三章節(jié)

    三 MyBatis基于XML的詳細(xì)使用-參數(shù)、返回結(jié)果處理

    3.1 參數(shù)的取值方式

    在xml文件中編寫sql語句的時(shí)候有兩種取值的方式,分別是#{}和${},下面來看一下他們之間的區(qū)別:

    <!--獲取參數(shù)的方式:
        1.#{} ==> jdbc String sql=" SELECT id,user_name FROM EMP WHERE id=?"
            1.會經(jīng)過JDBC當(dāng)中PreparedStatement的預(yù)編譯,會根據(jù)不同的數(shù)據(jù)類型來編譯成對應(yīng)數(shù)據(jù)庫所對應(yīng)的數(shù)據(jù)。
            2.能夠有效的防止SQL注入。 推薦使用??!
            特殊用法:
            自帶很多內(nèi)置參數(shù)的屬性:通常不會使用。了解
            javaType、jdbcType、mode、numericScale、resultMap、typeHandler.
            比如 需要改變默認(rèn)的NULL===>OTHER:#{id,javaType=NULL}
                 想保留小數(shù)點(diǎn)后兩位:#{id,numericScale=2}
    
        2.${} ==> jdbc String sql=" SELECT id,user_name FROM EMP WHERE id="+id
            1.不會進(jìn)行預(yù)編譯,會直接將輸入進(jìn)來的數(shù)據(jù)拼接在SQL中。
            2.存在SQL注入的風(fēng)險(xiǎn)。不推薦使用。
            特殊用法:
                1.調(diào)試情況下可以臨時(shí)使用。
                2.實(shí)現(xiàn)一些特殊功能:前提一定要保證數(shù)據(jù)的安全性。
                 比如:動(dòng)態(tài)表、動(dòng)態(tài)列. 動(dòng)態(tài)SQL.
    -->
    <select id="SelectEmp"  resultType="Emp"  resultMap="emp_map"  >
        SELECT id,user_name,create_date FROM EMP where id=#{id}
    </select>

    3.2 select的參數(shù)傳遞

    <!--
       參數(shù)傳遞的處理:
       1.單個(gè)參數(shù):SelectEmp(Integer id);
           mybatis 不會做任何特殊要求
           獲取方式:
               #:{輸入任何字符獲取參數(shù)}
    
       2.多個(gè)參數(shù):Emp SelectEmp(Integer id,String username);
           mybatis 會進(jìn)行封裝
           會將傳進(jìn)來的參數(shù)封裝成map:
           1個(gè)值就會對應(yīng)2個(gè)map項(xiàng) :  id===>  {key:arg0 ,value:id的值},{key:param1 ,value:id的值}
                                     username===>  {key:arg1 ,value:id的值},{key:param2 ,value:id的值}
           獲取方式:
                沒使用了@Param:
                          id=====>  #{arg0} 或者 #{param1}
                    username=====>  #{arg1} 或者 #{param2}
                除了使用這種方式還有別的方式,因?yàn)檫@種方式參數(shù)名沒有意義:
                設(shè)置參數(shù)的別名:@Param(""):SelectEmp(@Param("id") Integer id,@Param("username") String username);
                當(dāng)使用了@Param:
                          id=====>  #{id} 或者 #{param1}
                    username=====>  #{username} 或者 #{param2}
    
        3. javaBean的參數(shù):
           單個(gè)參數(shù):Emp SelectEmp(Emp emp);
           獲取方式:可以直接使用屬性名
               emp.id=====>#{id}
               emp.username=====>#{username}
           多個(gè)參數(shù):Emp SelectEmp(Integer num,Emp emp);
               num===>    #{param1} 或者 @Param
               emp===> 必須加上對象別名: emp.id===> #{param2.id} 或者  @Param("emp")Emp emp    ====>#{emp.id}
                                       emp.username===> #{param2.username} 或者  @Param("emp")Emp emp    ====>#{emp.username}
        4.集合或者數(shù)組參數(shù):
               Emp SelectEmp(List<String> usernames);
           如果是list,MyBatis會自動(dòng)封裝為map:
               {key:"list":value:usernames}
                 沒用@Param("")要獲得:usernames.get(0)  =====>  #{list[0]}
                                     :usernames.get(0)  =====>  #{agr0[0]}
                 有@Param("usernames")要獲得:usernames.get(0)  =====>  #{usernames[0]}
                                            :usernames.get(0)  =====>  #{param1[0]}
           如果是數(shù)組,MyBatis會自動(dòng)封裝為map:
               {key:"array":value:usernames}
                 沒用@Param("")要獲得:usernames.get(0)  =====>  #{array[0]}
                                   :usernames.get(0)  =====>  #{agr0[0]}
                 有@Param("usernames")要獲得:usernames.get(0)  =====>  #{usernames[0]}
                                            :usernames.get(0)  =====>  #{param1[0]}
         5.map參數(shù)
           和javaBean的參數(shù)傳遞是一樣。
           一般情況下:
               請求進(jìn)來的參數(shù) 和pojo對應(yīng),就用pojo
               請求進(jìn)來的參數(shù) 沒有和pojo對應(yīng),就用map
               請求進(jìn)來的參數(shù) 沒有和pojo對應(yīng)上,但是使用頻率很高,就用TO、DTO(就是單獨(dú)為這些參數(shù)創(chuàng)建一個(gè)對應(yīng)的javaBean出來,使參數(shù)傳遞更規(guī)范、更重用)
    
       --> 
    
       <!--
       接口:SelectEmp(String username,@Param("id") Integer id);
              username====>  #{arg0}  #{param1}
              id====>  #{id}  #{param2}
       接口:SelectEmp(@Param("beginDate") String beginDate,
                        String endDate,
                       Emp emp);
              beginDate====>  #{beginDate}  #{param1}
              endDate====>  #{arg1}  #{param2}
              emp.id====>#{arg2.id}  #{param2.id}
       接口:SelectEmp(List<Integer> ids,
                      String[] usernames,
                      @Param("beginDate") String beginDate,
                        String endDate,);
                   ids.get(0)=====> #{list[0]}      #{param1[0]}
                   usernames[0]=====> #{array[0]}      #{param2[0]}
              beginDate====>  #{beginDate}  #{param3}
              end====>  #{arg3}  #{param4}
       -->

    3.3 處理集合返回結(jié)果

    EmpDao.xml

    <!--當(dāng)返回值的結(jié)果是集合的時(shí)候,返回值的類型依然寫的是集合中具體的類型-->
        <select id="selectAllEmp" resultType="cn.tulingxueyuan.bean.Emp">
            select  * from emp
        </select>
    <!--在查詢的時(shí)候可以設(shè)置返回值的類型為map,當(dāng)mybatis查詢完成之后會把列的名稱作為key
        列的值作為value,轉(zhuǎn)換到map中
        -->
        <select id="selectEmpByEmpReturnMap" resultType="map">
            select * from emp where empno = #{empno}
        </select>
    
        <!--注意,當(dāng)返回的結(jié)果是一個(gè)集合對象的時(shí)候,返回值的類型一定要寫集合具體value的類型,
        同時(shí)在dao的方法上要添加@MapKey的注解,來設(shè)置key是什么結(jié)果
        @MapKey("empno")
        Map<Integer,Emp> getAllEmpReturnMap();-->
        <select id="getAllEmpReturnMap" resultType="cn.tulingxueyuan.bean.Emp">
            select * from emp
        </select>

    3.4 自定義結(jié)果集&mdash;resultMap

    <!--1.聲明resultMap自定義結(jié)果集   resultType 和 resultMap 只能使用一個(gè)。
        id 唯一標(biāo)識, 需要和<select 上的resultMap 進(jìn)行對應(yīng)
        type 需要映射的pojo對象, 可以設(shè)置別名
        autoMapping 自動(dòng)映射,(默認(rèn)=true) 只要字段名和屬性名遵循映射規(guī)則就可以自動(dòng)映射,但是不建議,哪怕屬性名和字段名一一對應(yīng)上了也要顯示的配置映射
        extends  如果多個(gè)resultMap有重復(fù)映射,可以聲明父resultMap,將公共的映射提取出來, 可以減少子resultMap的映射冗余
    -->
    <resultMap id="emp_map" type="emp" autoMapping="false" extends="common_map">
        <result column="create_date" property="cjsj"></result>
    </resultMap>
    
    <resultMap id="common_map" type="emp" autoMapping="false" >
        <!-- <id> 主鍵必須使用  對底層存儲有性能作用
                     column  需要映射的數(shù)據(jù)庫字段名
                     property 需要映射的pojo屬性名
         -->
        <id column="id" property="id"></id>
        <result column="user_name" property="username"></result>
    </resultMap>
    
    <!--2.使用resultMap 關(guān)聯(lián) 自定義結(jié)果集的id-->
    <select id="SelectEmp"  resultType="Emp"  resultMap="emp_map"  >
        SELECT id,user_name,create_date FROM EMP where id=#{id}
    </select>

    四 MyBatis基于XML的詳細(xì)使用&mdash;&mdash;高級結(jié)果映射

    4.1 聯(lián)合查詢

    emp.java

    import java.time.LocalDate;
    
    public class Emp {
        private Integer id;
        private String username;
        private LocalDate createDate;
        private deptId deptId;
    
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public LocalDate getCreateDate() {
            return createDate;
        }
    
        public void setCreateDate(LocalDate createDate) {
            this.createDate = createDate;
        }
    
        public Integer getDeptId() {
            return dept;
        }
    
        public void setDeptId(Integer deptId) {
            this.dept = dept;
        }
    
        @Override
        public String toString() {
            return "Emp{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", createDate=" + createDate +
                    ", deptId=" + deptId+
                    '}';
        }
    }

    EmpMapper.xml

    <!-- 實(shí)現(xiàn)表聯(lián)結(jié)查詢的方式:  可以映射: DTO -->
    <resultMap id="QueryEmp_Map" type="QueryEmpDTO">
        <id column="e_id" property="id"></id>
        <result column="user_name" property="username"></result>
        <result column="d_id" property="deptId"></result>
        <result column="dept_name" property="deptName"></result>
    </resultMap>
    
    <select id="QueryEmp"  resultMap="QueryEmp_Map">
        select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
        INNER JOIN dept t2 on t1.dept_id=t2.id
        where t1.id=#{id}
    </select>
    
    <!-- 實(shí)現(xiàn)表聯(lián)結(jié)查詢的方式:  可以映射map -->
    <resultMap id="QueryEmp_Map" type="map">
        <id column="e_id" property="id"></id>
        <result column="user_name" property="username"></result>
        <result column="d_id" property="deptId"></result>
        <result column="dept_name" property="deptName"></result>
    </resultMap>
    
    <select id="QueryEmp"  resultMap="QueryEmp_Map">
        select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
        INNER JOIN dept t2 on t1.dept_id=t2.id
        where t1.id=#{id}
    </select>

    QueryEmpDTO

    public class QueryEmpDTO {
    
        private String deptName;
        private Integer deptId;
        private Integer id;
        private String username;
    
        public String getDeptName() {
            return deptName;
        }
    
        public void setDeptName(String deptName) {
            this.deptName = deptName;
        }
    
        public Integer getDeptId() {
            return deptId;
        }
    
        public void setDeptId(Integer deptId) {
            this.deptId = deptId;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        @Override
        public String toString() {
            return "QueryEmpDTO{" +
                    "deptName='" + deptName + '\'' +
                    ", deptId=" + deptId +
                    ", id=" + id +
                    ", username='" + username + '\'' +
                    '}';
        }
    }

    Test

    @Test
    public void test01() {
        try(SqlSession sqlSession = sqlSessionFactory.openSession()){
            // Mybatis在getMapper就會給我們創(chuàng)建jdk動(dòng)態(tài)代理
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
            QueryEmpDTO dto = mapper.QueryEmp(4);
            System.out.println(dto);
        }
    }

    4.2 嵌套結(jié)果

    4.2.1 多對一

    EmpMapper.xml

    <!--嵌套結(jié)果   多 對 一  -->
    <resultMap id="QueryEmp_Map2" type="Emp">
        <id column="e_id" property="id"></id>
        <result column="user_name" property="username"></result>
        <!--
        association 實(shí)現(xiàn)多對一中的  “一”
            property 指定對象中的嵌套對象屬性
        -->
        <association property="dept">
            <id column="d_id" property="id"></id>
            <id column="dept_name" property="deptName"></id>
        </association>
    </resultMap>
    
    <select id="QueryEmp2"  resultMap="QueryEmp_Map2">
        select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
        INNER JOIN dept t2 on t1.dept_id=t2.id
        where t1.id=#{id}
    </select>
    4.2.2 一對多
    <!-- 嵌套結(jié)果: 一對多  查詢部門及所有員工 -->
    <resultMap id="SelectDeptAndEmpsMap" type="Dept">
        <id column="d_id"  property="id"></id>
        <id column="dept_name"  property="deptName"></id>
        <!--
        <collection  映射一對多中的 “多”
            property 指定需要映射的“多”的屬性,一般聲明為List
            ofType  需要指定list的類型
        -->
        <collection property="emps" ofType="Emp" >
            <id column="e_id" property="id"></id>
            <result column="user_name" property="username"></result>
            <result column="create_date" property="createDate"></result>
        </collection>
    </resultMap>
    
    <select id="SelectDeptAndEmps" resultMap="SelectDeptAndEmpsMap">
        select t1.id as d_id,t1.dept_name,t2.id e_id,t2.user_name,t2.create_date from dept t1
        LEFT JOIN emp t2 on t1.id=t2.dept_id
        where t1.id=#{id}
    </select>

    Emp.java

    import java.time.LocalDate;
    
    public class Emp {
        private Integer id;
        private String username;
        private LocalDate createDate;
        private Dept dept;
    
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public LocalDate getCreateDate() {
            return createDate;
        }
    
        public void setCreateDate(LocalDate createDate) {
            this.createDate = createDate;
        }
    
        public Dept getDept() {
            return dept;
        }
    
        public void setDept(Dept dept) {
            this.dept = dept;
        }
    
        @Override
        public String toString() {
            return "Emp{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", createDate=" + createDate +
                    ", dept=" + dept +
                    '}';
        }
    }

    Dept.java:

    public class Dept {
        private Integer id;
        private String deptName;
        private List<Emp> emps;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getDeptName() {
            return deptName;
        }
    
        public void setDeptName(String deptName) {
            this.deptName = deptName;
        }
    
        public List<Emp> getEmps() {
            return emps;
        }
    
        public void setEmps(List<Emp> emps) {
            this.emps = emps;
        }
    
    
        @Override
        public String toString() {
            return "Dept{" +
                    "id=" + id +
                    ", deptName='" + deptName + '\'' +
                    ", emps=" + emps +
                    '}';
        }
    }

    EmpMapper.java:

    public interface EmpMapper {
    
        /*徐庶老師實(shí)際開發(fā)中的實(shí)現(xiàn)方式*/
        QueryEmpDTO QueryEmp(Integer id);
    
        /*實(shí)用嵌套結(jié)果實(shí)現(xiàn)聯(lián)合查詢  多對一 */
        Emp QueryEmp2(Integer id);
    
    
        /*實(shí)用嵌套查詢實(shí)現(xiàn)聯(lián)合查詢  多對一 */
        Emp QueryEmp3(Integer id);
    }

    DeptMapper.java:

    public interface DeptMapper {
        //嵌套查詢: 一對多   使用部門id查詢員工
       Dept SelectDeptAndEmps(Integer id);
    
       // 嵌套查詢(異步查詢): 一對多  查詢部門及所有員工
        Dept SelectDeptAndEmps2(Integer id);
    }

    4.3 嵌套查詢

    在上述邏輯的查詢中,是由我們自己來完成sql語句的關(guān)聯(lián)查詢的,那么,我們能讓mybatis幫我們實(shí)現(xiàn)自動(dòng)的關(guān)聯(lián)查詢嗎?

    4.3.1 多對一

    EmpMapper.xml:

    <!--嵌套查詢(分步查詢)   多 對 一
      聯(lián)合查詢和分步查詢區(qū)別:   性能區(qū)別不大
                                分部查詢支持 懶加載(延遲加載)
       需要設(shè)置懶加載,一定要使用嵌套查詢的。
       要啟動(dòng)懶加載可以在全局配置文件中設(shè)置 lazyLoadingEnabled=true
       還可以單獨(dú)為某個(gè)分步查詢設(shè)置立即加載 <association fetchType="eager"
      -->
    <resultMap id="QueryEmp_Map3" type="Emp">
        <id column="id" property="id"></id>
        <result column="user_name" property="username"></result>
        <!-- association 實(shí)現(xiàn)多對一中的  “一”
            property 指定對象中的嵌套對象屬性
            column  指定將哪個(gè)字段傳到分步查詢中
            select 指定分步查詢的 命名空間+ID
            以上3個(gè)屬性是實(shí)現(xiàn)分步查詢必須的屬性
            fetchType 可選, eager|lazy   eager立即加載   lazy跟隨全局配置文件中的lazyLoadingEnabled
         -->
        <association property="dept"    column="dept_id"  select="cn.tulingxueyuan.mapper.DeptMapper.SelectDept">
        </association>
    </resultMap>
    
    <select id="QueryEmp3"  resultMap="QueryEmp_Map3">
       select  * from emp where id=#{id}
    </select>

    DeptMapper.xml

    <!-- 根據(jù)部門id查詢部門-->
    <select id="SelectDept" resultType="dept">
        SELECT * FROM dept where id=#{id}
    </select>
    4.3.2 一對多

    DeptMapper.xml

    <!-- 嵌套查詢(異步查詢): 一對多  查詢部門及所有員工 -->
    <resultMap id="SelectDeptAndEmpsMap2" type="Dept">
        <id column="d_id"  property="id"></id>
        <id column="dept_name"  property="deptName"></id>
        <!--
        <collection  映射一對多中的 “多”
            property 指定需要映射的“多”的屬性,一般聲明為List
            ofType  需要指定list的類型
            column 需要將當(dāng)前查詢的字段傳遞到異步查詢的參數(shù)
            select 指定異步查詢
        -->
        <collection property="emps" ofType="Emp" column="id" select="cn.tulingxueyuan.mapper.EmpMapper.SelectEmpByDeptId" >
        </collection>
    </resultMap>
    
    <select id="SelectDeptAndEmps2" resultMap="SelectDeptAndEmpsMap2">
        SELECT * FROM dept where id=#{id}
    </select>

    EmpMapper.xml

    <!-- 根據(jù)部門id所有員工 -->
    <select id="SelectEmpByDeptId"  resultType="emp">
        select  * from emp where dept_id=#{id}
    </select>

    Emp.java

    public class Emp {
        private Integer id;
        private String username;
        private LocalDate createDate;
        private Dept dept;
    
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public LocalDate getCreateDate() {
            return createDate;
        }
    
        public void setCreateDate(LocalDate createDate) {
            this.createDate = createDate;
        }
    
        public Dept getDept() {
            return dept;
        }
    
        public void setDept(Dept dept) {
            this.dept = dept;
        }
    
        @Override
        public String toString() {
            return "Emp{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", createDate=" + createDate +
                    ", dept=" + dept +
                    '}';
        }
    
    }

    Dept.java:

    public class Dept {
        private Integer id;
        private String deptName;
        private List<Emp> emps;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getDeptName() {
            return deptName;
        }
    
        public void setDeptName(String deptName) {
            this.deptName = deptName;
        }
    
        public List<Emp> getEmps() {
            return emps;
        }
    
        public void setEmps(List<Emp> emps) {
            this.emps = emps;
        }
    
    
        @Override
        public String toString() {
            return "Dept{" +
                    "id=" + id +
                    ", deptName='" + deptName + '\'' +
                    ", emps=" + emps +
                    '}';
        }
    }

    EmpMapper.java:

    public interface EmpMapper {
    
        /*徐庶老師實(shí)際開發(fā)中的實(shí)現(xiàn)方式*/
        QueryEmpDTO QueryEmp(Integer id);
    
        /*實(shí)用嵌套結(jié)果實(shí)現(xiàn)聯(lián)合查詢  多對一 */
        Emp QueryEmp2(Integer id);
    
    
        /*實(shí)用嵌套查詢實(shí)現(xiàn)聯(lián)合查詢  多對一 */
        Emp QueryEmp3(Integer id);
    }

    DeptMapper.java:

    public interface DeptMapper {
        //嵌套查詢: 一對多   使用部門id查詢員工
       Dept SelectDeptAndEmps(Integer id);
    
       // 嵌套查詢(異步查詢): 一對多  查詢部門及所有員工
        Dept SelectDeptAndEmps2(Integer id);
    }

    4.4 延遲查詢

    當(dāng)我們在進(jìn)行表關(guān)聯(lián)的時(shí)候,有可能在查詢結(jié)果的時(shí)候不需要關(guān)聯(lián)對象的屬性值(select count(1)&hellip;),那么此時(shí)可以通過延遲加載來實(shí)現(xiàn)功能。在全局配置文件中添加如下屬性
    mybatis-config.xml

    <!-- 開啟延遲加載,所有分步查詢都是懶加載 (默認(rèn)是立即加載)-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--當(dāng)開啟式, 使用pojo中任意屬性都會加載延遲查詢 ,默認(rèn)是false
    <setting name="aggressiveLazyLoading" value="false"/>-->
    <!--設(shè)置對象的哪些方法調(diào)用會加載延遲查詢   默認(rèn):equals,clone,hashCode,toString-->
    <setting name="lazyLoadTriggerMethods" value=""/>

    如果設(shè)置了全局加載,但是希望在某一個(gè)sql語句查詢的時(shí)候不使用延時(shí)策略,可以添加fetchType下屬性:

    <association property="dept" fetchType="eager"  column="dept_id"  select="cn.tulingxueyuan.mapper.DeptMapper.SelectDept">
    </association>

    4.5 總結(jié)

    Mybatis是什么及怎么使用

    三種關(guān)聯(lián)關(guān)系都有兩種關(guān)聯(lián)查詢的方式: 嵌套查詢,嵌套結(jié)果
    Mybatis的延遲加載配置, 在全局配置文件中加入下面代碼

    <settings>
    	<setting name=”lazyLoadingEnabled” value=”true” />
    	<setting name=”aggressiveLazyLoading” value=”false”/>
    </settings>

    在映射文件中,元素和元素中都已默認(rèn)配置了延遲加載屬性,即默認(rèn)屬性fetchType=”lazy”(屬性fetchType=”eager”表示立即加載),所以在配置文件中開啟延遲加載后,無需在映射文件中再做配置

    一對一

    使用元素進(jìn)行一對一關(guān)聯(lián)映射非常簡單,只需要參考如下兩種示例配置即可

    Mybatis是什么及怎么使用

    一對多

    <resultMap>元素中,包含了一個(gè)<collection>子元素,MyBatis就是通過該元素來處理一對多關(guān)聯(lián)關(guān)系的
    <collection>子元素的屬性大部分與<association>元素相同,但其還包含一個(gè)特殊屬性&ndash;ofType
    ofType屬性與javaType屬性對應(yīng),它用于指定實(shí)體對象中集合類屬性所包含的元素類型。
    <collection >元素的使用也非常簡單,同樣可以參考如下兩種示例進(jìn)行配置,具體代碼如下:

    Mybatis是什么及怎么使用

    多對多

    多對多的關(guān)聯(lián)關(guān)系查詢,同樣可以使用前面介紹的元素進(jìn)行處理(其用法和一對多關(guān)聯(lián)關(guān)系查詢語句用法基本相同)

    五 動(dòng)態(tài)sql

    動(dòng)態(tài) SQL 是 MyBatis 的強(qiáng)大特性之一。如果你使用過 JDBC 或其它類似的框架,你應(yīng)該能理解根據(jù)不同條件拼接 SQL 語句有多痛苦,例如拼接時(shí)要確保不能忘記添加必要的空格,還要注意去掉列表最后一個(gè)列名的逗號。利用動(dòng)態(tài) SQL,可以徹底擺脫這種痛苦。
    使用動(dòng)態(tài) SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強(qiáng)大的動(dòng)態(tài) SQL 語言,MyBatis 顯著地提升了這一特性的易用性。
    如果你之前用過 JSTL 或任何基于類 XML 語言的文本處理器,你對動(dòng)態(tài) SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時(shí)間了解大量的元素。借助功能強(qiáng)大的基于 OGNL 的表達(dá)式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現(xiàn)在要學(xué)習(xí)的元素種類比原來的一半還要少。

    • if

    • choose (when, otherwise)

    • trim (where, set)

    • foreach

    • bind

    • sql片段

    5.1 if

    EmpDao.xml

    <select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
        select * from emp where 
        <if test="empno!=null">
            empno = #{empno} and
        </if>
        <if test="ename!=null">
            ename like #{ename} and
        </if>
        <if test="sal!=null">
            sal > #{sal}
        </if>
    </select>

    上邊代碼看起來是比較正常的,但是大家需要注意的是如果我們傳入的參數(shù)值有缺失會怎么呢?這個(gè)時(shí)候拼接的sql語句就會變得有問題,例如不傳參數(shù)或者丟失最后一個(gè)參數(shù),那么語句中就會多一個(gè)where或者and的關(guān)鍵字,因此在mybatis中也給出了具體的解決方案:

    where

    where 元素只會在子元素返回任何內(nèi)容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們?nèi)コ?/p>

    <select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
        select * from emp
        <where>
            <if test="empno!=null">
                empno = #{empno}
            </if>
            <if test="ename!=null">
                and ename like #{ename}
            </if>
            <if test="sal!=null">
                and sal > #{sal}
            </if>
        </where>
    </select>

    現(xiàn)在看起來沒有什么問題了,但是我們的條件添加到了拼接sql語句的前后,那么我們該如何處理呢?

    trim

     <!--
     trim截取字符串:
     prefix:前綴,為sql整體添加一個(gè)前綴
     prefixOverrides:去除整體字符串前面多余的字符
     suffixOverrides:去除后面多余的字符串
     -->
     <select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
         select * from emp
    
         <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
             <if test="empno!=null">
                 empno = #{empno} and
             </if>
             <if test="ename!=null">
                 ename like #{ename} and
             </if>
             <if test="sal!=null">
                 sal > #{sal} and
             </if>
         </trim>
     </select>

    5.2 foreach

    動(dòng)態(tài) SQL 的另一個(gè)常見使用場景是對集合進(jìn)行遍歷(尤其是在構(gòu)建 IN 條件語句的時(shí)候)。

     <!--foreach是對集合進(jìn)行遍歷
    	 collection="deptnos" 指定要遍歷的集合
    	 close="" 表示以什么結(jié)束
    	 index="" 給定一個(gè)索引值
    	 item="" 遍歷的每一個(gè)元素的值
    	 open="" 表示以什么開始
    	 separator="" 表示多個(gè)元素的分隔符
     -->
     <select id="getEmpByDeptnos" resultType="Emp">
        select * from emp where deptno in
         <foreach collection="deptnos" close=")" index="idx" item="deptno" open="(" separator=",">
            #{deptno}
         </foreach>
     </select>

    5.3 choose、when、otherwise

    有時(shí)候,我們不想使用所有的條件,而只是想從多個(gè)條件中選擇一個(gè)使用。針對這種情況,MyBatis 提供了 choose 元素,它有點(diǎn)像 Java 中的 switch 語句。

    <select id="getEmpByConditionChoose" resultType="cn.tulingxueyuan.bean.Emp">
            select * from emp
         <where>
             <choose>
                 <when test="empno!=null">
                     empno > #{empno}
                 </when>
                 <when test="ename!=null">
                     ename like #{ename}
                 </when>
                 <when test="sal!=null">
                     sal > #{sal}
                 </when>
                 <otherwise>
                     1=1
                 </otherwise>
             </choose>
         </where>
     </select>

    5.4 set

    用于動(dòng)態(tài)更新語句的類似解決方案叫做 set。set 元素可以用于動(dòng)態(tài)包含需要更新的列,忽略其它不更新的列。

    <update id="updateEmpByEmpno">
      update emp
       <set>
           <if test="empno!=null">
              empno=#{empno},
           </if>
           <if test="ename!=null">
              ename = #{ename},
           </if>
           <if test="sal!=null">
              sal = #{sal}
           </if>
       </set>
       <where>
          empno = #{empno}
       </where>
    </update>

    5.5 bind

    bind 元素允許你在 OGNL 表達(dá)式以外創(chuàng)建一個(gè)變量,并將其綁定到當(dāng)前的上下文。比如:

    <select id="selectBlogsLike" resultType="Blog">
      <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
      SELECT * FROM BLOG
      WHERE title LIKE #{pattern}
    </select>

    5.6 sql

    這個(gè)元素可以用來定義可重用的 SQL 代碼片段,以便在其它語句中使用。 參數(shù)可以靜態(tài)地(在加載的時(shí)候)確定下來,并且可以在不同的 include 元素中定義不同的參數(shù)值。比如:

    <sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

    這個(gè) SQL 片段可以在其它語句中使用,例如:

    <select id="selectUsers" resultType="map">
      select
        <include refid="userColumns"><property name="alias" value="t1"/></include>,
        <include refid="userColumns"><property name="alias" value="t2"/></include>
      from some_table t1
        cross join some_table t2
    </select>

    六 MyBatis緩存

    6.1 緩存介紹

    MyBatis 內(nèi)置了一個(gè)強(qiáng)大的事務(wù)性查詢緩存機(jī)制,它可以非常方便地配置和定制。 為了使它更加強(qiáng)大而且易于配置,我們對 MyBatis 3 中的緩存實(shí)現(xiàn)進(jìn)行了許多改進(jìn)。
    默認(rèn)情況下,只啟用了本地的會話緩存,它僅僅對一個(gè)會話中的數(shù)據(jù)進(jìn)行緩存。 要啟用全局的二級緩存,只需要在你的 SQL 映射文件中添加一行:

    <cache/>

    當(dāng)添加上該標(biāo)簽之后,會有如下效果:

    • 映射語句文件中的所有 select 語句的結(jié)果將會被緩存。

    • 映射語句文件中的所有 insert、update 和 delete 語句會刷新緩存。

    • 緩存會使用最近最少使用算法(LRU, Least Recently Used)算法來清除不需要的緩存。

    • 緩存不會定時(shí)進(jìn)行刷新(也就是說,沒有刷新間隔)。

    • 緩存會保存列表或?qū)ο螅o論查詢方法返回哪種)的 1024 個(gè)引用。

    • 緩存會被視為讀/寫緩存,這意味著獲取到的對象并不是共享的,可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。

    在進(jìn)行配置的時(shí)候還會分為一級緩存和二級緩存:

    • 一級緩存:線程級別的緩存,是本地緩存,sqlSession級別的緩存

    • 二級緩存:全局范圍的緩存,不止局限于當(dāng)前會話

    6.2 一級緩存的使用

    一級緩存是sqlsession級別的緩存,默認(rèn)是存在的。在下面的案例中,大家發(fā)現(xiàn)我發(fā)送了兩個(gè)相同的請求,但是sql語句僅僅執(zhí)行了一次,那么就意味著第一次查詢的時(shí)候已經(jīng)將結(jié)果進(jìn)行了緩存。

    @Test
    public void test01() {
    
         SqlSession sqlSession = sqlSessionFactory.openSession();
         try {
             EmpDao mapper = sqlSession.getMapper(EmpDao.class);
             List<Emp> list = mapper.selectAllEmp();
             for (Emp emp : list) {
                 System.out.println(emp);
            }
             System.out.println("--------------------------------");
             List<Emp> list2 = mapper.selectAllEmp();
             for (Emp emp : list2) {
                 System.out.println(emp);
            }
        } catch (Exception e) {
             e.printStackTrace();
        } finally {
             sqlSession.close();
        }
    }

    在大部分的情況下一級緩存是可以的,但是有幾種特殊的情況會造成一級緩存失效:

    1、一級緩存是sqlSession級別的緩存,如果在應(yīng)用程序中只有開啟了多個(gè)sqlsession,那么會造成緩存失效

     @Test
     public void test02(){
         SqlSession sqlSession = sqlSessionFactory.openSession();
         EmpDao mapper = sqlSession.getMapper(EmpDao.class);
         List<Emp> list = mapper.selectAllEmp();
         for (Emp emp : list) {
             System.out.println(emp);
        }
         System.out.println("================================");
         SqlSession sqlSession2 = sqlSessionFactory.openSession();
         EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
         List<Emp> list2 = mapper2.selectAllEmp();
         for (Emp emp : list2) {
             System.out.println(emp);
        }
         sqlSession.close();
         sqlSession2.close();
    }

    2、在編寫查詢的sql語句的時(shí)候,一定要注意傳遞的參數(shù),如果參數(shù)不一致,那么也不會緩存結(jié)果
    3、如果在發(fā)送過程中發(fā)生了數(shù)據(jù)的修改,那么結(jié)果就不會緩存

    @Test
    public void test03(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        EmpDao mapper = sqlSession.getMapper(EmpDao.class);
        Emp empByEmpno = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno);
        System.out.println("================================");
        empByEmpno.setEname("zhangsan");
        int i = mapper.updateEmp(empByEmpno);
        System.out.println(i);
        System.out.println("================================");
        Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno1);
        sqlSession.close();
    }

    4、在兩次查詢期間,手動(dòng)去清空緩存,也會讓緩存失效

    @Test
    public void test03(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        EmpDao mapper = sqlSession.getMapper(EmpDao.class);
        Emp empByEmpno = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno);
        System.out.println("================================");
        System.out.println("手動(dòng)清空緩存");
        sqlSession.clearCache();
        System.out.println("================================");
        Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno1);
        sqlSession.close();
    }

    特性

    一級緩存特性:

    1. 默認(rèn)就開啟了,也可以關(guān)閉一級緩存 localCacheScope=STATEMENT

    2. 作用域:是基于sqlSession(默認(rèn)),一次數(shù)據(jù)庫操作會話。

    3. 緩存默認(rèn)實(shí)現(xiàn)類PerpetualCache ,使用map進(jìn)行存儲的

    4. 查詢完就會進(jìn)行存儲

    5. 先從二級緩存中獲取,再從一級緩存中獲取 key==> sqlid+sql

    一級緩存失效情況:

    1. 不同的sqlSession會使一級緩存失效

    2. 同一個(gè)SqlSession,但是查詢語句不一樣

    3. 同一個(gè)SqlSession,查詢語句一樣,期間執(zhí)行增刪改操作

    4. 同一個(gè)SqlSession,查詢語句一樣,執(zhí)行手動(dòng)清除緩存

    6.3 二級緩存的使用

    二級緩存是全局作用域緩存,默認(rèn)是不開啟的,需要手動(dòng)進(jìn)行配置。
    Mybatis提供二級緩存的接口以及實(shí)現(xiàn),緩存實(shí)現(xiàn)的時(shí)候要求實(shí)體類實(shí)現(xiàn)Serializable接口,二級緩存在sqlSession關(guān)閉或提交之后才會生效。

    二級緩存的使用

    步驟:
    1、全局配置文件中添加如下配置:

    <setting name="cacheEnabled" value="true"/>

    2、需要在使用二級緩存的映射文件處使用標(biāo)簽標(biāo)注
    3、實(shí)體類必須要實(shí)現(xiàn)Serializable接口

    @Test
    public void test04(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        EmpDao mapper = sqlSession.getMapper(EmpDao.class);
        EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
        Emp empByEmpno = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno);
        sqlSession.close();
    
        Emp empByEmpno1 = mapper2.findEmpByEmpno(1111);
        System.out.println(empByEmpno1);
        sqlSession2.close();
    }

    緩存的屬性

    • eviction:表示緩存回收策略,默認(rèn)是

      • LRU LRU:最近最少使用的,移除最長時(shí)間不被使用的對象

      • FIFO:先進(jìn)先出,按照對象進(jìn)入緩存的順序來移除

      • SOFT:軟引用,移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象

      • WEAK:弱引用,更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象

    • flushInternal:刷新間隔,單位毫秒

      • 默認(rèn)情況是不設(shè)置,也就是沒有刷新間隔,緩存僅僅調(diào)用語句時(shí)刷新

    • size:引用數(shù)目,正整數(shù)

      • 代表緩存最多可以存儲多少個(gè)對象,太大容易導(dǎo)致內(nèi)存溢出

    • readonly:只讀,true/false

      • true:只讀緩存,會給所有調(diào)用這返回緩存對象的相同實(shí)例,因此這些對象不能被修改。

      • false:讀寫緩存,會返回緩存對象的拷貝(序列化實(shí)現(xiàn)),這種方式比較安全,默認(rèn)值

    //可以看到會去二級緩存中查找數(shù)據(jù),而且二級緩存跟一級緩存中不會同時(shí)存在數(shù)據(jù),因?yàn)槎壘彺嬷械臄?shù)據(jù)是在sqlsession 關(guān)閉之后才生效的
    @Test
    public void test05(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        EmpDao mapper = sqlSession.getMapper(EmpDao.class);
        Emp empByEmpno = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno);
        sqlSession.close();
    
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
        Emp empByEmpno2 = mapper2.findEmpByEmpno(1111);
        System.out.println(empByEmpno2);
        Emp empByEmpno3 = mapper2.findEmpByEmpno(1111);
        System.out.println(empByEmpno3);
        sqlSession2.close();
    }

    緩存查詢的順序是先查詢二級緩存再查詢一級緩存

    @Test
    public void test05(){
         SqlSession sqlSession = sqlSessionFactory.openSession();
         EmpDao mapper = sqlSession.getMapper(EmpDao.class);
         Emp empByEmpno = mapper.findEmpByEmpno(1111);
         System.out.println(empByEmpno);
         sqlSession.close();
    
         SqlSession sqlSession2 = sqlSessionFactory.openSession();
         EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
         Emp empByEmpno2 = mapper2.findEmpByEmpno(1111);
         System.out.println(empByEmpno2);
         Emp empByEmpno3 = mapper2.findEmpByEmpno(1111);
         System.out.println(empByEmpno3);
    
         Emp empByEmpno4 = mapper2.findEmpByEmpno(7369);
         System.out.println(empByEmpno4);
         Emp empByEmpno5 = mapper2.findEmpByEmpno(7369);
         System.out.println(empByEmpno5);
         sqlSession2.close();
    }

    二級緩存的作用范圍

    如果設(shè)置了全局的二級緩存配置,那么在使用的時(shí)候需要注意,在每一個(gè)單獨(dú)的select語句中,可以設(shè)置將查詢緩存關(guān)閉,以完成特殊的設(shè)置

    1、在setting中設(shè)置,是配置二級緩存開啟,一級緩存默認(rèn)一直開啟

    <setting name="cacheEnabled" value="true"/>

    2、select標(biāo)簽的useCache屬性:

    在每一個(gè)select的查詢中可以設(shè)置當(dāng)前查詢是否要使用二級緩存,只對二級緩存有效

    3、sql標(biāo)簽的flushCache屬性

    增刪改操作默認(rèn)值為true,sql執(zhí)行之后會清空一級緩存和二級緩存,而查詢操作默認(rèn)是false

    4、sqlSession.clearCache()

    只是用來清除一級緩存

    二級緩存特性

    • 默認(rèn)開啟了,沒有實(shí)現(xiàn)

    • 作用域:基于全局范圍,應(yīng)用級別。

    • 緩存默認(rèn)實(shí)現(xiàn)類PerpetualCache ,使用map進(jìn)行存儲的但是二級緩存根據(jù)不同的mapper命名空間多包了一層map

     : org.apache.ibatis.session.Configuration#caches    key:mapper命名空間   value:erpetualCache.map
     key==> sqlid+sql
    • 事務(wù)提交的時(shí)候(sqlSession關(guān)閉)

    • 先從二級緩存中獲取,再從一級緩存中獲取

    實(shí)現(xiàn):

    1. 開啟二級緩存<setting name="cacheEnabled" value="true"/>

    2. 在需要使用到二級緩存的映射文件中加入,基于Mapper映射文件來實(shí)現(xiàn)緩存的,基于Mapper映射文件的命名空間來存儲的

    3. 在需要使用到二級緩存的javaBean中實(shí)現(xiàn)序列化接口implements Serializable 配置成功就會出現(xiàn)緩存命中率 同一個(gè)sqlId: 從緩存中拿出的次數(shù)/查詢總次數(shù)

    失效:

    1. 同一個(gè)命名空間進(jìn)行了增刪改的操作,會導(dǎo)致二級緩存失效 但是如果不想失效:可以將SQL的flushCache 這是為false,但是要慎重設(shè)置,因?yàn)闀斐蓴?shù)據(jù)臟讀問題,除非你能保證查詢的數(shù)據(jù)永遠(yuǎn)不會執(zhí)行增刪改

    2. 讓查詢不緩存數(shù)據(jù)到二級緩存中useCache=“false”

    3. 如果希望其他mapper映射文件的命名空間執(zhí)行了增刪改清空另外的命名空間就可以設(shè)置:

    <cache-ref namespace="cn.tulingxueyuan.mapper.DeptMapper"/>

    6.4 整合第三方緩存

    整合redis

    添加redis-mybatis 緩存適配器 依賴

    <dependencies>
        <!--添加依賴-->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-redis</artifactId>
            <version>1.0.0-beta2</version>
        </dependency>
    </dependencies>

    添加redis.properties在resources根目錄

    host=localhost
    port=6379
    connectionTimeout=5000
    soTimeout=5000
    password=無密碼可不填
    database=0
    clientName=

    設(shè)置mybatis二級緩存實(shí)現(xiàn)類

    <cache ... type="org.mybatis.caches.redis.RedisCache" ....../>

    整合ehcache

    導(dǎo)入對應(yīng)的maven依賴

    <!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
     <dependency>
         <groupId>org.ehcache</groupId>
         <artifactId>ehcache</artifactId>
         <version>3.8.1</version>
     </dependency>
     <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
     <dependency>
         <groupId>org.mybatis.caches</groupId>
         <artifactId>mybatis-ehcache</artifactId>
         <version>1.2.0</version>
     </dependency>

    導(dǎo)入ehcache配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <!-- 磁盤保存路徑 -->
    <diskStore path="D:\ehcache" />
    
    <defaultCache
      maxElementsInMemory="1"
      maxElementsOnDisk="10000000"
      eternal="false"
      overflowToDisk="true"
      timeToIdleSeconds="120"
      timeToLiveSeconds="120"
      diskExpiryThreadIntervalSeconds="120"
      memoryStoreEvictionPolicy="LRU">
    </defaultCache>
    </ehcache>
    
    <!--
    屬性說明:
    l diskStore:指定數(shù)據(jù)在磁盤中的存儲位置。
    l defaultCache:當(dāng)借助CacheManager.add("demoCache")創(chuàng)建Cache時(shí),EhCache便會采用<defalutCache/>指定的的管理策略
    
    以下屬性是必須的:
    l maxElementsInMemory - 在內(nèi)存中緩存的element的最大數(shù)目
    l maxElementsOnDisk - 在磁盤上緩存的element的最大數(shù)目,若是0表示無窮大
    l eternal - 設(shè)定緩存的elements是否永遠(yuǎn)不過期。如果為true,則緩存的數(shù)據(jù)始終有效,如果為false那么還要根據(jù)timeToIdleSeconds,timeToLiveSeconds判斷
    l overflowToDisk - 設(shè)定當(dāng)內(nèi)存緩存溢出的時(shí)候是否將過期的element緩存到磁盤上
    
    以下屬性是可選的:
    l timeToIdleSeconds - 當(dāng)緩存在EhCache中的數(shù)據(jù)前后兩次訪問的時(shí)間超過timeToIdleSeconds的屬性取值時(shí),這些數(shù)據(jù)便會刪除,默認(rèn)值是0,也就是可閑置時(shí)間無窮大
    l timeToLiveSeconds - 緩存element的有效生命期,默認(rèn)是0.,也就是element存活時(shí)間無窮大
    diskSpoolBufferSizeMB 這個(gè)參數(shù)設(shè)置DiskStore(磁盤緩存)的緩存區(qū)大小.默認(rèn)是30MB.每個(gè)Cache都應(yīng)該有自己的一個(gè)緩沖區(qū).
    l diskPersistent - 在VM重啟的時(shí)候是否啟用磁盤保存EhCache中的數(shù)據(jù),默認(rèn)是false。
    l diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運(yùn)行間隔,默認(rèn)是120秒。每個(gè)120s,相應(yīng)的線程會進(jìn)行一次EhCache中數(shù)據(jù)的清理工作
    l memoryStoreEvictionPolicy - 當(dāng)內(nèi)存緩存達(dá)到最大,有新的element加入的時(shí)候, 移除緩存中element的策略。默認(rèn)是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進(jìn)先出)
    -->

    在mapper文件中添加自定義緩存

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

    七 MyBatis分頁插件&逆向工程

    7.1 分頁插件

    MyBatis 通過提供插件機(jī)制,讓我們可以根據(jù)自己的需要去增強(qiáng)MyBatis 的功能。需要注意的是,如果沒有完全理解MyBatis 的運(yùn)行原理和插件的工作方式,最好不要使用插件,因?yàn)樗鼤淖兿档讓拥墓ぷ鬟壿?,給系統(tǒng)帶來很大的影響。
      MyBatis 的插件可以在不修改原來的代碼的情況下,通過攔截的方式,改變四大核心對象的行為,比如處理參數(shù),處理SQL,處理結(jié)果。

    Mybatis插件典型適用場景

    分頁功能
    mybatis的分頁默認(rèn)是基于內(nèi)存分頁的(查出所有,再截?。?,數(shù)據(jù)量大的情況下效率較低,不過使用mybatis插件可以改變該行為,只需要攔截StatementHandler類的prepare方法,改變要執(zhí)行的SQL語句為分頁語句即可;

    公共字段統(tǒng)一賦值
    一般業(yè)務(wù)系統(tǒng)都會有創(chuàng)建者,創(chuàng)建時(shí)間,修改者,修改時(shí)間四個(gè)字段,對于這四個(gè)字段的賦值,實(shí)際上可以在DAO層統(tǒng)一攔截處理,可以用mybatis插件攔截Executor類的update方法,對相關(guān)參數(shù)進(jìn)行統(tǒng)一賦值即可;

    性能監(jiān)控
    對于SQL語句執(zhí)行的性能監(jiān)控,可以通過攔截Executor類的update, query等方法,用日志記錄每個(gè)方法執(zhí)行的時(shí)間;

    其它
    其實(shí)mybatis擴(kuò)展性還是很強(qiáng)的,基于插件機(jī)制,基本上可以控制SQL執(zhí)行的各個(gè)階段,如執(zhí)行階段,參數(shù)處理階段,語法構(gòu)建階段,結(jié)果集處理階段,具體可以根據(jù)項(xiàng)目業(yè)務(wù)來實(shí)現(xiàn)對應(yīng)業(yè)務(wù)邏輯。

    實(shí)現(xiàn)思考:
    第一個(gè)問題:
      不修改對象的代碼,怎么對對象的行為進(jìn)行修改,比如說在原來的方法前面做一點(diǎn)事情,在原來的方法后面做一點(diǎn)事情?
      答案:大家很容易能想到用代理模式,這個(gè)也確實(shí)是MyBatis 插件的原理。
      
    第二個(gè)問題:
      我們可以定義很多的插件,那么這種所有的插件會形成一個(gè)鏈路,比如我們提交一個(gè)休假申請,先是項(xiàng)目經(jīng)理審批,然后是部門經(jīng)理審批,再是HR 審批,再到總經(jīng)理審批,怎么實(shí)現(xiàn)層層的攔截?
      答案:插件是層層攔截的,我們又需要用到另一種設(shè)計(jì)模式&mdash;&mdash;責(zé)任鏈模式。
      
      在之前的源碼中我們也發(fā)現(xiàn)了,mybatis內(nèi)部對于插件的處理確實(shí)使用的代理模式,既然是代理模式,我們應(yīng)該了解MyBatis 允許哪些對象的哪些方法允許被攔截,并不是每一個(gè)運(yùn)行的節(jié)點(diǎn)都是可以被修改的。只有清楚了這些對象的方法的作用,當(dāng)我們自己編寫插件的時(shí)候才知道從哪里去攔截。在MyBatis 官網(wǎng)有答案,我們來看一下:https://mybatis.org/mybatis-3/zh/configuration.html#plugins

    Mybatis是什么及怎么使用

    Executor 會攔截到CachingExcecutor 或者BaseExecutor。因?yàn)閯?chuàng)建Executor 時(shí)是先創(chuàng)建CachingExcecutor,再包裝攔截。從代碼順序上能看到。我們可以通過mybatis的分頁插件來看看整個(gè)插件從包裝攔截器鏈到執(zhí)行攔截器鏈的過程。
      在查看插件原理的前提上,我們需要來看看官網(wǎng)對于自定義插件是怎么來做的,官網(wǎng)上有介紹:通過 MyBatis 提供的強(qiáng)大機(jī)制,使用插件是非常簡單的,只需實(shí)現(xiàn) Interceptor 接口,并指定想要攔截的方法簽名即可。這里本人踩了一個(gè)坑,在Springboot中集成,同時(shí)引入了pagehelper-spring-boot-starter 導(dǎo)致RowBounds參數(shù)的值被刷掉了,也就是走到了我的攔截其中沒有被設(shè)置值,這里需要注意,攔截器出了問題,可以Debug看一下Configuration配置類中攔截器鏈的包裝情況。

    自定義分頁插件

    @Intercepts({
            @Signature(type = Executor.class,method = "query" ,args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ), // 需要代理的對象和方法
            @Signature(type = Executor.class,method = "query" ,args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} ) // 需要代理的對象和方法
    })
    public class MyPageInterceptor implements Interceptor {
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("簡易版的分頁插件:邏輯分頁改成物理分頁");
    
            // 修改sql 拼接Limit 0,10
            Object[] args = invocation.getArgs();
            // MappedStatement 對mapper映射文件里面元素的封裝
            MappedStatement ms= (MappedStatement) args[0];
            // BoundSql 對sql和參數(shù)的封裝
            Object parameterObject=args[1];
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            // RowBounds 封裝了邏輯分頁的參數(shù) :當(dāng)前頁offset,一頁數(shù)limit
            RowBounds rowBounds= (RowBounds) args[2];
    
            // 拿到原來的sql語句
            String sql = boundSql.getSql();
            String limitSql=sql+ " limit "+rowBounds.getOffset()+","+ rowBounds.getLimit();
    
            //將分頁sql重新封裝一個(gè)BoundSql 進(jìn)行后續(xù)執(zhí)行
            BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), limitSql, boundSql.getParameterMappings(), parameterObject);
    
            // 被代理的對象
            Executor executor= (Executor) invocation.getTarget();
            CacheKey cacheKey = executor.createCacheKey(ms, parameterObject, rowBounds, pageBoundSql);
            // 調(diào)用修改過后的sql繼續(xù)執(zhí)行查詢
            return  executor.query(ms,parameterObject,rowBounds, (ResultHandler) args[3],cacheKey,pageBoundSql);
        }
    }

    攔截簽名跟參數(shù)的順序有嚴(yán)格要求,如果按照順序找不到對應(yīng)方法會拋出異常:

    org.apache.ibatis.exceptions.PersistenceException:
                ### Error opening session.  Cause: org.apache.ibatis.plugin.PluginException: 
                Could not find method on interface org.apache.ibatis.executor.Executor named queryv

    MyBatis 啟動(dòng)時(shí)掃描 標(biāo)簽, 注冊到Configuration 對象的 InterceptorChain 中。property 里面的參數(shù),會調(diào)用setProperties()方法處理。

    分頁插件使用
    添加pom依賴:

    <dependency>
    	<groupId>com.github.pagehelper</groupId>
    	<artifactId>pagehelper</artifactId>
    	<version>1.2.15</version>
    </dependency>

    插件注冊,在mybatis-config.xml 中注冊插件:

    <configuration>
    
    	<plugins>
    		<!-- com.github.pagehelper為PageHelper類所在包名 -->
    		<plugin interceptor="com.github.pagehelper.PageHelper">
    			<property name="helperDialect" value="mysql" />
    			<!-- 該參數(shù)默認(rèn)為false -->
    			<!-- 設(shè)置為true時(shí),會將RowBounds第一個(gè)參數(shù)offset當(dāng)成pageNum頁碼使用 -->
    			<!-- 和startPage中的pageNum效果一樣 -->
    			<property name="offsetAsPageNum" value="true" />
    			<!-- 該參數(shù)默認(rèn)為false -->
    			<!-- 設(shè)置為true時(shí),使用RowBounds分頁會進(jìn)行count查詢 -->
    			<property name="rowBoundsWithCount" value="true" />
    			<!-- 設(shè)置為true時(shí),如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結(jié)果 -->
    			<!-- (相當(dāng)于沒有執(zhí)行分頁查詢,但是返回結(jié)果仍然是Page類型) -->
    			<property name="pageSizeZero" value="true" />
    			<!-- 3.3.0版本可用 - 分頁參數(shù)合理化,默認(rèn)false禁用 -->
    			<!-- 啟用合理化時(shí),如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最后一頁 -->
    			<!-- 禁用合理化時(shí),如果pageNum<1或pageNum>pages會返回空數(shù)據(jù) -->
    			<property name="reasonable" value="true" />
    			<!-- 3.5.0版本可用 - 為了支持startPage(Object params)方法 -->
    			<!-- 增加了一個(gè)`params`參數(shù)來配置參數(shù)映射,用于從Map或ServletRequest中取值 -->
    			<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認(rèn)值 -->
    			<!-- 不理解該含義的前提下,不要隨便復(fù)制該配置 -->
    			<property name="params" value="pageNum=start;pageSize=limit;" />
    		</plugin>
    	</plugins>
    </configuration>

    調(diào)用

    // 獲取配置文件
    InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
    // 通過加載配置文件獲取SqlSessionFactory對象
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        // Mybatis在getMapper就會給我們創(chuàng)建jdk動(dòng)態(tài)代理
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        PageHelper.startPage(1, 5);
        List<Emp> list=mapper.selectAll(); 
        PageInfo<ServiceStation> info = new PageInfo<ServiceStation>(list, 3);                   
              System.out.println("當(dāng)前頁碼:"+info.getPageNum());
              System.out.println("每頁的記錄數(shù):"+info.getPageSize());
              System.out.println("總記錄數(shù):"+info.getTotal());
              System.out.println("總頁碼:"+info.getPages());
              System.out.println("是否第一頁:"+info.isIsFirstPage());
              System.out.println("連續(xù)顯示的頁碼:");
              int[] nums = info.getNavigatepageNums();
              for (int i = 0; i < nums.length; i++) {
                   System.out.println(nums[i]);
              }     
    }

    代理和攔截是怎么實(shí)現(xiàn)的?
      上面提到的可以被代理的四大對象都是什么時(shí)候被代理的呢?Executor 是openSession() 的時(shí)候創(chuàng)建的; StatementHandler 是SimpleExecutor.doQuery()創(chuàng)建的;里面包含了處理參數(shù)的ParameterHandler 和處理結(jié)果集的ResultSetHandler 的創(chuàng)建,創(chuàng)建之后即調(diào)用InterceptorChain.pluginAll(),返回層層代理后的對象。代理是由Plugin 類創(chuàng)建。在我們重寫的 plugin() 方法里面可以直接調(diào)用returnPlugin.wrap(target, this);返回代理對象。
      單個(gè)插件的情況下,代理能不能被代理?代理順序和調(diào)用順序的關(guān)系? 可以被代理。

    Mybatis是什么及怎么使用

    因?yàn)榇眍愂荘lugin,所以最后調(diào)用的是Plugin 的invoke()方法。它先調(diào)用了定義的攔截器的intercept()方法??梢酝ㄟ^invocation.proceed()調(diào)用到被代理對象被攔截的方法。

    Mybatis是什么及怎么使用

    調(diào)用流程時(shí)序圖:

    Mybatis是什么及怎么使用

    PageHelper 原理
      先來看一下分頁插件的簡單用法:

    PageHelper.startPage(1, 3);
    List<Blog> blogs = blogMapper.selectBlogById2(blog);
    PageInfo page = new PageInfo(blogs, 3);

    對于插件機(jī)制我們上面已經(jīng)介紹過了,在這里我們自然的會想到其所涉及的核心類 :PageInterceptor。攔截的是Executor 的兩個(gè)query()方法,要實(shí)現(xiàn)分頁插件的功能,肯定是要對我們寫的sql進(jìn)行改寫,那么一定是在 intercept 方法中進(jìn)行操作的,我們會發(fā)現(xiàn)這么一行代碼:

     String pageSql = this.dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);

    調(diào)用到 AbstractHelperDialect 中的 getPageSql 方法:

    public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
            // 獲取sql
            String sql = boundSql.getSql();
            //獲取分頁參數(shù)對象
            Page page = this.getLocalPage();
            return this.getPageSql(sql, page, pageKey);
        }

    這里可以看到會去調(diào)用 this.getLocalPage(),我們來看看這個(gè)方法:

    public <T> Page<T> getLocalPage() {
      return PageHelper.getLocalPage();
    }
    //線程獨(dú)享
    protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
    public static <T> Page<T> getLocalPage() {
      return (Page)LOCAL_PAGE.get();
    }

    可以發(fā)現(xiàn)這里是調(diào)用的是PageHelper的一個(gè)本地線程變量中的一個(gè) Page對象,從其中獲取我們所設(shè)置的 PageSize 與 PageNum,那么他是怎么設(shè)置值的呢?請看:

    PageHelper.startPage(1, 3);
    
    public static <E> Page<E> startPage(int pageNum, int pageSize) {
            return startPage(pageNum, pageSize, true);
    }
    
    public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
            Page<E> page = new Page(pageNum, pageSize, count);
            page.setReasonable(reasonable);
            page.setPageSizeZero(pageSizeZero);
            Page<E> oldPage = getLocalPage();
            if (oldPage != null && oldPage.isOrderByOnly()) {
                page.setOrderBy(oldPage.getOrderBy());
         }
            //設(shè)置頁數(shù),行數(shù)信息
            setLocalPage(page);
            return page;
    }
    
    protected static void setLocalPage(Page page) {
            //設(shè)置值
            LOCAL_PAGE.set(page);
    }

    在我們調(diào)用 PageHelper.startPage(1, 3); 的時(shí)候,系統(tǒng)會調(diào)用 LOCAL_PAGE.set(page) 進(jìn)行設(shè)置,從而在分頁插件中可以獲取到這個(gè)本地變量對象中的參數(shù)進(jìn)行 SQL 的改寫,由于改寫有很多實(shí)現(xiàn),我們這里用的Mysql的實(shí)現(xiàn):

    Mybatis是什么及怎么使用

     在這里我們會發(fā)現(xiàn)分頁插件改寫SQL的核心代碼,這個(gè)代碼就很清晰了,不必過多贅述:

    public String getPageSql(String sql, Page page, CacheKey pageKey) {
            StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
            sqlBuilder.append(sql);
            if (page.getStartRow() == 0) {
                sqlBuilder.append(" LIMIT ");
                sqlBuilder.append(page.getPageSize());
            } else {
                sqlBuilder.append(" LIMIT ");
                sqlBuilder.append(page.getStartRow());
                sqlBuilder.append(",");
                sqlBuilder.append(page.getPageSize());
                pageKey.update(page.getStartRow());
            }
    
            pageKey.update(page.getPageSize());
            return sqlBuilder.toString();
    }

    PageHelper 就是這么一步一步的改寫了我們的SQL 從而達(dá)到一個(gè)分頁的效果。
    關(guān)鍵類總結(jié):
      

    Mybatis是什么及怎么使用

    7.2 MyBatis逆向工程

    引入pom依賴

    <dependency>
       <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.4.0</version>
    </dependency>

    編寫配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
      PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
      "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
    <!-- 指定數(shù)據(jù)庫驅(qū)動(dòng)
    	用java代碼的方式生成可以不指定(只需要吧mybatis-generator和數(shù)據(jù)庫驅(qū)動(dòng)依賴到項(xiàng)目)
    
      <classPathEntry location ="F:\java\jar\mysql-connector-java-5.1.22-bin.jar" /> -->
      
      <!-- targetRuntime
      	MyBatis3  可以生成通用查詢,可以指定動(dòng)態(tài)where條件
        MyBatis3Simple 只生成CURD
       -->
      <context id="DB2Tables" targetRuntime="MyBatis3">
      
      <!-- 關(guān)于注釋的生成規(guī)則 -->
    	<commentGenerator>
    	<!-- 設(shè)置不生成注釋 -->
    		<property name="suppressAllComments" value="true"/>
    	</commentGenerator>
    	
      <!-- 數(shù)據(jù)庫的連接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/bookstore"
            userId="root"
            password="root">
        </jdbcConnection>
    
    <!-- java類型生成方式 -->
        <javaTypeResolver >
        <!-- forceBigDecimals 
    	    	true 當(dāng)數(shù)據(jù)庫類型為decimal 或number 生成對應(yīng)的java的BigDecimal
    	    	false 會根據(jù)可數(shù)據(jù)的數(shù)值長度生成不同的對應(yīng)java類型
    	    useJSR310Types
    	        false 所有數(shù)據(jù)庫的日期類型都會生成java的 Date類型	
    	        true  會將數(shù)據(jù)庫的日期類型生成對應(yīng)的JSR310的日期類型
    	        		比如 mysql的數(shù)據(jù)庫類型是date===>LocalDate
    	        		必須jdk是1.8以上
        -->
          <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
    
    	<!-- pojo的生成規(guī)則 -->
        <javaModelGenerator  
        	targetPackage="cn.tuling.pojo" targetProject="./src/main/java">
          <property name="enableSubPackages" value="true" />
          <property name="trimStrings" value="true" />
        </javaModelGenerator>
    
    	<!-- sql映射文件的生成規(guī)則 -->
        <sqlMapGenerator targetPackage="cn.tuling.mapper"  targetProject="./src/main/resources">
          <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
    
    	<!-- dao的接口生成規(guī)則 UserMapper-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="cn.tuling.mapper"  targetProject="./src/main/java">
          <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
    
        <table tableName="emp" domainObjectName="Emp" mapperName="EmpMapper" ></table>
     	<table tableName="dept" domainObjectName="Dept" mapperName="DeptMapper" ></table>
    
    
      </context>
    </generatorConfiguration>

    編寫測試類

    public class MBGTest {
    
        @Test
        public void test01() throws Exception {
            List<String> warnings = new ArrayList<String>();
            boolean overwrite = true;
            File configFile = new File("generatorConfig.xml");
            ConfigurationParser cp = new ConfigurationParser(warnings);
            Configuration config = cp.parseConfiguration(configFile);
            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
            MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
            myBatisGenerator.generate(null);
        }
    }

    調(diào)用

    @Test
    public void test01() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            // Mybatis在getMapper就會給我們創(chuàng)建jdk動(dòng)態(tài)代理
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    
            Emp emp = mapper.selectByPrimaryKey(4);
            System.out.println(emp);
        }
    }
    
    
    
    /**
     * Mybatis3生成調(diào)用方式
     */
    @Test
    public void test02() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            // Mybatis在getMapper就會給我們創(chuàng)建jdk動(dòng)態(tài)代理
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    
            // 使用Example實(shí)現(xiàn)動(dòng)態(tài)條件語句
            EmpExample empExample=new EmpExample();
            EmpExample.Criteria criteria = empExample.createCriteria();
            criteria.andUserNameLike("%帥%")
                    .andIdEqualTo(4);
    
            List<Emp> emps = mapper.selectByExample(empExample);
            System.out.println(emps);
        }
    }

    到此,關(guān)于“Mybatis是什么及怎么使用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI