Java面試---MyBatis

一、MyBatis 與 JDBC 的區別

【1】JDBC 是 Java 提供操作數據庫的 API;MyBatis 是一個持久層 ORM 框架,底層是對 JDBC 的封裝。
【2】使用 JDBC 需要連接數據庫,註冊驅動和數據庫信息工作量大,每次都要去創建、關閉、獲取JDBC 編程可能的異常進行捕獲處理,並正確關閉資源對象關閉映射(ORM)。操作 Connection,打開 Statement 對象。通過 Statement 執行 SQL 返回結果到 ResultSet 對象,然後通過代碼轉化爲具體的 POJO 對象。關閉數據庫的相關資源等。MyBatis 使用已有的連接池管理,避免浪費資源,提高程序可靠性。

//JDBC 操作數據庫時,部分代碼
conn = (Connection) DriverManager.getConnection(DB_URL, USER, PASS);
// 執行查詢
stmt = (Statement) conn.createStatement();
String sql = "SELECT * FROM xtb";
//獲取到的ResultSet需要一個個的get屬性,並賦給返回值對象
ResultSet rs = stmt.executeQuery(sql);
//......
// 完成後關閉
rs.close();
stmt.close();
conn.close();

【3】MyBatis 提供了 Dao 層自動生成工具(mybatis-generator),提高了編碼效率和準確性。
【4】MyBatis 提供了一級和二級緩存,提高了程序的性能。
【5】MyBatis 支持動態 SQL 語句編寫,提高了 SQL 維護和防止 SQL 注入。
【6】MyBatis 提供映射標籤,對數據庫操作結果進行自動映射到POJO對象或 Map中,支持對象與數據庫的 ORM 映射關係。
【7】MyBatis 將 SQL 語句寫入 xml 中,便於統一管理和優化,解除了 SQL與程序代碼的耦合
【8】JDBC 向 SQL語句傳參數麻煩,因爲 SQL語句的 where條件不一定,可能多也可能少,佔位符需要和參數一一對應。Mybatis 會自動將 Java 對象映射至 SQL語句(比如查詢的時候,用戶輸入了什麼參數就是用什麼作爲條件,沒輸入的參數就應當過濾掉等)。

二、Mybatis 與 Hibernate 的區別

【1】Hibernate 是一個標準的 ORM 框架不需要寫 SQL語句,維護關係比較複雜,SQL 語句自動生成,對 SQL語句優化,修改比較困難。
Hibernate 的優點:面向對象開發,不需要自己寫 SQL語句。如果進行數據庫遷移不需要修改 SQL語句,只需要修改一下方言
Hibernate 的缺點:Hibernate 維護數據表關係比較複雜。完全是由 Hibernate來管理數據表的關係,對於我們來說完全是透明的,不易維護。Hibernate 自動生成 SQL語句,比較複雜,比較難挑錯。Hibernate 由於是面向對象開發,不能開發比較複雜的業務。
應用場景:適合需求變化較少的項目,比如 ERP,CRM 等等;

【2】Mybatis 框架對 jdbc框架進行封裝,屏蔽了 jdbc的缺點,開發簡單。Mybatis 只需要程序員關注 SQL本身,不需要過多的關注業務。對 SQL的優化,修改比較容易。
適應場景:適合需求變化多端的項目,比如:互聯網項目;

三、MyBatis 中 #{} 與 ${} 的區別

它們都在 SQL 中動態的傳入值,能用 #{} 就不要用 ${}。

【1】#{} 解析之後會將 String類型的數據自動加上引號,其他數據類型不會;常用與where 條件,例如#{name}解析之後就可能爲#{'zzx'} 。而 ${} 解析之後是什麼就是什麼,他不會當做字符串處理,一般用於傳入數據庫對象,常用與傳入表名和 order by 條件,例如:${column} 解析之後就是 order by id 。
【2】#{} 解析爲一個 JDBC 預編譯語句(prepared statement)的參數標記符,一個 #{} 被解析爲一個參數佔位符《?》;而 ${} 僅僅爲一個純碎的 String 替換,在動態 SQL解析階段將會進行變量替換。
【3】基於【2】,#{} 很大程度上可以防止 SQL注入(SQL注入是發生在編譯的過程中,因爲惡意注入了某些特殊字符,最後被編譯成了惡意的執行操作);而 ${} 主要用於 SQL拼接的時候,有很大的 SQL注入隱患。

四、MyBatis 的一級、二級緩存

【1】一級緩存:MyBatis 的一級緩存是 SqlSession級別的,當 Session flush 後 close 之後,該 Session 中的所有 Cache 就將清空,默認一級緩存是打開的。與有沒有配置無關,只要 SqlSession 存在,MyBastis 一級緩存就存在。
【一級緩存失效原因】:① 是否在同一個 SqlSession 連接中;② 如果進行了增刪改操作程序會 clear 緩存。手動清空緩存數據。調用 sqlsession.clearCache();④ 執行語句的參數不同,緩存中也不存在數據。因爲 map 的 key 是根據 mapperStatment 對象的 id、以及 sql、以及傳入的參數生成 cacheKey 對象的。

【2】二級緩存:與一級緩存的不同之處在於其存儲作用域爲 Mapper(Namespace) ,多個 SqlSession去操作同一個 Mapper的sql 語句,多個 SqlSession可以共用二級緩存。MyBatis 二級緩存讀取優先級高於 MyBatis一級緩存。關閉 sqlsession後,會把該 sqlsession一級緩存中的數據添加到 namespace 的二級緩存中。MyBatis 二級緩存的生命週期即整個應用的生命週期,應用不結束,定義的二級緩存都會存在在內存中。從這個角度考慮,爲了避免 MyBatis二級緩存中數據量過大導致內存溢出,MyBatis在配置文件中給我們增加了很多配置例如 size(緩存大小)、flushInterval(緩存清理時間間隔)、eviction(數據淘汰算法)來保證緩存中存儲的數據不至於太過龐大。
【二級緩存使用】:① 開啓全局二級緩存配置 setting 配置文件中添加:

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

 ② 去 mapper.xml 中配置使用二級緩存:<cache></cache>

<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>

  ● eviction(回收策略):LRU 最近最少使用、FIFO 先進先出、SOFT 軟引用,移除基於垃圾回收器狀態和軟引用規則的對象。 WEAK 弱引用,移除基於垃圾回收器狀態和弱引用規則的對象。
  ● flushInterval(刷新間隔):可以被設置爲任意的正整數(60*60*1000這種形式是不允許的),而且它們代表一個合理的毫秒形式的時間段。默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。
  ● size(引用數目) :設置爲任意正整數,要記住你緩存的對象數目和你運行環境的可用內存資源數目。默認值是1024。
  ● readOnly(只讀)屬性可以被設置爲 true或 false。只讀的緩存會給所有調用者返回緩存對象的相同實例,因此這些對象不能被修改,這提供了很重要的性能優勢。可讀寫的緩存會返回緩存對象的拷貝(通過發序列化)。這會慢一些,但是安全,因此默認是 false。
③ 我們的 POJO 需要實現序列化接口;

【二級緩存弊端】:二級緩存是建立在同一個 namespace 下的,如果對錶的操作查詢可能有多個 namespace,那麼就可能會出現髒讀的數據。舉個栗子:例如存在兩個表“student” 和 “teacher”,在 student 表中關聯查詢 teacher 表,就會將結果存放在 student 表的 namespace 中。問題來了,如果此時修改了 teacher 表,只會對 teacher 表中的 namespace 緩存進行清空,並不會影響 student 表中的緩存。因此 student 表中緩存的 teacher 信息就有可能是髒數據(好好琢磨一下)。
【二級緩存使用注意事項】:  ① 對該表的查詢與增刪改操作都放在同一個 namespace 中,其它的 namespace 如果有操作,就會出現髒讀的數據。② 對關聯表的查詢,關聯的所有表的操作都必須在同一個 namespace 下。

五、Mybatis 動態 Sql 都有哪些

其實動態 sql 語句的編寫往往就是一個拼接的問題,爲了保證拼接準確,我們最好首先要寫原生的 sql 語句出來,然後在通過 mybatis 動態sql 對照着改,防止出錯。 

【1】if 與 where 標籤:parmaeterType 中基本數據類型可直接寫類型(Integer、String、Map 等),如果標籤返回的內容是以AND 或OR 開頭的,則它會剔除掉。

<select id="selectUserByUsernameAndSex" resultMap="user" parameterType="com.pojo.User">
    select * from user
    <where>
        <if test="username != null">
           username=#{username}
        </if>
        <!--如果標籤返回的內容是以AND 或OR 開頭的,則它會剔除掉。-->
        <if test="username != null">
           and sex=#{sex}
        </if>
    </where>
</select>

【2】 if 與 set 標籤:如果標籤返回的內容是逗號結尾的,則它會剔除掉逗號。

<update id="updateUserById" parameterType="com.pojo.User">
    update user u
        <set>
            <if test="username != null and username != ''">
                u.username = #{username},
            </if>
            <if test="sex != null and sex != ''">
                u.sex = #{sex}
            </if>
        </set>
     where id=#{id}
</update>

【3】choose、when 和 otherwise 標籤:選擇其中的一個查詢條件,一個滿足即可,類似於 Java 的 switch 語句。

<select id="selectUserByChoose" resultType="com.pojo.User" parameterType="com.pojo.User">
      select * from user where 1=1
          <choose>
              <when test="id !='' and id != null">
                  and id=#{id}
              </when>
              <when test="username !='' and username != null">
                  and username=#{username}
              </when>
              <otherwise>
                  and sex=#{sex}
              </otherwise>
          </choose>
</select>

【4】trim 標籤:標記是一個格式化的標記,可以完成 set 或者是 where 標記的功能,prefix:前綴 、suffix:後綴。prefixoverride:去掉第一個配置的值,suffixoverride:去掉最後一個配置的值。

<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.pojo.User">
        select * from user
        <trim prefix="where" prefixOverrides="and | or">
            <if test="username != null">
               and username=#{username}
            </if>
            <if test="sex != null">
               and sex=#{sex}
            </if>
        </trim>
</select>

【5】sql 與 include 標籤:有時候可能某個 sql 語句我們用的特別多,爲了增加代碼的重用性,簡化代碼,我們需要將這些代碼抽取出來,然後使用時直接調用。

<!-- 定義 sql 片段 -->
<sql id="columns">
    name,age
</sql>
<!-- 引入sql代碼塊-->
<select id="selectUser" resultMap="com.pojo.User" >
      select <include refid="columns"/> from User 
</select>

【6】foreach 標籤:當傳入參數爲數組或者集合時需要通過<foreach></foreach>標籤進行遍歷。

<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.ys.po.User">
        select * from user
        <where>
            <!--
                collection:指定輸入對象中的集合屬性
                item:每次遍歷生成的對象
                open:開始遍歷時的拼接字符串
                close:結束時拼接的字符串
                separator:遍歷對象之間需要拼接的字符串
                select * from user where 1=1 and id in (1,2,3)
              -->
            <foreach collection="ids" item="id" open="and id in (" close=") " separator=",">
                #{id}
            </foreach>
        </where>
</select>

六、Mybatis 有幾種執行器 SOL 執行過程

SqlSession 是 Mybatis 最重要的構建之一,可以簡單的認爲 Mybatis一系列的配置目的是生成類似 JDBC 生成的Connection 對象的 SqlSession 對象,這樣才能與數據庫開啓“溝通”,通過 SqlSession 可以實現增刪改查。

Executor 是 MyBatis 是核心接口之一,其中定義了數據庫操作的基本方法。在實際應用中經常涉及的 SqlSession 接口的功能,都是基於 Executor 接口實現的。UML 類圖關係如下:接口實現中涉及兩種設計模式,分別是模板模式和裝飾器模式。CachingExecutor 扮演了裝飾器的角色,爲 Executor 添加了二級緩存的功能。這裏主要說 SimpleExecutor、ReuseExecutor、BatchExecutor 三種執行器。

BaseExecutor 是一個抽象類,實現了 Executor 的大部分方法,其中使用模板模式。BaseExecutor 中主要提供了緩存管理(一級緩存)和事務管理的基本方法,繼承BaseExecutor 的子類只要實現四個基本方法來完成數據庫的相關操作。

【1】SimpleExecutor:每執行一次 update 或 select,就開啓一個 Statement 對象,用完立刻關閉 Statement 對象。
【2】ReuseExecutor:執行 update 或 select,以 sql 作爲 key 查找 Statement對象,存在就使用,不存在就創建,用完後,不關閉 Statement 對象,而是放置於 Map內,供下一次使用。簡言之,就是重複使用 Statement對象。key 是SQL 語句,value 則是對應的 Statement。
【3】BatchExecutor :執行 update(沒有select,JDBC批處理不支持select),將所有 sql 都添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個 Statement對象,每個 Statement對象都是 addBatch()完畢後,等待逐一執行executeBatch()批處理。與JDBC批處理相同。

SOL 執行過程:SQL 語句的執行涉及多個組件,其中比較重要的是 Executor 、StatementHandler 、ParameterHandler 和ResultSetHandler(四大核心組件)。Executor 主要負責維護一級緩存和二級緩存,並提供事務管理的相關操作,它會將數據庫相關操作委託給 StatementHandler 完成。StatementHandler 首先通過 ParameterHandler 完成 SQL 語句的實參綁定;然後通過 java.sql.Statement 對象執行SQL 語句並得到結果集;最後通過 ResultSetHandler 完成結果集ResultSet 的映射,得到結果對象並返回。如下展示了 MyBatis 執行一條 SQL 的過程:

七、MyBatis 註解與 xml 的優缺點

【1】mapper.xml:跟接口分離、統一管理。複雜的語句可以不影響接口的可讀性。   缺點:過多的xml文件;
【2】Annotation:接口就能看到 sql 語句,可讀性高,不需要找 xml 文件,方便,優先級高於xml。 缺點:複雜的聯合查詢不好維護,代碼可讀性差,不能複用 sql 語句;

八、MyBatis 是如何調用存儲過程的

<!-- statementType 聲明指向的是什麼類型,其中CALLABLE是執行存儲過程和函數的-->
<select id="getXXX" parameterType="map" useCache="false" statementType="CALLABLE">
    <![CDATA[
        CALL 存儲過程名稱(
            --parameterType="map" 使用map封裝參數,直接輸入key名稱就可以獲取到
            --mode=IN  輸入參數
            #{iPageSize, jdbcType=DOUBLE, mode=IN}, --條數
            --mode=OUT 返回結果
            #{iTotalRecords, jdbcType=DOUBLE, mode=OUT}, --總條數
            --resultMap  映射實體類或用LinkedHashMap接收
            #{vCursor, mode=OUT, jdbcType=CURSOR, resultMap=cursorMap})
    ]]>
</select>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章