欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Mybatis的介紹、基本使用、高級使用

 更新時間:2023年03月27日 15:12:54   作者:麥芽糖0219  
這篇文章主要介紹了Mybatis的介紹、基本使用、高級使用,Mybatis是一款半自動的ORM持久層框架,具有較高的SQL靈活性,如何使用看這篇就夠了,需要的朋友可以參考下

一 數(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ù)庫跨平臺

在這里插入圖片描述

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ù)庫操作實用工具,小巧簡單實用。
DBUtils封裝了對JDBC的操作,簡化了JDBC操作,可以少寫代碼。
DBUtils三個核心功能介紹

  1. QueryRunner中提供對sql語句操作的API
  2. ResultSetHandler接口,用于定義select操作后,怎樣封裝結(jié)果集
  3. DBUtils類,它就是一個工具類,定義了關(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ī)制和模式的那些對象。

在這里插入圖片描述

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ù)查詢提供了多個重載的模板方法,你可以根據(jù)需要選用不同的模板方法.如果你的查詢很簡單,僅僅是傳入相應(yīng)SQL或者相關(guān)參數(shù),然后取得一個單一的結(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)秀的持久層框架/半自動的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)簽,支持編寫動態(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 取個名字
            class 不同實現(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)前日志級別輸出到哪個追加器上面 -->
        <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的項目中,我們發(fā)現(xiàn)了有一個mybatis-config.xml的配置文件,這個配置文件是mybatis的全局配置文件,用來進(jìn)行相關(guān)的全局配置,在任何操作下都生效的配置。下面我們要針對其中的屬性做詳細(xì)的解釋,方便大家在后續(xù)使用的時候更加熟練。

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

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)行時的行為,是mybatis中的重要配置-->
    <settings>
        <!--設(shè)置列名映射的時候是否是駝峰標(biāo)識-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--typeAliases表示為我們引用的實體類起別名,默認(rèn)情況下我們需要寫類的完全限定名
    如果在此處做了配置,那么可以直接寫類的名稱,在type中配置上類的完全限定名,在使用的時候可以忽略大小寫
    還可以通過alias屬性來表示類的別名
    -->
    <typeAliases>
<!--        <typeAlias type="cn.tulingxueyuan.bean.Emp" alias="Emp"></typeAlias>-->
        <!--如果需要引用多個類,那么給每一個類起別名肯定會很麻煩,因此可以指定對應(yīng)的包名,那么默認(rèn)用的是類名-->
        <package name="cn.tulingxueyuan.bean"/>
    </typeAliases>
    <!--
    在實際的開發(fā)過程中,我們可能分為開發(fā)環(huán)境,生產(chǎn)環(huán)境,測試環(huán)境等等,每個環(huán)境的配置可以是不一樣的
    environment就用來表示不同環(huán)境的細(xì)節(jié)配置,每一個環(huán)境中都需要一個事務(wù)管理器以及數(shù)據(jù)源的配置
    我們在后續(xù)的項目開發(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ù)庫的操作實現(xiàn)
    在編寫不同的sql語句的時候,可以指定databaseId屬性來標(biāo)識當(dāng)前sql語句可以運(yùn)行在哪個數(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è)置相同的文件名稱,同時可以使用注解的方式來進(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)包含多個配置文件或者配置類的時候,可以使用批量注冊的功能,也就是引入對應(yīng)的包,而不是具體的配置文件或者類
        但是需要注意的是,
        1、如果使用的配置文件的形式,必須要將配置文件跟dao類放在一起,這樣才能找到對應(yīng)的配置文件.
            如果是maven的項目的話,還需要添加以下配置,原因是maven在編譯的文件的時候只會編譯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 映射文件只有很少的幾個頂級元素(按照應(yīng)被定義的順序列出):

  • cache – 該命名空間的緩存配置。
  • cache-ref – 引用其它命名空間的緩存配置。
  • resultMap – 描述如何從數(shù)據(jù)庫結(jié)果集中加載對象,是最復(fù)雜也是最強(qiáng)大的元素。
  • parameterMap – 老式風(fēng)格的參數(shù)映射。此元素已被廢棄,并可能在將來被移除!請使用行內(nèi)參數(shù)映射。文檔中不會介紹此元素。
  • sql – 可被其它語句引用的可重用語句塊。
  • insert – 映射插入語句。
  • update – 映射更新語句。
  • delete – 映射刪除語句。
  • select – 映射查詢語句。

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

insert、update、delete元素

屬性描述
id在命名空間中唯一的標(biāo)識符,可以被用來引用這條語句。
parameterType將會傳入這條語句的參數(shù)的類全限定名或別名。這個屬性是可選的,因為 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這個設(shè)置是在拋出異常之前,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)。默認(rèn)值為未設(shè)置(unset)(依賴數(shù)據(jù)庫驅(qū)動)。
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)的自動遞增字段),默認(rèn)值:false。
keyProperty(僅適用于 insert 和 update)指定能夠唯一識別對象的屬性,MyBatis 會使用 getGeneratedKeys 的返回值或 insert 語句的 selectKey 子元素設(shè)置它的值,默認(rèn)值:未設(shè)置(unset)。如果生成列不止一個,可以用逗號分隔多個屬性名稱。
keyColumn(僅適用于 insert 和 update)設(shè)置生成鍵值在表中的列名,在某些數(shù)據(jù)庫(像 PostgreSQL)中,當(dāng)主鍵列不是表中的第一列的時候,是必須設(shè)置的。如果生成列不止一個,可以用逗號分隔多個屬性名稱。
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語句的時候有兩種取值的方式,分別是#{}和${},下面來看一下他們之間的區(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)險。不推薦使用。
        特殊用法:
            1.調(diào)試情況下可以臨時使用。
            2.實現(xiàn)一些特殊功能:前提一定要保證數(shù)據(jù)的安全性。
             比如:動態(tài)表、動態(tài)列. 動態(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.單個參數(shù):SelectEmp(Integer id);
       mybatis 不會做任何特殊要求
       獲取方式:
           #:{輸入任何字符獲取參數(shù)}

   2.多個參數(shù):Emp SelectEmp(Integer id,String username);
       mybatis 會進(jìn)行封裝
       會將傳進(jìn)來的參數(shù)封裝成map:
       1個值就會對應(yīng)2個map項 :  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}
            除了使用這種方式還有別的方式,因為這種方式參數(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ù):
       單個參數(shù):Emp SelectEmp(Emp emp);
       獲取方式:可以直接使用屬性名
           emp.id=====>#{id}
           emp.username=====>#{username}
       多個參數(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會自動封裝為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會自動封裝為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)建一個對應(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é)果是集合的時候,返回值的類型依然寫的是集合中具體的類型-->
    <select id="selectAllEmp" resultType="cn.tulingxueyuan.bean.Emp">
        select  * from emp
    </select>
<!--在查詢的時候可以設(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é)果是一個集合對象的時候,返回值的類型一定要寫集合具體value的類型,
    同時在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é)果集—resultMap

<!--1.聲明resultMap自定義結(jié)果集   resultType 和 resultMap 只能使用一個。
    id 唯一標(biāo)識, 需要和<select 上的resultMap 進(jìn)行對應(yīng)
    type 需要映射的pojo對象, 可以設(shè)置別名
    autoMapping 自動映射,(默認(rèn)=true) 只要字段名和屬性名遵循映射規(guī)則就可以自動映射,但是不建議,哪怕屬性名和字段名一一對應(yīng)上了也要顯示的配置映射
    extends  如果多個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ì)使用——高級結(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

<!-- 實現(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>

<!-- 實現(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動態(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 實現(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 {

    /*徐庶老師實際開發(fā)中的實現(xiàn)方式*/
    QueryEmpDTO QueryEmp(Integer id);

    /*實用嵌套結(jié)果實現(xiàn)聯(lián)合查詢  多對一 */
    Emp QueryEmp2(Integer id);


    /*實用嵌套查詢實現(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幫我們實現(xiàn)自動的關(guān)聯(lián)查詢嗎?

4.3.1 多對一

EmpMapper.xml:

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

    /*徐庶老師實際開發(fā)中的實現(xiàn)方式*/
    QueryEmpDTO QueryEmp(Integer id);

    /*實用嵌套結(jié)果實現(xiàn)聯(lián)合查詢  多對一 */
    Emp QueryEmp2(Integer id);


    /*實用嵌套查詢實現(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)的時候,有可能在查詢結(jié)果的時候不需要關(guān)聯(lián)對象的屬性值(select count(1)…),那么此時可以通過延遲加載來實現(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è)置了全局加載,但是希望在某一個sql語句查詢的時候不使用延時策略,可以添加fetchType下屬性:

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

4.5 總結(jié)

在這里插入圖片描述

三種關(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)映射非常簡單,只需要參考如下兩種示例配置即可

在這里插入圖片描述

一對多

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

在這里插入圖片描述

多對多

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

五 動態(tài)sql

動態(tài) SQL 是 MyBatis 的強(qiáng)大特性之一。如果你使用過 JDBC 或其它類似的框架,你應(yīng)該能理解根據(jù)不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態(tài) SQL,可以徹底擺脫這種痛苦。
使用動態(tài) SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強(qiáng)大的動態(tài) SQL 語言,MyBatis 顯著地提升了這一特性的易用性。
如果你之前用過 JSTL 或任何基于類 XML 語言的文本處理器,你對動態(tài) SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間了解大量的元素。借助功能強(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ù)值有缺失會怎么呢?這個時候拼接的sql語句就會變得有問題,例如不傳參數(shù)或者丟失最后一個參數(shù),那么語句中就會多一個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整體添加一個前綴
 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

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

 <!--foreach是對集合進(jìn)行遍歷
	 collection="deptnos" 指定要遍歷的集合
	 close="" 表示以什么結(jié)束
	 index="" 給定一個索引值
	 item="" 遍歷的每一個元素的值
	 open="" 表示以什么開始
	 separator="" 表示多個元素的分隔符
 -->
 <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

有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,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

用于動態(tài)更新語句的類似解決方案叫做 set。set 元素可以用于動態(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)建一個變量,并將其綁定到當(dāng)前的上下文。比如:

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

5.6 sql

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

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

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

<cache/>

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

  • 映射語句文件中的所有 select 語句的結(jié)果將會被緩存。
  • 映射語句文件中的所有 insert、update 和 delete 語句會刷新緩存。
  • 緩存會使用最近最少使用算法(LRU, Least Recently Used)算法來清除不需要的緩存。
  • 緩存不會定時進(jìn)行刷新(也就是說,沒有刷新間隔)。
  • 緩存會保存列表或?qū)ο螅o論查詢方法返回哪種)的 1024 個引用。
  • 緩存會被視為讀/寫緩存,這意味著獲取到的對象并不是共享的,可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。

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

  • 一級緩存:線程級別的緩存,是本地緩存,sqlSession級別的緩存
  • 二級緩存:全局范圍的緩存,不止局限于當(dāng)前會話

6.2 一級緩存的使用

一級緩存是sqlsession級別的緩存,默認(rèn)是存在的。在下面的案例中,大家發(fā)現(xiàn)我發(fā)送了兩個相同的請求,但是sql語句僅僅執(zhí)行了一次,那么就意味著第一次查詢的時候已經(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)用程序中只有開啟了多個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ù)不一致,那么也不會緩存結(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、在兩次查詢期間,手動去清空緩存,也會讓緩存失效

@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("手動清空緩存");
    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)實現(xiàn)類PerpetualCache ,使用map進(jìn)行存儲的
  4. 查詢完就會進(jìn)行存儲
  5. 先從二級緩存中獲取,再從一級緩存中獲取 key==> sqlid+sql

一級緩存失效情況:

  1. 不同的sqlSession會使一級緩存失效
  2. 同一個SqlSession,但是查詢語句不一樣
  3. 同一個SqlSession,查詢語句一樣,期間執(zhí)行增刪改操作
  4. 同一個SqlSession,查詢語句一樣,執(zhí)行手動清除緩存

6.3 二級緩存的使用

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

二級緩存的使用

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

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

2、需要在使用二級緩存的映射文件處使用標(biāo)簽標(biāo)注
3、實體類必須要實現(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:最近最少使用的,移除最長時間不被使用的對象
    • FIFO:先進(jìn)先出,按照對象進(jìn)入緩存的順序來移除
    • SOFT:軟引用,移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象
    • WEAK:弱引用,更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象
  • flushInternal:刷新間隔,單位毫秒
    • 默認(rèn)情況是不設(shè)置,也就是沒有刷新間隔,緩存僅僅調(diào)用語句時刷新
  • size:引用數(shù)目,正整數(shù)
    • 代表緩存最多可以存儲多少個對象,太大容易導(dǎo)致內(nèi)存溢出
  • readonly:只讀,true/false
    • true:只讀緩存,會給所有調(diào)用這返回緩存對象的相同實例,因此這些對象不能被修改。
    • false:讀寫緩存,會返回緩存對象的拷貝(序列化實現(xiàn)),這種方式比較安全,默認(rèn)值
//可以看到會去二級緩存中查找數(shù)據(jù),而且二級緩存跟一級緩存中不會同時存在數(shù)據(jù),因為二級緩存中的數(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è)置了全局的二級緩存配置,那么在使用的時候需要注意,在每一個單獨(dú)的select語句中,可以設(shè)置將查詢緩存關(guān)閉,以完成特殊的設(shè)置

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

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

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

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

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

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

4、sqlSession.clearCache()

只是用來清除一級緩存

二級緩存特性

  • 默認(rèn)開啟了,沒有實現(xiàn)
  • 作用域:基于全局范圍,應(yīng)用級別。
  • 緩存默認(rèn)實現(xiàn)類PerpetualCache ,使用map進(jìn)行存儲的但是二級緩存根據(jù)不同的mapper命名空間多包了一層map
 : org.apache.ibatis.session.Configuration#caches    key:mapper命名空間   value:erpetualCache.map
 key==> sqlid+sql
  • 事務(wù)提交的時候(sqlSession關(guān)閉)
  • 先從二級緩存中獲取,再從一級緩存中獲取

實現(xiàn):

  1. 開啟二級緩存<setting name="cacheEnabled" value="true"/>
  2. 在需要使用到二級緩存的映射文件中加入,基于Mapper映射文件來實現(xiàn)緩存的,基于Mapper映射文件的命名空間來存儲的
  3. 在需要使用到二級緩存的javaBean中實現(xiàn)序列化接口implements Serializable 配置成功就會出現(xiàn)緩存命中率 同一個sqlId: 從緩存中拿出的次數(shù)/查詢總次數(shù)

失效:

  1. 同一個命名空間進(jìn)行了增刪改的操作,會導(dǎo)致二級緩存失效 但是如果不想失效:可以將SQL的flushCache 這是為false,但是要慎重設(shè)置,因為會造成數(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二級緩存實現(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時,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)存緩存溢出的時候是否將過期的element緩存到磁盤上

以下屬性是可選的:
l timeToIdleSeconds - 當(dāng)緩存在EhCache中的數(shù)據(jù)前后兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數(shù)據(jù)便會刪除,默認(rèn)值是0,也就是可閑置時間無窮大
l timeToLiveSeconds - 緩存element的有效生命期,默認(rèn)是0.,也就是element存活時間無窮大
diskSpoolBufferSizeMB 這個參數(shù)設(shè)置DiskStore(磁盤緩存)的緩存區(qū)大小.默認(rèn)是30MB.每個Cache都應(yīng)該有自己的一個緩沖區(qū).
l diskPersistent - 在VM重啟的時候是否啟用磁盤保存EhCache中的數(shù)據(jù),默認(rèn)是false。
l diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運(yùn)行間隔,默認(rèn)是120秒。每個120s,相應(yīng)的線程會進(jìn)行一次EhCache中數(shù)據(jù)的清理工作
l memoryStoreEvictionPolicy - 當(dāng)內(nèi)存緩存達(dá)到最大,有新的element加入的時候, 移除緩存中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)行原理和插件的工作方式,最好不要使用插件,因為它會改變系底層的工作邏輯,給系統(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)建時間,修改者,修改時間四個字段,對于這四個字段的賦值,實際上可以在DAO層統(tǒng)一攔截處理,可以用mybatis插件攔截Executor類的update方法,對相關(guān)參數(shù)進(jìn)行統(tǒng)一賦值即可;

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

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

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

在這里插入圖片描述

Executor 會攔截到CachingExcecutor 或者BaseExecutor。因為創(chuàng)建Executor 時是先創(chuàng)建CachingExcecutor,再包裝攔截。從代碼順序上能看到。我們可以通過mybatis的分頁插件來看看整個插件從包裝攔截器鏈到執(zhí)行攔截器鏈的過程。
  在查看插件原理的前提上,我們需要來看看官網(wǎng)對于自定義插件是怎么來做的,官網(wǎng)上有介紹:通過 MyBatis 提供的強(qiáng)大機(jī)制,使用插件是非常簡單的,只需實現(xiàn) Interceptor 接口,并指定想要攔截的方法簽名即可。這里本人踩了一個坑,在Springboot中集成,同時引入了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重新封裝一個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 啟動時掃描 標(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時,會將RowBounds第一個參數(shù)offset當(dāng)成pageNum頁碼使用 -->
			<!-- 和startPage中的pageNum效果一樣 -->
			<property name="offsetAsPageNum" value="true" />
			<!-- 該參數(shù)默認(rèn)為false -->
			<!-- 設(shè)置為true時,使用RowBounds分頁會進(jìn)行count查詢 -->
			<property name="rowBoundsWithCount" value="true" />
			<!-- 設(shè)置為true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結(jié)果 -->
			<!-- (相當(dāng)于沒有執(zhí)行分頁查詢,但是返回結(jié)果仍然是Page類型) -->
			<property name="pageSizeZero" value="true" />
			<!-- 3.3.0版本可用 - 分頁參數(shù)合理化,默認(rèn)false禁用 -->
			<!-- 啟用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最后一頁 -->
			<!-- 禁用合理化時,如果pageNum<1或pageNum>pages會返回空數(shù)據(jù) -->
			<property name="reasonable" value="true" />
			<!-- 3.5.0版本可用 - 為了支持startPage(Object params)方法 -->
			<!-- 增加了一個`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動態(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]);
          }     
}  

代理和攔截是怎么實現(xiàn)的?
  上面提到的可以被代理的四大對象都是什么時候被代理的呢?Executor 是openSession() 的時候創(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);返回代理對象。
  單個插件的情況下,代理能不能被代理?代理順序和調(diào)用順序的關(guān)系? 可以被代理。

在這里插入圖片描述

因為代理類是Plugin,所以最后調(diào)用的是Plugin 的invoke()方法。它先調(diào)用了定義的攔截器的intercept()方法。可以通過invocation.proceed()調(diào)用到被代理對象被攔截的方法。

在這里插入圖片描述

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

在這里插入圖片描述

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

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

對于插件機(jī)制我們上面已經(jīng)介紹過了,在這里我們自然的會想到其所涉及的核心類 :PageInterceptor。攔截的是Executor 的兩個query()方法,要實現(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(),我們來看看這個方法:

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

在這里插入圖片描述

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

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á)到一個分頁的效果。
關(guān)鍵類總結(jié):
  

在這里插入圖片描述

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ū)動
	用java代碼的方式生成可以不指定(只需要吧mybatis-generator和數(shù)據(jù)庫驅(qū)動依賴到項目)

  <classPathEntry location ="F:\java\jar\mysql-connector-java-5.1.22-bin.jar" /> -->
  
  <!-- targetRuntime
  	MyBatis3  可以生成通用查詢,可以指定動態(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動態(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動態(tài)代理
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

        // 使用Example實現(xiàn)動態(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的介紹、基本使用、高級使用的文章就介紹到這了,更多相關(guān)Mybatis的介紹和使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java多線程使用阻塞隊列實現(xiàn)生產(chǎn)者消費(fèi)者模型詳解

    Java多線程使用阻塞隊列實現(xiàn)生產(chǎn)者消費(fèi)者模型詳解

    這篇文章主要介紹了Java多線程使用阻塞隊列實現(xiàn)生產(chǎn)者消費(fèi)者模型詳解,主要講解阻塞隊列的特性、實際開發(fā)中常用的到的生產(chǎn)者消費(fèi)者模型,以及生產(chǎn)者消費(fèi)者模型解耦合、削峰填谷的好處,需要的朋友可以參考下
    2023-07-07
  • Spring集成Web環(huán)境與SpringMVC組件的擴(kuò)展使用詳解

    Spring集成Web環(huán)境與SpringMVC組件的擴(kuò)展使用詳解

    這篇文章主要介紹了Spring集成Web環(huán)境與SpringMVC組件,它是一個MVC架構(gòu),用來簡化基于MVC架構(gòu)的Web應(yīng)用開發(fā)。SpringMVC最重要的就是五大組件
    2022-08-08
  • Java-性能分析和監(jiān)控工具深入詳解

    Java-性能分析和監(jiān)控工具深入詳解

    這篇文章主要介紹了Java-性能分析和監(jiān)控工具深入詳解,文章內(nèi)容詳細(xì),簡單易懂,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2023-01-01
  • 解析Nacos的API居然存在這么嚴(yán)重的漏洞

    解析Nacos的API居然存在這么嚴(yán)重的漏洞

    這篇文章主要介紹了Nacos的API居然存在這么嚴(yán)重的漏洞,Nacos為我們提供了大量API,但是這些API默認(rèn)是沒有開啟認(rèn)證的,直接可以訪問,針對于這一點(diǎn)我們也都可以去驗證一下,本文給大家詳細(xì)講解,感興趣的朋友跟隨小編一起看看吧
    2022-09-09
  • SpringCloud降級規(guī)則使用介紹

    SpringCloud降級規(guī)則使用介紹

    這篇文章主要介紹了SpringCloud降級規(guī)則,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • MyBatis-Plus?updateById更新不了空字符串或null的解決方法

    MyBatis-Plus?updateById更新不了空字符串或null的解決方法

    本文主要介紹了MyBatis-Plus?updateById更新不了空字符串或null的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • SpringBoot 單元測試實戰(zhàn)(Mockito,MockBean)

    SpringBoot 單元測試實戰(zhàn)(Mockito,MockBean)

    這篇文章主要介紹了SpringBoot 單元測試實戰(zhàn)(Mockito,MockBean),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 詳解Java中native方法的使用

    詳解Java中native方法的使用

    native是與C++聯(lián)合開發(fā)的時候用的!使用native關(guān)鍵字說明這個方法是原生函數(shù),也就是這個方法是用C/C++語言實現(xiàn)的,并且被編譯成了DLL,由java去調(diào)用。本文給大家介紹java 中native方法使用,感興趣的朋友一起看看吧
    2020-09-09
  • @RequestParam 參數(shù)偶爾丟失的解決

    @RequestParam 參數(shù)偶爾丟失的解決

    這篇文章主要介紹了@RequestParam 參數(shù)偶爾丟失的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • java對象強(qiáng)轉(zhuǎn)成object的方法實現(xiàn)

    java對象強(qiáng)轉(zhuǎn)成object的方法實現(xiàn)

    在 Java 編程中,有時候我們需要將一個具體的對象強(qiáng)制轉(zhuǎn)換成 Object 類型,本文主要介紹了java對象強(qiáng)轉(zhuǎn)成object的方法實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2024-03-03

最新評論