<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面渣逆襲:MyBatis連環(huán)20問,這誰頂?shù)米。?/h1>

          共 37648字,需瀏覽 76分鐘

           ·

          2022-06-18 16:18

          大家好,今天我們的主角是MyBatis,作為當(dāng)前國內(nèi)最流行的ORM框架,是我們這些crud選手最趁手的工具,趕緊來看看面試都會問哪些問題吧。

          基礎(chǔ)

          1.說說什么是MyBatis?

          MyBatis logo

          先吹一下

          • Mybatis 是一個半 ORM(對象關(guān)系映射)框架,它內(nèi)部封裝了 JDBC,開發(fā)時只需要關(guān)注 SQL 語句本身,不需要花費精力去處理加載驅(qū)動、創(chuàng)建連接、創(chuàng)建statement 等繁雜的過程。程序員直接編寫原生態(tài) sql,可以嚴(yán)格控制 sql 執(zhí)行性能,靈活度高。

          • MyBatis 可以使用 XML 或注解來配置和映射原生信息,將 POJO 映射成數(shù)據(jù)庫中的記錄,避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。

          再說一下缺點

          • SQL語句的編寫工作量較大,尤其當(dāng)字段多、關(guān)聯(lián)表多時,對開發(fā)人員編寫SQL語句的功底有一定要求
          • SQL語句依賴于數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫移植性差,不能隨意更換數(shù)據(jù)庫

          ORM是什么?

          ORM簡單示意圖
          • ORM(Object Relational Mapping),對象關(guān)系映射,是一種為了解決關(guān)系型數(shù)據(jù)庫數(shù)據(jù)與簡單Java對象(POJO)的映射關(guān)系的技術(shù)。簡單來說,ORM是通過使用描述對象和數(shù)據(jù)庫之間映射的元數(shù)據(jù),將程序中的對象自動持久化到關(guān)系型數(shù)據(jù)庫中。

          為什么說Mybatis是半自動ORM映射工具?它與全自動的區(qū)別在哪里?

          • Hibernate屬于全自動ORM映射工具,使用Hibernate查詢關(guān)聯(lián)對象或者關(guān)聯(lián)集合對象時,可以根據(jù)對象關(guān)系模型直接獲取,所以它是全自動的。
          • 而Mybatis在查詢關(guān)聯(lián)對象或關(guān)聯(lián)集合對象時,需要手動編寫SQL來完成,所以,被稱之為半自動ORM映射工具。

          JDBC編程有哪些不足之處,MyBatis是如何解決的?

          JDBC編程的不足
          • 1、數(shù)據(jù)連接創(chuàng)建、釋放頻繁造成系統(tǒng)資源浪費從而影響系統(tǒng)性能
            • 解決:在mybatis-config.xml中配置數(shù)據(jù)鏈接池,使用連接池統(tǒng)一管理數(shù)據(jù)庫連接。
          • 2、sql語句寫在代碼中造成代碼不易維護(hù)
            • 解決:將sql語句配置在XXXXmapper.xml文件中與java代碼分離。
          • 3、向sql語句傳參數(shù)麻煩,因為sql語句的where條件不一定,可能多也可能少,占位符需要和參數(shù)一一對應(yīng)。
            • 解決:Mybatis自動將java對象映射至sql語句。
          • 4、對結(jié)果集解析麻煩,sql變化導(dǎo)致解析代碼變化,且解析前需要遍歷,如果能將數(shù)據(jù)庫記錄封裝成pojo對象解析比較方便。
            • 解決:Mybatis自動將sql執(zhí)行結(jié)果映射至java對象。

          2.Hibernate 和 MyBatis 有什么區(qū)別?

          PS:直接用Hibernate的應(yīng)該不多了吧,畢竟大家都是“敏捷開發(fā)”,但架不住面試愛問。

          相同點

          • 都是對jdbc的封裝,都是應(yīng)用于持久層的框架。
          這還用說?

          不同點

          • 映射關(guān)系

            • MyBatis 是一個半自動映射的框架,配置Java對象與sql語句執(zhí)行結(jié)果的對應(yīng)關(guān)系,多表關(guān)聯(lián)關(guān)系配置簡單
            • Hibernate 是一個全表映射的框架,配置Java對象與數(shù)據(jù)庫表的對應(yīng)關(guān)系,多表關(guān)聯(lián)關(guān)系配置復(fù)雜
          • SQL優(yōu)化和移植性

            • Hibernate 對SQL語句封裝,提供了日志、緩存、級聯(lián)(級聯(lián)比 MyBatis 強(qiáng)大)等特性,此外還提供 HQL(Hibernate Query Language)操作數(shù)據(jù)庫,數(shù)據(jù)庫無關(guān)性支持好,但會多消耗性能。如果項目需要支持多種數(shù)據(jù)庫,代碼開發(fā)量少,但SQL語句優(yōu)化困難。
            • MyBatis 需要手動編寫 SQL,支持動態(tài) SQL、處理列表、動態(tài)生成表名、支持存儲過程。開發(fā)工作量相對大些。直接使用SQL語句操作數(shù)據(jù)庫,不支持?jǐn)?shù)據(jù)庫無關(guān)性,但sql語句優(yōu)化容易。

          MyBatis和Hibernate的適用場景?

          Mybatis vs Hibernate
          • Hibernate 是標(biāo)準(zhǔn)的ORM框架,SQL編寫量較少,但不夠靈活,適合于需求相對穩(wěn)定,中小型的軟件項目,比如:辦公自動化系統(tǒng)

          • MyBatis 是半ORM框架,需要編寫較多SQL,但是比較靈活,適合于需求變化頻繁,快速迭代的項目,比如:電商網(wǎng)站

          3.MyBatis使用過程?生命周期?

          MyBatis基本使用的過程大概可以分為這么幾步:

          Mybatis基本使用步驟
          • 1、 創(chuàng)建SqlSessionFactory

            可以從配置或者直接編碼來創(chuàng)建SqlSessionFactory

          String resource = "org/mybatis/example/mybatis-config.xml";
          InputStream inputStream = Resources.getResourceAsStream(resource);
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          • 2、 通過SqlSessionFactory創(chuàng)建SqlSession

            SqlSession(會話)可以理解為程序和數(shù)據(jù)庫之間的橋梁

          SqlSession session = sqlSessionFactory.openSession();
          • 3、 通過sqlsession執(zhí)行數(shù)據(jù)庫操作

            • 可以通過 SqlSession 實例來直接執(zhí)行已映射的 SQL 語句:

              Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog"101);
            • 更常用的方式是先獲取Mapper(映射),然后再執(zhí)行SQL語句:

              BlogMapper mapper = session.getMapper(BlogMapper.class);
              Blog blog = mapper.selectBlog(101);
          • 4、 調(diào)用session.commit()提交事務(wù)

            如果是更新、刪除語句,我們還需要提交一下事務(wù)。

          • 5、 調(diào)用session.close()關(guān)閉會話

            最后一定要記得關(guān)閉會話。

          MyBatis生命周期?

          上面提到了幾個MyBatis的組件,一般說的MyBatis生命周期就是這些組件的生命周期。

          • SqlSessionFactoryBuilder

            一旦創(chuàng)建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 實例的生命周期只存在于方法的內(nèi)部。

          • SqlSessionFactory

            SqlSessionFactory 是用來創(chuàng)建SqlSession的,相當(dāng)于一個數(shù)據(jù)庫連接池,每次創(chuàng)建SqlSessionFactory都會使用數(shù)據(jù)庫資源,多次創(chuàng)建和銷毀是對資源的浪費。所以SqlSessionFactory是應(yīng)用級的生命周期,而且應(yīng)該是單例的。

          • SqlSession

            SqlSession相當(dāng)于JDBC中的Connection,SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的生命周期是一次請求或一個方法。

          • Mapper

            映射器是一些綁定映射語句的接口。映射器接口的實例是從 SqlSession 中獲得的,它的生命周期在sqlsession事務(wù)方法之內(nèi),一般會控制在方法級。

          MyBatis主要組件生命周期

          當(dāng)然,萬物皆可集成Spring,MyBatis通常也是和Spring集成使用,Spring可以幫助我們創(chuàng)建線程安全的、基于事務(wù)的 SqlSession 和映射器,并將它們直接注入到我們的 bean 中,我們不需要關(guān)心它們的創(chuàng)建過程和生命周期,那就是另外的故事了。

          ps:接下來看看Mybatis的基本使用,不會有人不會吧?不會吧!

          這個應(yīng)該會

          4.在mapper中如何傳遞多個參數(shù)?

          mapper傳遞多個參數(shù)方法

          方法1:順序傳參法

          public User selectUser(String name, int deptId);

          <select id="selectUser" resultMap="UserResultMap">
              select * from user
              where user_name = #{0} and dept_id = #{1}
          </select>
          • #{}里面的數(shù)字代表傳入?yún)?shù)的順序。
          • 這種方法不建議使用,sql層表達(dá)不直觀,且一旦順序調(diào)整容易出錯。

          方法2:@Param注解傳參法

          public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);

          <select id="selectUser" resultMap="UserResultMap">
              select * from user
              where user_name = #{userName} and dept_id = #{deptId}
          </select>
          • #{}里面的名稱對應(yīng)的是注解@Param括號里面修飾的名稱。
          • 這種方法在參數(shù)不多的情況還是比較直觀的,(推薦使用)。

          方法3:Map傳參法

          public User selectUser(Map<String, Object> params);

          <select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
              select * from user
              where user_name = #{userName} and dept_id = #{deptId}
          </select>
          • #{}里面的名稱對應(yīng)的是Map里面的key名稱。
          • 這種方法適合傳遞多個參數(shù),且參數(shù)易變能靈活傳遞的情況。

          方法4:Java Bean傳參法

          public User selectUser(User user);

          <select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
              select * from user
              where user_name = #{userName} and dept_id = #{deptId}
          </select>
          • #{}里面的名稱對應(yīng)的是User類里面的成員屬性。
          • 這種方法直觀,需要建一個實體類,擴(kuò)展不容易,需要加屬性,但代碼可讀性強(qiáng),業(yè)務(wù)邏輯處理方便,推薦使用。(推薦使用)。

          5.實體類屬性名和表中字段名不一樣 ,怎么辦?

          • 第1種:通過在查詢的SQL語句中定義字段名的別名,讓字段名的別名和實體類的屬性名一致。

            <select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
                   select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
            </select>

          • 第2種:通過resultMap  中的<result>來映射字段名和實體類屬性名的一一對應(yīng)的關(guān)系。

            <select id="getOrder" parameterType="int" resultMap="orderResultMap">
             select * from orders where order_id=#{id}
            </select>
                
            <resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
                <!–用id屬性來映射主鍵字段–>
                <id property="id" column="order_id">
                <!–用result屬性來映射非主鍵字段,property為實體類屬性名,column為數(shù)據(jù)庫表中的屬性–>
             <result property ="orderno" column ="order_no"/>
             <result property="price" column="order_price" />
            </reslutMap>

          6.Mybatis是否可以映射Enum枚舉類?

          • Mybatis當(dāng)然可以映射枚舉類,不單可以映射枚舉類,Mybatis可以映射任何對象到表的一列上。映射方式為自定義一個TypeHandler,實現(xiàn)TypeHandler的setParameter()和getResult()接口方法。
          • TypeHandler有兩個作用,一是完成從javaType至jdbcType的轉(zhuǎn)換,二是完成jdbcType至javaType的轉(zhuǎn)換,體現(xiàn)為setParameter()和getResult()兩個方法,分別代表設(shè)置sql問號占位符參數(shù)和獲取列查詢結(jié)果。

          7.#{}和${}的區(qū)別?

          #{}和${}比較
          • #{}是占位符,預(yù)編譯處理;${}是拼接符,字符串替換,沒有預(yù)編譯處理。
          • Mybatis在處理#{}時,#{}傳入?yún)?shù)是以字符串傳入,會將SQL中的#{}替換為?號,調(diào)用PreparedStatement的set方法來賦值。
          • #{} 可以有效的防止SQL注入,提高系統(tǒng)安全性;${} 不能防止SQL 注入
          • #{} 的變量替換是在DBMS 中;${} 的變量替換是在 DBMS 外

          8.模糊查詢like語句該怎么寫?

          concat拼接like
          • 1 ’%${question}%’ 可能引起SQL注入,不推薦
          • 2 "%"#{question}"%" 注意:因為#{…}解析成sql語句時候,會在變量外側(cè)自動加單引號’ ',所以這里 % 需要使用雙引號" ",不能使用單引號 ’ ',不然會查不到任何結(jié)果。
          • 3 CONCAT(’%’,#{question},’%’) 使用CONCAT()函數(shù),(推薦?)
          • 4 使用bind標(biāo)簽(不推薦)
          <select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
          &emsp;&emsp;<bind name="pattern" value="'%' + username + '%'" />
          &emsp;&emsp;select id,sex,age,username,password from person where username LIKE #{pattern}
          </select>

          9.Mybatis能執(zhí)行一對一、一對多的關(guān)聯(lián)查詢嗎?

          當(dāng)然可以,不止支持一對一、一對多的關(guān)聯(lián)查詢,還支持多對多、多對一的關(guān)聯(lián)查詢。

          MyBatis級聯(lián)
          • 一對一<association>

            比如訂單和支付是一對一的關(guān)系,這種關(guān)聯(lián)的實現(xiàn):

            • 實體類:

              public class Order {
                  private Integer orderId;
                  private String orderDesc;

                  /**
                   * 支付對象
                   */

                  private Pay pay;
                  //……
              }
            • 結(jié)果映射

              <!-- 訂單resultMap -->
              <resultMap id="peopleResultMap" type="cn.fighter3.entity.Order">
                  <id property="orderId" column="order_id" />
                  <result property="orderDesc" column="order_desc"/>
                  <!--一對一結(jié)果映射-->
                  <association property="pay" javaType="cn.fighter3.entity.Pay">
                      <id column="payId" property="pay_id"/>
                      <result column="account" property="account"/>
                  </association>
              </resultMap>
            • 查詢就是普通的關(guān)聯(lián)查

                  <select id="getTeacher" resultMap="getTeacherMap" parameterType="int">
                      select * from order o 
                       left join pay p on o.order_id=p.order_id
                      where  o.order_id=#{orderId}
                  </select>
          • 一對多<collection>

            比如商品分類和商品,是一對多的關(guān)系。

            • 查詢

              查詢就是一個普通的關(guān)聯(lián)查詢

                      <!-- 關(guān)聯(lián)查詢分類和產(chǎn)品表 -->
                      <select id="listCategory" resultMap="categoryBean">
                          select c.*, p.* from category_ c left join product_ p on c.id = p.cid
                      </select>  
            • 實體類

              public class Category {
                  private int categoryId;
                  private String categoryName;
                
                  /**
                  * 商品列表
                  **/

                  List<Product> products;
                  //……
              }
            • 結(jié)果映射

                      <resultMap type="Category" id="categoryBean">
                          <id column="categoryId" property="category_id" />
                          <result column="categoryName" property="category_name" />
                   
                          <!-- 一對多的關(guān)系 -->
                          <!-- property: 指的是集合屬性的值, ofType:指的是集合中元素的類型 -->
                          <collection property="products" ofType="Product">
                              <id column="product_id" property="productId" />
                              <result column="productName" property="productName" />
                              <result column="price" property="price" />
                          </collection>
                      </resultMap>

          那么多對一、多對多怎么實現(xiàn)呢?還是利用<association>和<collection>,篇幅所限,這里就不展開了。

          10.Mybatis是否支持延遲加載?原理?

          • Mybatis支持association關(guān)聯(lián)對象和collection關(guān)聯(lián)集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啟用延遲加載lazyLoadingEnabled=true|false。
          • 它的原理是,使用CGLIB創(chuàng)建目標(biāo)對象的代理對象,當(dāng)調(diào)用目標(biāo)方法時,進(jìn)入攔截器方法,比如調(diào)用a.getB().getName(),攔截器invoke()方法發(fā)現(xiàn)a.getB()是null值,那么就會單獨發(fā)送事先保存好的查詢關(guān)聯(lián)B對象的sql,把B查詢上來,然后調(diào)用a.setB(b),于是a的對象b屬性就有值了,接著完成a.getB().getName()方法的調(diào)用。這就是延遲加載的基本原理。
          • 當(dāng)然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。

          11.如何獲取生成的主鍵?

          • 新增標(biāo)簽中添加:keyProperty=" ID "  即可

            <insert id="insert" useGeneratedKeys="true" keyProperty="userId" >
                insert into user
                user_name, user_password, create_time)
             
                values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
            </insert>
          • 這時候就可以完成回填主鍵

            mapper.insert(user);
            user.getId;

          12.MyBatis支持動態(tài)SQL嗎?

          MyBatis中有一些支持動態(tài)SQL的標(biāo)簽,它們的原理是使用OGNL從SQL參數(shù)對象中計算表達(dá)式的值,根據(jù)表達(dá)式的值動態(tài)拼接SQL,以此來完成動態(tài)SQL的功能。

          ![

          • if

            根據(jù)條件來組成where子句

            <select id="findActiveBlogWithTitleLike"
                 resultType="Blog">
              SELECT * FROM BLOG
              WHERE state = ‘ACTIVE’
              <if test="title != null">
                AND title like #{title}
              </if>
            </select>
          • choose (when, otherwise)

            這個和Java 中的 switch 語句有點像

            <select id="findActiveBlogLike"
                 resultType="Blog">
              SELECT * FROM BLOG WHERE state = ‘ACTIVE’
              <choose>
                <when test="title != null">
                  AND title like #{title}
                </when>
                <when test="author != null and author.name != null">
                  AND author_name like #{author.name}
                </when>
                <otherwise>
                  AND featured = 1
                </otherwise>
              </choose>
            </select>
          • trim (where, set)

            <select id="findActiveBlogLike"
                 resultType="Blog">
              SELECT * FROM BLOG
              <where>
                <if test="state != null">
                     state = #{state}
                </if>
                <if test="title != null">
                    AND title like #{title}
                </if>
                <if test="author != null and author.name != null">
                    AND author_name like #{author.name}
                </if>
              </where>
            </select>
            • <set> 可以用在動態(tài)更新的時候

              <update id="updateAuthorIfNecessary">
                update Author
                  <set>
                    <if test="username != null">username=#{username},</if>
                    <if test="password != null">password=#{password},</if>
                    <if test="email != null">email=#{email},</if>
                    <if test="bio != null">bio=#{bio}</if>
                  </set>
                where id=#{id}
              </update>
            • <where>可以用在所有的查詢條件都是動態(tài)的情況
          • foreach

            看到名字就知道了,這個是用來循環(huán)的,可以對集合進(jìn)行遍歷

            <select id="selectPostIn" resultType="domain.blog.Post">
              SELECT *
              FROM POST P
              <where>
                <foreach item="item" index="index" collection="list"
                    open="ID in (" separator="," close=")" nullable="true">
                      #{item}
                </foreach>
              </where>
            </select>

          13.MyBatis如何執(zhí)行批量操作?

          MyBatis批量操作

          第一種方法:使用foreach標(biāo)簽

          foreach的主要用在構(gòu)建in條件中,它可以在SQL語句中進(jìn)行迭代一個集合。foreach標(biāo)簽的屬性主要有item,index,collection,open,separator,close。

          • item??    表示集合中每一個元素進(jìn)行迭代時的別名,隨便起的變量名;
          • index??  指定一個名字,用于表示在迭代過程中,每次迭代到的位置,不常用;
          • open??  表示該語句以什么開始,常用“(”;
          • separator 表示在每次進(jìn)行迭代之間以什么符號作為分隔符,常用“,”;
          • close??  表示以什么結(jié)束,常用“)”。

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

          1. 如果傳入的是單參數(shù)且參數(shù)類型是一個List的時候,collection屬性值為list
          2. 如果傳入的是單參數(shù)且參數(shù)類型是一個array數(shù)組的時候,collection的屬性值為array
          3. 如果傳入的參數(shù)是多個的時候,我們就需要把它們封裝成一個Map了,當(dāng)然單參數(shù)也可以封裝成map,實際上如果你在傳入?yún)?shù)的時候,在MyBatis里面也是會把它封裝成一個Map的, map的key就是參數(shù)名,所以這個時候collection屬性值就是傳入的List或array對象在自己封裝的map里面的key

          看看批量保存的兩種用法:

          <!-- MySQL下批量保存,可以foreach遍歷 mysql支持values(),(),()語法 --> //推薦使用
          <insert id="addEmpsBatch">
              INSERT INTO emp(ename,gender,email,did)
              VALUES
              <foreach collection
          ="emps" item="emp" separator=",">
                  (#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
              </foreach>
          </insert>
          <!-- 這種方式需要數(shù)據(jù)庫連接屬性allowMutiQueries=true的支持
           如jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true -->  
          <insert id="addEmpsBatch">
              <foreach collection="emps" item="emp" separator=";">                                 
                  INSERT INTO emp(ename,gender,email,did)
                  VALUES(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
              </foreach>
          </insert>

          第二種方法:使用ExecutorType.BATCH

          • Mybatis內(nèi)置的ExecutorType有3種,默認(rèn)為simple,該模式下它為每個語句的執(zhí)行創(chuàng)建一個新的預(yù)處理語句,單條提交sql;而batch模式重復(fù)使用已經(jīng)預(yù)處理的語句,并且批量執(zhí)行所有更新語句,顯然batch性能將更優(yōu);但batch模式也有自己的問題,比如在Insert操作時,在事務(wù)沒有提交之前,是沒有辦法獲取到自增的id,在某些情況下不符合業(yè)務(wù)的需求。

            具體用法如下:

            //批量保存方法測試
            @Test  
            public void testBatch() throws IOException{
                SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
                //可以執(zhí)行批量操作的sqlSession
                SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

                //批量保存執(zhí)行前時間
                long start = System.currentTimeMillis();
                try {
                    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
                    for (int i = 0; i < 1000; i++) {
                        mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(05), "b""1"));
                    }

                    openSession.commit();
                    long end = System.currentTimeMillis();
                    //批量保存執(zhí)行后的時間
                    System.out.println("執(zhí)行時長" + (end - start));
                    //批量 預(yù)編譯sql一次==》設(shè)置參數(shù)==》10000次==》執(zhí)行1次   677
                    //非批量  (預(yù)編譯=設(shè)置參數(shù)=執(zhí)行 )==》10000次   1121

                } finally {
                    openSession.close();
                }
            }
          • mapper和mapper.xml如下

            public interface EmployeeMapper {   
                //批量保存員工
                Long addEmp(Employee employee);
            }
            <mapper namespace="com.jourwon.mapper.EmployeeMapper"
                 <!--批量保存員工 -->
                <insert id="addEmp">
                    insert into employee(lastName,email,gender)
                    values(#{lastName},#{email},#{gender})
                </insert>
            </mapper>

          14.說說Mybatis的一級、二級緩存?

          1. 一級緩存: 基于 PerpetualCache 的 HashMap 本地緩存,其存儲作用域為SqlSession,各個SqlSession之間的緩存相互隔離,當(dāng) Session flush 或 close 之后,該 SqlSession 中的所有 Cache 就將清空,MyBatis默認(rèn)打開一級緩存。

            Mybatis一級緩存
          2. 二級緩存與一級緩存其機(jī)制相同,默認(rèn)也是采用 PerpetualCache,HashMap 存儲,不同之處在于其存儲作用域為 Mapper(Namespace),可以在多個SqlSession之間共享,并且可自定義存儲源,如 Ehcache。默認(rèn)不打開二級緩存,要開啟二級緩存,使用二級緩存屬性類需要實現(xiàn)Serializable序列化接口(可用來保存對象的狀態(tài)),可在它的映射文件中配置。

          Mybatis二級緩存示意圖

          原理

          15.能說說MyBatis的工作原理嗎?

          我們已經(jīng)大概知道了MyBatis的工作流程,按工作原理,可以分為兩大步:生成會話工廠會話運行。

          MyBatis的工作流程

          MyBatis是一個成熟的框架,篇幅限制,這里抓大放小,來看看它的主要工作流程。

          構(gòu)建會話工廠

          構(gòu)造會話工廠也可以分為兩步:

          構(gòu)建會話工廠
          • 獲取配置

            獲取配置這一步經(jīng)過了幾步轉(zhuǎn)化,最終由生成了一個配置類Configuration實例,這個配置類實例非常重要,主要作用包括:

            • 讀取配置文件,包括基礎(chǔ)配置文件和映射文件

            • 初始化基礎(chǔ)配置,比如MyBatis的別名,還有其它的一些重要的類對象,像插件、映射器、ObjectFactory等等

            • 提供一個單例,作為會話工廠構(gòu)建的重要參數(shù)

            • 它的構(gòu)建過程也會初始化一些環(huán)境變量,比如數(shù)據(jù)源

               public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
                      SqlSessionFactory var5;
                      //省略異常處理
                          //xml配置構(gòu)建器
                          XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
                          //通過轉(zhuǎn)化的Configuration構(gòu)建SqlSessionFactory
                          var5 = this.build(parser.parse());
               }
          • 構(gòu)建SqlSessionFactory

            SqlSessionFactory只是一個接口,構(gòu)建出來的實際上是它的實現(xiàn)類的實例,一般我們用的都是它的實現(xiàn)類DefaultSqlSessionFactory,

                public SqlSessionFactory build(Configuration config) {
                    return new DefaultSqlSessionFactory(config);
                }

          會話運行

          會話運行是MyBatis最復(fù)雜的部分,它的運行離不開四大組件的配合:

          MyBatis會話運行四大關(guān)鍵組件
          • Executor(執(zhí)行器)

            Executor起到了至關(guān)重要的作用,SqlSession只是一個門面,相當(dāng)于客服,真正干活的是是Executor,就像是默默無聞的工程師。它提供了相應(yīng)的查詢和更新方法,以及事務(wù)方法。

                        Environment environment = this.configuration.getEnvironment();
                        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
                        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
                        //通過Configuration創(chuàng)建executor
                        Executor executor = this.configuration.newExecutor(tx, execType);
                        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
          • StatementHandler(數(shù)據(jù)庫會話器)

            StatementHandler,顧名思義,處理數(shù)據(jù)庫會話的。我們以SimpleExecutor為例,看一下它的查詢方法,先生成了一個StatementHandler實例,再拿這個handler去執(zhí)行query。

                 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
                    Statement stmt = null;

                    List var9;
                    try {
                        Configuration configuration = ms.getConfiguration();
                        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
                        stmt = this.prepareStatement(handler, ms.getStatementLog());
                        var9 = handler.query(stmt, resultHandler);
                    } finally {
                        this.closeStatement(stmt);
                    }

                    return var9;
                }

            再以最常用的PreparedStatementHandler看一下它的query方法,其實在上面的prepareStatement已經(jīng)對參數(shù)進(jìn)行了預(yù)編譯處理,到了這里,就直接執(zhí)行sql,使用ResultHandler處理返回結(jié)果。

                public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
                    PreparedStatement ps = (PreparedStatement)statement;
                    ps.execute();
                    return this.resultSetHandler.handleResultSets(ps);
                }
          • ParameterHandler (參數(shù)處理器)

            PreparedStatementHandler里對sql進(jìn)行了預(yù)編譯處理

                public void parameterize(Statement statement) throws SQLException {
                    this.parameterHandler.setParameters((PreparedStatement)statement);
                }

            這里用的就是ParameterHandler,setParameters的作用就是設(shè)置預(yù)編譯SQL語句的參數(shù)。

            里面還會用到typeHandler類型處理器,對類型進(jìn)行處理。

            public interface ParameterHandler {
                Object getParameterObject();

                void setParameters(PreparedStatement var1) throws SQLException;
            }
          • ResultSetHandler(結(jié)果處理器)

            我們前面也看到了,最后的結(jié)果要通過ResultSetHandler來進(jìn)行處理,handleResultSets這個方法就是用來包裝結(jié)果集的。Mybatis為我們提供了一個DefaultResultSetHandler,通常都是用這個實現(xiàn)類去進(jìn)行結(jié)果的處理的。

            public interface ResultSetHandler {
                <E> List<E> handleResultSets(Statement var1) throws SQLException;

                <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;

                void handleOutputParameters(CallableStatement var1) throws SQLException;
            }

            它會使用typeHandle處理類型,然后用ObjectFactory提供的規(guī)則組裝對象,返回給調(diào)用者。

          整體上總結(jié)一下會話運行:

          會話運行的簡單示意圖

          PS:以上源碼分析比較簡單,在真正的源碼大佬面前可能過不了關(guān),有條件的建議Debug一下MyBatis的源碼。

          我們最后把整個的工作流程串聯(lián)起來,簡單總結(jié)一下:

          MyBatis整體工作原理圖
          1. 讀取 MyBatis 配置文件——mybatis-config.xml 、加載映射文件——映射文件即 SQL 映射文件,文件中配置了操作數(shù)據(jù)庫的 SQL 語句。最后生成一個配置對象。

          2. 構(gòu)造會話工廠:通過 MyBatis 的環(huán)境等配置信息構(gòu)建會話工廠 SqlSessionFactory。

          3. 創(chuàng)建會話對象:由會話工廠創(chuàng)建 SqlSession 對象,該對象中包含了執(zhí)行 SQL 語句的所有方法。

          4. Executor 執(zhí)行器:MyBatis 底層定義了一個 Executor 接口來操作數(shù)據(jù)庫,它將根據(jù) SqlSession 傳遞的參數(shù)動態(tài)地生成需要執(zhí)行的 SQL 語句,同時負(fù)責(zé)查詢緩存的維護(hù)。

          5. StatementHandler:數(shù)據(jù)庫會話器,串聯(lián)起參數(shù)映射的處理和運行結(jié)果映射的處理。

          6. 參數(shù)處理:對輸入?yún)?shù)的類型進(jìn)行處理,并預(yù)編譯。

          7. 結(jié)果處理:對返回結(jié)果的類型進(jìn)行處理,根據(jù)對象映射規(guī)則,返回相應(yīng)的對象。

          16.MyBatis的功能架構(gòu)是什么樣的?

          MyBatis功能架構(gòu)

          我們一般把Mybatis的功能架構(gòu)分為三層:

          • API接口層:提供給外部使用的接口API,開發(fā)人員通過這些本地API來操縱數(shù)據(jù)庫。接口層一接收到調(diào)用請求就會調(diào)用數(shù)據(jù)處理層來完成具體的數(shù)據(jù)處理。
          • 數(shù)據(jù)處理層:負(fù)責(zé)具體的SQL查找、SQL解析、SQL執(zhí)行和執(zhí)行結(jié)果映射處理等。它主要的目的是根據(jù)調(diào)用的請求完成一次數(shù)據(jù)庫操作。
          • 基礎(chǔ)支撐層:負(fù)責(zé)最基礎(chǔ)的功能支撐,包括連接管理、事務(wù)管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來作為最基礎(chǔ)的組件。為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐。

          17.為什么Mapper接口不需要實現(xiàn)類?

          四個字回答:動態(tài)代理,我們來看一下獲取Mapper的過程:

          Mapper代理
          • 獲取Mapper

            我們都知道定義的Mapper接口是沒有實現(xiàn)類的,Mapper映射其實是通過動態(tài)代理實現(xiàn)的。

            BlogMapper mapper = session.getMapper(BlogMapper.class);

            七拐八繞地進(jìn)去看一下,發(fā)現(xiàn)獲取Mapper的過程,需要先獲取MapperProxyFactory——Mapper代理工廠。

                public <T> getMapper(Class<T> type, SqlSession sqlSession) {
                    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
                    if (mapperProxyFactory == null) {
                        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
                    } else {
                        try {
                            return mapperProxyFactory.newInstance(sqlSession);
                        } catch (Exception var5) {
                            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
                        }
                    }
                }
            public class MapperProxyFactory<T{
                private final Class<T> mapperInterface;
                ……
                protected T newInstance(MapperProxy<T> mapperProxy) {
                    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
                }

                public T newInstance(SqlSession sqlSession) {
                    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
                    return this.newInstance(mapperProxy);
                }
            }

            這里可以看到動態(tài)代理對接口的綁定,它的作用就是生成動態(tài)代理對象(占位),而代理的方法被放到了MapperProxy中。

            MapperProxy里,通常會生成一個MapperMethod對象,它是通過cachedMapperMethod方法對其進(jìn)行初始化的,然后執(zhí)行excute方法。

                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    try {
                        return Object.class.equals(method.getDeclaringClass()) ? method.invoke(thisargs) : this.cachedInvoker(method).invoke(proxymethodargsthis.sqlSession);
                    } catch (Throwable var5) {
                        throw ExceptionUtil.unwrapThrowable(var5);
                    }
                }
            • MapperMethod

              MapperMethod里的excute方法,會真正去執(zhí)行sql。這里用到了命令模式,其實繞一圈,最終它還是通過SqlSession的實例去運行對象的sql。

                public Object execute(SqlSession sqlSession, Object[] args) {
                      Object result;
                      Object param;
                      ……
                      case SELECT:
                          if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                              this.executeWithResultHandler(sqlSession, args);
                              result = null;
                          } else if (this.method.returnsMany()) {
                              result = this.executeForMany(sqlSession, args);
                          } else if (this.method.returnsMap()) {
                              result = this.executeForMap(sqlSession, args);
                          } else if (this.method.returnsCursor()) {
                              result = this.executeForCursor(sqlSession, args);
                          } else {
                              param = this.method.convertArgsToSqlCommandParam(args);
                              result = sqlSession.selectOne(this.command.getName(), param);
                              if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                                  result = Optional.ofNullable(result);
                              }
                          }
                          break;
                         ……
                  }
            • MapperProxy
            • MapperProxyFactory

              MapperProxyFactory的作用是生成MapperProxy(Mapper代理對象)。

          18.Mybatis都有哪些Executor執(zhí)行器?

          Mybatis Executor類型

          Mybatis有三種基本的Executor執(zhí)行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

          • SimpleExecutor:每執(zhí)行一次update或select,就開啟一個Statement對象,用完立刻關(guān)閉Statement對象。
          • ReuseExecutor:執(zhí)行update或select,以sql作為key查找Statement對象,存在就使用,不存在就創(chuàng)建,用完后,不關(guān)閉Statement對象,而是放置于Map<String, Statement>內(nèi),供下一次使用。簡言之,就是重復(fù)使用Statement對象。
          • BatchExecutor:執(zhí)行update(沒有select,JDBC批處理不支持select),將所有sql都添加到批處理中(addBatch()),等待統(tǒng)一執(zhí)行(executeBatch()),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢后,等待逐一執(zhí)行executeBatch()批處理。與JDBC批處理相同。

          作用范圍:Executor的這些特點,都嚴(yán)格限制在SqlSession生命周期范圍內(nèi)。

          Mybatis中如何指定使用哪一種Executor執(zhí)行器?

          • 在Mybatis配置文件中,在設(shè)置(settings)可以指定默認(rèn)的ExecutorType執(zhí)行器類型,也可以手動給DefaultSqlSessionFactory的創(chuàng)建SqlSession的方法傳遞ExecutorType類型參數(shù),如SqlSession openSession(ExecutorType execType)。
          • 配置默認(rèn)的執(zhí)行器。SIMPLE 就是普通的執(zhí)行器;REUSE 執(zhí)行器會重用預(yù)處理語句(prepared statements);BATCH 執(zhí)行器將重用語句并執(zhí)行批量更新。

          插件

          19.說說Mybatis的插件運行原理,如何編寫一個插件?

          插件的運行原理?

          Mybatis會話的運行需要ParameterHandler、ResultSetHandler、StatementHandler、Executor這四大對象的配合,插件的原理就是在這四大對象調(diào)度的時候,插入一些我我們自己的代碼。

          MyBatis插件原理簡圖

          Mybatis使用JDK的動態(tài)代理,為目標(biāo)對象生成代理對象。它提供了一個工具類Plugin,實現(xiàn)了InvocationHandler接口。

          Plugin中調(diào)用插件方法

          使用Plugin生成代理對象,代理對象在調(diào)用方法的時候,就會進(jìn)入invoke方法,在invoke方法中,如果存在簽名的攔截方法,插件的intercept方法就會在這里被我們調(diào)用,然后就返回結(jié)果。如果不存在簽名方法,那么將直接反射調(diào)用我們要執(zhí)行的方法。

          如何編寫一個插件?

          我們自己編寫MyBatis 插件,只需要實現(xiàn)攔截器接口 Interceptor (org.apache.ibatis. plugin Interceptor ),在實現(xiàn)類中對攔截對象和方法進(jìn)行處理。

          • 實現(xiàn)Mybatis的Interceptor接口并重寫intercept()方法

            這里我們只是在目標(biāo)對象執(zhí)行目標(biāo)方法的前后進(jìn)行了打??;

            public class MyInterceptor implements Interceptor {
                Properties props=null;

                @Override
                public Object intercept(Invocation invocation) throws Throwable {
                    System.out.println("before……");
                    //如果當(dāng)前代理的是一個非代理對象,那么就會調(diào)用真實攔截對象的方法
                    // 如果不是它就會調(diào)用下個插件代理對象的invoke方法
                    Object obj=invocation.proceed();
                    System.out.println("after……");
                    return obj;
                }
            }
          • 然后再給插件編寫注解,確定要攔截的對象,要攔截的方法

            @Intercepts({@Signature(
                    type = Executor.class,  //確定要攔截的對象
                    method 
            "update",        //確定要攔截的方法
                    args = {MappedStatement.class,Object.class}   //攔截方法的參數(shù)
            )})
            public class MyInterceptor implements Interceptor 
            {
                Properties props=null;

                @Override
                public Object intercept(Invocation invocation) throws Throwable {
                    System.out.println("before……");
                    //如果當(dāng)前代理的是一個非代理對象,那么就會調(diào)用真實攔截對象的方法
                    // 如果不是它就會調(diào)用下個插件代理對象的invoke方法
                    Object obj=invocation.proceed();
                    System.out.println("after……");
                    return obj;
                }
            }
          • 最后,再MyBatis配置文件里面配置插件

          <plugins>
              <plugin interceptor="xxx.MyPlugin">
                 <property name="dbType",value="mysql"/>
              </plugin>
          </plugins>    

          20.MyBatis是如何進(jìn)行分頁的?分頁插件的原理是什么?

          MyBatis是如何分頁的?

          MyBatis使用RowBounds對象進(jìn)行分頁,它是針對ResultSet結(jié)果集執(zhí)行的內(nèi)存分頁,而非物理分頁??梢栽趕ql內(nèi)直接書寫帶有物理分頁的參數(shù)來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。

          分頁插件的原理是什么?

          • 分頁插件的基本原理是使用Mybatis提供的插件接口,實現(xiàn)自定義插件,攔截Executor的query方法
          • 在執(zhí)行查詢的時候,攔截待執(zhí)行的sql,然后重寫sql,根據(jù)dialect方言,添加對應(yīng)的物理分頁語句和物理分頁參數(shù)。
          • 舉例:select * from student,攔截sql后重寫為:select t.* from (select * from student) t limit 0, 10

          可以看一下一個大概的MyBatis通用分頁攔截器:

          Mybatis-通用分頁攔截器


          參考
          • [1]. MyBatis面試題(2020最新版:https://blog.csdn.net/ThinkWon/article/details/101292950)

          • [2].mybatis官網(wǎng):https://mybatis.org/mybatis-3/zh/index.html

          • [3].《深入淺出MyBatis基礎(chǔ)原理與實戰(zhàn)》

          • [4].聊聊MyBatis緩存機(jī)制:https://tech.meituan.com/2018/01/19/mybatis-cache.html

          • [5].《MyBatis從入門到精通》



          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報

          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月天成人在线观看视频 | 黑人无码精品黑人,黑人 | 国产91娱乐乱伦视频 | 亚洲日韩欧美另类 | 日韩黄色电影院 |