欧美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,可以為多種關系數(shù)據(jù)庫提供統(tǒng)一訪問,它由一組用Java語言編寫的類和接口組成.JDBC提供了一種基準,據(jù)此可以構建更高級的工具和接口,使數(shù)據(jù)庫開發(fā)人員能夠編寫數(shù)據(jù)庫應用程序

  • 優(yōu)點:運行期:快捷、高效
  • 缺點:編輯期:代碼量大、繁瑣異常處理、不支持數(shù)據(jù)庫跨平臺

在這里插入圖片描述

jdbc核心api

  1. DriverManager 連接數(shù)據(jù)庫
  2. Connection 連接數(shù)據(jù)庫的抽象
  3. Statment 執(zhí)行SQL
  4. ResultSet 數(shù)據(jù)結果集

1.2 DBUtils

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

  1. QueryRunner中提供對sql語句操作的API
  2. ResultSetHandler接口,用于定義select操作后,怎樣封裝結果集
  3. DBUtils類,它就是一個工具類,定義了關閉資源與事務處理的方法

1.3 Hibernate

ORM 對象關系映射

  1. object java對象
  2. relational 關系型數(shù)據(jù)
  3. mapping 映射
  • Hibernate 是由 Gavin King 于 2001 年創(chuàng)建的開放源代碼的對象關系框架。它強大且高效的構建具有關系對象持久性和查詢服務的 Java 應用程序。
  • Hibernate 將 Java 類映射到數(shù)據(jù)庫表中,從 Java 數(shù)據(jù)類型中映射到 SQL 數(shù)據(jù)類型中,并把開發(fā)人員從 95% 的公共數(shù)據(jù)持續(xù)性編程工作中解放出來。
  • Hibernate 是傳統(tǒng) Java 對象和數(shù)據(jù)庫服務器之間的橋梁,用來處理基于 O/R 映射機制和模式的那些對象。

在這里插入圖片描述

Hibernate 優(yōu)勢

  • Hibernate 使用 XML 文件來處理映射 Java 類別到數(shù)據(jù)庫表格中,并且不用編寫任何代碼。
  • 為在數(shù)據(jù)庫中直接儲存和檢索 Java 對象提供簡單的 APIs。
  • 如果在數(shù)據(jù)庫中或任何其它表格中出現(xiàn)變化,那么僅需要改變 XML 文件屬性。
  • 抽象不熟悉的 SQL 類型,并為我們提供工作中所熟悉的 Java 對象。
  • Hibernate 不需要應用程序服務器來操作。
  • 操控你數(shù)據(jù)庫中對象復雜的關聯(lián)。
  • 最小化與訪問數(shù)據(jù)庫的智能提取策略。
  • 提供簡單的數(shù)據(jù)詢問。

Hibernate劣勢

  • hibernate的完全封裝導致無法使用數(shù)據(jù)的一些功能。
  • Hibernate的緩存問題。
  • Hibernate對于代碼的耦合度太高。
  • Hibernate尋找bug困難。
  • Hibernate批量數(shù)據(jù)操作需要大量的內(nèi)存空間而且執(zhí)行過程中需要的對象太多

1.4 JDBCTemplate

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

  • 優(yōu)點:運行期:高效、內(nèi)嵌Spring框架中、支持基于AOP的聲明式事務
  • 缺點:必須于Spring框架結合在一起使用、不支持數(shù)據(jù)庫跨平臺、默認沒有緩存

1.5 Mybatis

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

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

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

二 MyBatis的配置文件詳解

2.1 MyBatis日志配置

導入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 追加器   日志以哪種方式進行輸出
            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-->
<!--控制跟細粒度的日志級別  根據(jù)包\根據(jù)類-->
    <logger name="cn.tulingxueyuan.mapper" level="debug"></logger>
    org.apache.ibatis.transaction
    <!--控制所有的日志級別-->
    <root level="error">
        <!-- 將當前日志級別輸出到哪個追加器上面 -->
        <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的全局配置文件,用來進行相關的全局配置,在任何操作下都生效的配置。下面我們要針對其中的屬性做詳細的解釋,方便大家在后續(xù)使用的時候更加熟練。

官方說明:
MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設置和屬性信息。 配置文檔的頂層結構如下:

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)絡路徑引入
    -->
    <properties resource="db.properties"></properties>
    <!--用來控制mybatis運行時的行為,是mybatis中的重要配置-->
    <settings>
        <!--設置列名映射的時候是否是駝峰標識-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--typeAliases表示為我們引用的實體類起別名,默認情況下我們需要寫類的完全限定名
    如果在此處做了配置,那么可以直接寫類的名稱,在type中配置上類的完全限定名,在使用的時候可以忽略大小寫
    還可以通過alias屬性來表示類的別名
    -->
    <typeAliases>
<!--        <typeAlias type="cn.tulingxueyuan.bean.Emp" alias="Emp"></typeAlias>-->
        <!--如果需要引用多個類,那么給每一個類起別名肯定會很麻煩,因此可以指定對應的包名,那么默認用的是類名-->
        <package name="cn.tulingxueyuan.bean"/>
    </typeAliases>
    <!--
    在實際的開發(fā)過程中,我們可能分為開發(fā)環(huán)境,生產(chǎn)環(huán)境,測試環(huán)境等等,每個環(huán)境的配置可以是不一樣的
    environment就用來表示不同環(huán)境的細節(jié)配置,每一個環(huán)境中都需要一個事務管理器以及數(shù)據(jù)源的配置
    我們在后續(xù)的項目開發(fā)中幾乎都是使用spring中配置的數(shù)據(jù)源和事務管理器來配置,此處不需要研究
    -->
    <!--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語句的寫法是不一樣的,為了增強移植性,可以提供不同數(shù)據(jù)庫的操作實現(xiàn)
    在編寫不同的sql語句的時候,可以指定databaseId屬性來標識當前sql語句可以運行在哪個數(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進行映射-->
    <mappers>
        <!--
        指定具體的不同的配置文件
        class:直接引入接口的全類名,可以將xml文件放在dao的同級目錄下,并且設置相同的文件名稱,同時可以使用注解的方式來進行相關的配置
        url:可以從磁盤或者網(wǎng)絡路徑查找sql映射文件
        resource:在類路徑下尋找sql映射文件
        -->
<!--        <mapper resource="EmpDao.xml"/>
        <mapper resource="UserDao.xml"/>
        <mapper class="cn.tulingxueyuan.dao.EmpDaoAnnotation"></mapper>-->
        <!--
        當包含多個配置文件或者配置類的時候,可以使用批量注冊的功能,也就是引入對應的包,而不是具體的配置文件或者類
        但是需要注意的是,
        1、如果使用的配置文件的形式,必須要將配置文件跟dao類放在一起,這樣才能找到對應的配置文件.
            如果是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 的真正強大在于它的語句映射,這是它的魔力所在。由于它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發(fā)現(xiàn)省掉了將近 95% 的代碼。MyBatis 致力于減少使用成本,讓用戶能更專注于 SQL 代碼。
SQL 映射文件只有很少的幾個頂級元素(按照應被定義的順序列出):

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

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

insert、update、delete元素

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

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

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

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

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

<!--獲取參數(shù)的方式:
    1.#{} ==> jdbc String sql=" SELECT id,user_name FROM EMP WHERE id=?"
        1.會經(jīng)過JDBC當中PreparedStatement的預編譯,會根據(jù)不同的數(shù)據(jù)類型來編譯成對應數(shù)據(jù)庫所對應的數(shù)據(jù)。
        2.能夠有效的防止SQL注入。 推薦使用??!
        特殊用法:
        自帶很多內(nèi)置參數(shù)的屬性:通常不會使用。了解
        javaType、jdbcType、mode、numericScale、resultMap、typeHandler.
        比如 需要改變默認的NULL===>OTHER:#{id,javaType=NULL}
             想保留小數(shù)點后兩位:#{id,numericScale=2}

    2.${} ==> jdbc String sql=" SELECT id,user_name FROM EMP WHERE id="+id
        1.不會進行預編譯,會直接將輸入進來的數(shù)據(jù)拼接在SQL中。
        2.存在SQL注入的風險。不推薦使用。
        特殊用法:
            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 會進行封裝
       會將傳進來的參數(shù)封裝成map:
       1個值就會對應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ù)的別名:@Param(""):SelectEmp(@Param("id") Integer id,@Param("username") String username);
            當使用了@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ù)傳遞是一樣。
       一般情況下:
           請求進來的參數(shù) 和pojo對應,就用pojo
           請求進來的參數(shù) 沒有和pojo對應,就用map
           請求進來的參數(shù) 沒有和pojo對應上,但是使用頻率很高,就用TO、DTO(就是單獨為這些參數(shù)創(chuà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 處理集合返回結果

EmpDao.xml

<!--當返回值的結果是集合的時候,返回值的類型依然寫的是集合中具體的類型-->
    <select id="selectAllEmp" resultType="cn.tulingxueyuan.bean.Emp">
        select  * from emp
    </select>
<!--在查詢的時候可以設置返回值的類型為map,當mybatis查詢完成之后會把列的名稱作為key
    列的值作為value,轉換到map中
    -->
    <select id="selectEmpByEmpReturnMap" resultType="map">
        select * from emp where empno = #{empno}
    </select>

    <!--注意,當返回的結果是一個集合對象的時候,返回值的類型一定要寫集合具體value的類型,
    同時在dao的方法上要添加@MapKey的注解,來設置key是什么結果
    @MapKey("empno")
    Map<Integer,Emp> getAllEmpReturnMap();-->
    <select id="getAllEmpReturnMap" resultType="cn.tulingxueyuan.bean.Emp">
        select * from emp
    </select>

3.4 自定義結果集—resultMap

<!--1.聲明resultMap自定義結果集   resultType 和 resultMap 只能使用一個。
    id 唯一標識, 需要和<select 上的resultMap 進行對應
    type 需要映射的pojo對象, 可以設置別名
    autoMapping 自動映射,(默認=true) 只要字段名和屬性名遵循映射規(guī)則就可以自動映射,但是不建議,哪怕屬性名和字段名一一對應上了也要顯示的配置映射
    extends  如果多個resultMap有重復映射,可以聲明父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 關聯(lián) 自定義結果集的id-->
<select id="SelectEmp"  resultType="Emp"  resultMap="emp_map"  >
    SELECT id,user_name,create_date FROM EMP where id=#{id}
</select>

四 MyBatis基于XML的詳細使用——高級結果映射

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)結查詢的方式:  可以映射: 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)結查詢的方式:  可以映射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 嵌套結果

4.2.1 多對一

EmpMapper.xml

<!--嵌套結果   多 對 一  -->
<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 一對多

<!-- 嵌套結果: 一對多  查詢部門及所有員工 -->
<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);

    /*實用嵌套結果實現(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語句的關聯(lián)查詢的,那么,我們能讓mybatis幫我們實現(xiàn)自動的關聯(lián)查詢嗎?

4.3.1 多對一

EmpMapper.xml:

<!--嵌套查詢(分步查詢)   多 對 一
  聯(lián)合查詢和分步查詢區(qū)別:   性能區(qū)別不大
                            分部查詢支持 懶加載(延遲加載)
   需要設置懶加載,一定要使用嵌套查詢的。
   要啟動懶加載可以在全局配置文件中設置 lazyLoadingEnabled=true
   還可以單獨為某個分步查詢設置立即加載 <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 需要將當前查詢的字段傳遞到異步查詢的參數(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);

    /*實用嵌套結果實現(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 延遲查詢

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

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

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

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

4.5 總結

在這里插入圖片描述

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

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

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

一對一

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

在這里插入圖片描述

一對多

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

在這里插入圖片描述

多對多

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

五 動態(tài)sql

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

  • 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的關鍵字,因此在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 的另一個常見使用場景是對集合進行遍歷(尤其是在構建 IN 條件語句的時候)。

 <!--foreach是對集合進行遍歷
	 collection="deptnos" 指定要遍歷的集合
	 close="" 表示以什么結束
	 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 元素,它有點像 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 表達式以外創(chuà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)置了一個強大的事務性查詢緩存機制,它可以非常方便地配置和定制。 為了使它更加強大而且易于配置,我們對 MyBatis 3 中的緩存實現(xiàn)進行了許多改進。
默認情況下,只啟用了本地的會話緩存,它僅僅對一個會話中的數(shù)據(jù)進行緩存。 要啟用全局的二級緩存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

當添加上該標簽之后,會有如下效果:

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

在進行配置的時候還會分為一級緩存和二級緩存:

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

6.2 一級緩存的使用

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

@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級別的緩存,如果在應用程序中只有開啟了多個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ù)不一致,那么也不會緩存結果
3、如果在發(fā)送過程中發(fā)生了數(shù)據(jù)的修改,那么結果就不會緩存

@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. 默認就開啟了,也可以關閉一級緩存 localCacheScope=STATEMENT
  2. 作用域:是基于sqlSession(默認),一次數(shù)據(jù)庫操作會話。
  3. 緩存默認實現(xiàn)類PerpetualCache ,使用map進行存儲的
  4. 查詢完就會進行存儲
  5. 先從二級緩存中獲取,再從一級緩存中獲取 key==> sqlid+sql

一級緩存失效情況:

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

6.3 二級緩存的使用

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

二級緩存的使用

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

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

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

二級緩存的作用范圍

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

1、在setting中設置,是配置二級緩存開啟,一級緩存默認一直開啟

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

2、select標簽的useCache屬性:

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

3、sql標簽的flushCache屬性

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

4、sqlSession.clearCache()

只是用來清除一級緩存

二級緩存特性

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

實現(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. 同一個命名空間進行了增刪改的操作,會導致二級緩存失效 但是如果不想失效:可以將SQL的flushCache 這是為false,但是要慎重設置,因為會造成數(shù)據(jù)臟讀問題,除非你能保證查詢的數(shù)據(jù)永遠不會執(zhí)行增刪改
  2. 讓查詢不緩存數(shù)據(jù)到二級緩存中useCache=“false”
  3. 如果希望其他mapper映射文件的命名空間執(zhí)行了增刪改清空另外的命名空間就可以設置:
<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=

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

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

整合ehcache

導入對應的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>

導入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:當借助CacheManager.add("demoCache")創(chuàng)建Cache時,EhCache便會采用<defalutCache/>指定的的管理策略

以下屬性是必須的:
l maxElementsInMemory - 在內(nèi)存中緩存的element的最大數(shù)目
l maxElementsOnDisk - 在磁盤上緩存的element的最大數(shù)目,若是0表示無窮大
l eternal - 設定緩存的elements是否永遠不過期。如果為true,則緩存的數(shù)據(jù)始終有效,如果為false那么還要根據(jù)timeToIdleSeconds,timeToLiveSeconds判斷
l overflowToDisk - 設定當內(nèi)存緩存溢出的時候是否將過期的element緩存到磁盤上

以下屬性是可選的:
l timeToIdleSeconds - 當緩存在EhCache中的數(shù)據(jù)前后兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數(shù)據(jù)便會刪除,默認值是0,也就是可閑置時間無窮大
l timeToLiveSeconds - 緩存element的有效生命期,默認是0.,也就是element存活時間無窮大
diskSpoolBufferSizeMB 這個參數(shù)設置DiskStore(磁盤緩存)的緩存區(qū)大小.默認是30MB.每個Cache都應該有自己的一個緩沖區(qū).
l diskPersistent - 在VM重啟的時候是否啟用磁盤保存EhCache中的數(shù)據(jù),默認是false。
l diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運行間隔,默認是120秒。每個120s,相應的線程會進行一次EhCache中數(shù)據(jù)的清理工作
l memoryStoreEvictionPolicy - 當內(nèi)存緩存達到最大,有新的element加入的時候, 移除緩存中element的策略。默認是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)
-->

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

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

七 MyBatis分頁插件&逆向工程

7.1 分頁插件

MyBatis 通過提供插件機制,讓我們可以根據(jù)自己的需要去增強MyBatis 的功能。需要注意的是,如果沒有完全理解MyBatis 的運行原理和插件的工作方式,最好不要使用插件,因為它會改變系底層的工作邏輯,給系統(tǒng)帶來很大的影響。
  MyBatis 的插件可以在不修改原來的代碼的情況下,通過攔截的方式,改變四大核心對象的行為,比如處理參數(shù),處理SQL,處理結果。

Mybatis插件典型適用場景

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

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

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

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

實現(xiàn)思考:
第一個問題:
  不修改對象的代碼,怎么對對象的行為進行修改,比如說在原來的方法前面做一點事情,在原來的方法后面做一點事情?
  答案:大家很容易能想到用代理模式,這個也確實是MyBatis 插件的原理。
  
第二個問題:
  我們可以定義很多的插件,那么這種所有的插件會形成一個鏈路,比如我們提交一個休假申請,先是項目經(jīng)理審批,然后是部門經(jīng)理審批,再是HR 審批,再到總經(jīng)理審批,怎么實現(xiàn)層層的攔截?
  答案:插件是層層攔截的,我們又需要用到另一種設計模式——責任鏈模式。
  
  在之前的源碼中我們也發(fā)現(xiàn)了,mybatis內(nèi)部對于插件的處理確實使用的代理模式,既然是代理模式,我們應該了解MyBatis 允許哪些對象的哪些方法允許被攔截,并不是每一個運行的節(jié)點都是可以被修改的。只有清楚了這些對象的方法的作用,當我們自己編寫插件的時候才知道從哪里去攔截。在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 提供的強大機制,使用插件是非常簡單的,只需實現(xiàn) Interceptor 接口,并指定想要攔截的方法簽名即可。這里本人踩了一個坑,在Springboot中集成,同時引入了pagehelper-spring-boot-starter 導致RowBounds參數(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ù) :當前頁offset,一頁數(shù)limit
        RowBounds rowBounds= (RowBounds) args[2];

        // 拿到原來的sql語句
        String sql = boundSql.getSql();
        String limitSql=sql+ " limit "+rowBounds.getOffset()+","+ rowBounds.getLimit();

        //將分頁sql重新封裝一個BoundSql 進行后續(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ù)的順序有嚴格要求,如果按照順序找不到對應方法會拋出異常:

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 啟動時掃描 標簽, 注冊到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ù)默認為false -->
			<!-- 設置為true時,會將RowBounds第一個參數(shù)offset當成pageNum頁碼使用 -->
			<!-- 和startPage中的pageNum效果一樣 -->
			<property name="offsetAsPageNum" value="true" />
			<!-- 該參數(shù)默認為false -->
			<!-- 設置為true時,使用RowBounds分頁會進行count查詢 -->
			<property name="rowBoundsWithCount" value="true" />
			<!-- 設置為true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結果 -->
			<!-- (相當于沒有執(zhí)行分頁查詢,但是返回結果仍然是Page類型) -->
			<property name="pageSizeZero" value="true" />
			<!-- 3.3.0版本可用 - 分頁參數(shù)合理化,默認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,不配置映射的用默認值 -->
			<!-- 不理解該含義的前提下,不要隨便復制該配置 -->
			<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("當前頁碼:"+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 和處理結果集的ResultSetHandler 的創(chuàng)建,創(chuàng)建之后即調(diào)用InterceptorChain.pluginAll(),返回層層代理后的對象。代理是由Plugin 類創(chuàng)建。在我們重寫的 plugin() 方法里面可以直接調(diào)用returnPlugin.wrap(target, this);返回代理對象。
  單個插件的情況下,代理能不能被代理?代理順序和調(diào)用順序的關系? 可以被代理。

在這里插入圖片描述

因為代理類是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īng)介紹過了,在這里我們自然的會想到其所涉及的核心類 :PageInterceptor。攔截的是Executor 的兩個query()方法,要實現(xiàn)分頁插件的功能,肯定是要對我們寫的sql進行改寫,那么一定是在 intercept 方法中進行操作的,我們會發(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();
}
//線程獨享
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對象,從其中獲取我們所設置的 PageSize 與 PageNum,那么他是怎么設置值的呢?請看:

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ù)信息
        setLocalPage(page);
        return page;
}

protected static void setLocalPage(Page page) {
        //設置值
        LOCAL_PAGE.set(page);
}

在我們調(diào)用 PageHelper.startPage(1, 3); 的時候,系統(tǒng)會調(diào)用 LOCAL_PAGE.set(page) 進行設置,從而在分頁插件中可以獲取到這個本地變量對象中的參數(shù)進行 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 從而達到一個分頁的效果。
關鍵類總結:
  

在這里插入圖片描述

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

  <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ī)則 -->
	<commentGenerator>
	<!-- 設置不生成注釋 -->
		<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 當數(shù)據(jù)庫類型為decimal 或number 生成對應的java的BigDecimal
	    	false 會根據(jù)可數(shù)據(jù)的數(shù)值長度生成不同的對應java類型
	    useJSR310Types
	        false 所有數(shù)據(jù)庫的日期類型都會生成java的 Date類型	
	        true  會將數(shù)據(jù)庫的日期類型生成對應的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);
    }
}

到此這篇關于Mybatis的介紹、基本使用、高級使用的文章就介紹到這了,更多相關Mybatis的介紹和使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

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

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

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

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

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

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

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

    解析Nacos的API居然存在這么嚴重的漏洞

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

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

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

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

    本文主要介紹了MyBatis-Plus?updateById更新不了空字符串或null的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    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關鍵字說明這個方法是原生函數(shù),也就是這個方法是用C/C++語言實現(xiàn)的,并且被編譯成了DLL,由java去調(diào)用。本文給大家介紹java 中native方法使用,感興趣的朋友一起看看吧
    2020-09-09
  • @RequestParam 參數(shù)偶爾丟失的解決

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

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

    java對象強轉成object的方法實現(xiàn)

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

最新評論