《今天面試了嗎》-Mybatis

前言

昨天剛剛面完spring,根據hr的反饋說面試官對個人總體表現還算滿意,而後又通知我今天有空去再聊聊有關的技術。去的路上,我一直在想,今天會問些什麼問題,JVM?多線程?仍是分佈式......真是越想內心越沒底。想着想着就到了,儘管仍是那個熟悉的面試官,但那張年輕有爲的面孔絲毫沒有讓我放下緊張的情緒。他先開口了:昨天的面試感受你挺好的,你說你項目中還用的是mybatis框架做爲數據庫訪問,那咱們今天就來聊聊吧。java

面試環節

  • 面試官:你先說下你對mybatis的總體理解。mysql

  • 我:MyBatis是支持定製化SQL、存儲過程以及高級映射的優秀的持久層框架。它避免了幾乎全部JDBC代碼和手動設置參數以及獲取結果集。MyBatis能夠對配置和原生Map使用簡單的XML或註解,將接口和Java的POJO映射成數據庫中的記錄。程序員

  • 面試官:那大家公司爲何選擇Mybatis,爲何不用Hibernate呢?他兩有什麼區別嗎?面試

  • 我:mybatis的着力點在於POJO和SQL之間的映射關係,而後經過映射配置文件,將SQL所需的參數,以及返回的結果字段映射到指定POJO。Hibernate的ORM實現了POJO和數據庫表之間的映射,以及SQl的自動生成和執行,也就是說Hibernate會根據制定的存儲邏輯,自動生成對應的SQl並調用JDBC接口加以執行。下面我經過四個方面對比二者的區別:redis

  1. 開發對比:mybatis框架相對簡單容易上手,針對高級查詢,Mybatis須要手動編寫SQL語句。Hibernate的真正掌握要比MyBatis難一些,Hibernate有良好的的映射機制,開發者無需關心SQL的生成與結果映射,能夠更關注業務流程。
  2. 調優方案:mybatis能夠進行詳細的SQL優化設計,採用合理的session管理機制。Hibernate能夠指定合理的緩存策略;儘可能採用延遲加載特性;採用合理的session管理機制;採用批量抓取,設定合理的批處理參數。
  3. 擴展性方面:mybatis項目中的全部SQL語句都是依賴所用的數據庫的,因此不一樣數據庫類型的支持很差。Hibernate與具體數據庫的關聯只需在XML文件中配置便可,全部的HQL語句與具體使用的數據庫無關,移植性很好。
  4. 緩存機制:mybatis默認狀況下開啓了一級緩存;要開啓二級緩存,須要在sql映射文件中加上:映射文件中的全部select語句將會緩存,映射文件中的全部insert/update/delete會刷新緩存;緩存會使用LRU(最近最少使用)算法來回收;緩存會存儲列表集合會對象的1024個引用;緩存會被視爲read/write(可讀可寫)緩存,意味着對象檢索不是共享的,並且能夠安全地被調用者修改,而不干擾其餘調用者或線程所作的潛在修改。
    Hibernate的一級緩存是Session緩存,利用好一級緩存就須要對Session的生命週期進行管理好;二級緩存是SessionFactory級的緩存,分爲內置緩存和外置緩存。
  • 我:另外,有種說法,mybatis是半自動ORM映射工具,Hibernate是全自動的。這主要就是由於使用Hibernate查詢關聯對象或集合對象時,能夠根據對象關係模型調用api接口直接獲取。而Mybatis在查詢關聯對象或集合對象時,須要手動編寫sql來完成,因此叫作半自動。算法

  • 我:至於咱們公司爲何選擇半自動的mybatis,主要是由於咱們的業務常常須要編寫複雜的sql,好比動態的sql。還有這種更便於咱們使用索引來優化sql語句。spring

  • 面試官:你先說下JDBC的執行流程吧sql

  • 我:(1)加載JDBC驅動(2)創建並獲取數據庫鏈接(3)建立JDBC Statements對象(4)設置SQL語句的傳入參數(5)執行SQL語句並得到查詢結果(6)對查詢結果進行轉換處理並將處理結果返回(7)釋放相關資源(關閉Connection,關閉Statement,關閉ResultSet)數據庫

  • 面試官:那你能說下mybatis執行SQL的流程嗎?api

  • 我:好的。

  1. 加載配置並初始化:加載配置文件,將SQl配置信息加載成爲一個個MappedStatement對象(包括傳入參數映射配置,執行的sql語句,結果映射的配置),存儲在內存中。
  2. 傳遞調用請求:調用Mybatis提供的API,傳入SQL的ID和參數對象,將請求傳遞給下層的請求處理層進行處理。
  3. 處理請求:根據SQL的ID查找到對應的MappedStatement對象;根據傳入的參數對象解析MappedStatement對象,獲得最終要執行的SQL和執行參數;獲取數據庫鏈接,根據獲得的SQL語句和執行參數到數據庫中執行,並獲得執行結果;根據MappedStatement對象中的結果映射配置對獲得的執行結果進行轉換處理,獲得最終的處理結果;釋放鏈接資源;將最終的結果返回。 總之,這個過程就是:加載配置->SQL解析->SQL執行->結果映射->釋放鏈接
  • 面試官:很好。你剛說到初始化,你對mybatis初始化了解嗎?

  • 我:能夠這麼說,Myabtis初始化的過程就是建立Configuration對象的過程。過程也很簡單:(1)加載配置文件mybatis-config.xml到Mybatis內部。(2)使用Configuration對象做爲一個全部配置信息的容器,這個對象的組織結構和XML配置文件的組織結構幾乎徹底同樣,這樣配置文件的信息就能夠存到這個對象中,訪問起來很方便。

  • 面試官:那我問的再深刻一點,你看過mybatis的源碼嗎?

  • 我:沒看過。。關鍵的類仍是知道一點的。

  • 面試官:哦,那你說下你瞭解的mybatis的有哪些核心的類?

  • 我:(心想:既然面試前準備了,仍是要說的,否則怎麼顯得本身nb一些)

  1. 第一個是SqlSessionFactoryBuilder:經過類名就看出來這個類的主要做用是建立一個SqlSessionFactory。能夠重用這個類來建立多個SqlSessionFactory實例,可是最好不要讓其一直存在以保證全部的XML解析資源開放給更重要的事情。這個類能夠被實例化、使用和丟棄,一旦建立了SqlSessionFactory,就再也不須要它了。
  2. 第二個是SqlSessionFactory接口:它的做用就是sql會話工廠,用於建立SqlSession。SqlSessionFactory一旦被建立就應該在應用的運行期間一直存在,它的最佳做用域是應用做用域。
  3. 第三個是很重要的SqlSession接口:他是mybatis的一個重要接口,定義了數據庫的增刪改查以及事務管理的經常使用方法。SqlSession還提供了查找Mapper接口的有關方法。每一個線程都應該有本身的SqlSession實例,由於這個實例不是線程安全的,因此它的最佳做用域是請求或方法做用域。每次收到一個HTTP請求,就能夠打開一個SqlSession,返回了響應以後就關閉它。
  4. 第四個就是咱們編碼的主角Mapper接口:Mapper接口是指程序員自行定義的一個數據操縱接口,相似於一般所說的DAO接口。跟DAO接口不一樣的地方在於Mapper接口只須要定義不須要實現,mybatis會自動爲Mapper接口建立動態代理對象。Mapper接口的方法一般與Mapper配置文件中的select、insert、update和delete等XML節點存在一一對應關係。

另外,附贈一張mybatis的層次結構圖: mybatis層次結構.png

  • 面試官:那你能說下mybatis源碼中的主要部件嗎?

  • 我:(1)SqlSession:做爲mybatis工做的主要頂層API,表示和數據庫交互的會話,完成必要的數據庫增刪改查功能。(2)Executor:mybatis執行器,是Mybatis調度的核心,負責SQL語句的生成和查詢緩存的維護。(3)StatementHandler:封裝了JDBCStatement操做,負責對JDBC Statement的操做。(4)ParameterHandler:負責對用戶傳遞的參數轉換成JDBC Statement所須要的參數。(5)ResultSetHandler:負責將JDBC返回的ResultSet結果集轉換成List類型的集合。(6)TypeHandler:負責Java數據類型和jdbc數據類型之間的映射和轉換。(7)MappedStatement:維護了一條select/update/delete/insert節點的封裝。(8)Sqlsource:負責根據用戶傳遞的parameterObject,動態生成SQL語句,將信息封裝在BoundSql對象中,並返回。(9)BoundSql:表示動態生成的SQL語句以及相應的參數信息。(10)Configuration:Mybatis全部的配置信息都維護在這個對象中。

  • 面試官:原理聊完了,接下來咱們聊下實戰吧。。你在項目中是怎麼整合spring和mybatis的?

  • 我:我先說下xml的配置方式吧。

  1. 添加mybatis-spring的包:
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>x.x.x</version>
</dependency>
複製代碼
  1. 配置SqlSessionFactory:整合後,能夠不須要單獨的mybatis配置文件,所有的配置內容能夠再spring的上下文中。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  <!-- 當mybatis的xml文件和mapper接口不在相同包下時,須要用mapperLocations屬性指定xml文件的路徑。  
         *是個通配符,表明全部的文件,**表明全部目錄下 -->  
  <property name="mapperLocations" value="classpath:mapper/**/*.xml"/>
<!-- 加載mybatis的全局配置文件 --> 
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
</bean>
複製代碼

2.1:datasource:是數據源配置,經常使用的有DBCP,C3P0,Druid等。 2.2:mapperLocations:是指接口xml的文件配置,若是不配置的話映射接口類文件(mapper接口)和映射xml文件(mapper.xml)須要放在相同的包下。 3. 配置數據映射器類:利用mybatis-spring提供的自動掃描機制:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
  xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
 
 <!-- 自動掃描 -->
  <mybatis:scan base-package="org.mybatis.spring.sample.mapper" />

</beans>
複製代碼
  • 我:(接着說)如今好像大多使用的是註解配置mybatis和數據源的方式,也就是使用java代碼和spring提供的註解。(其實步驟大體差很少,因爲涉及安全問題代碼不透露,想學習的能夠網上找。)

  • 面試官:你能寫一個mapper映射文件中select的sql語句嗎?

  • 我:隨手寫了一個,接着解釋到:這個語句被稱做selectPerson,接收一個int類型的參數,並返回一個HashMap類型的對象,其中的鍵是列名,值即是結果行中的對應值。

<select id="selectPerson" parameterType = "int" resultType="hashmap"
select * from person where id = #{id}
</select>
複製代碼
  • 我(接着說):select中有這些屬性可選:
  1. id:必選的,命名空間中惟一的標識符,能夠被用來引用這條語句。
  2. parameterType:可選,將會傳入這條語句的參數類的徹底限定名或別名。
  3. resultType:從這條語句返回的指望類型的類的徹底限定名或別名。若是是集合,那應該是集合包含的類型,而不是集合自己。
  4. resultMap:外部resultMap的命名引用。注意使用resultType或resultMap,不能同時使用。
  5. flushCache:默認false。設置爲true表示只要語句被調用,都會致使本地緩存和二級緩存被清空。
  6. useCache:對select元素爲true。設置爲true會致使本條語句的結果被二級緩存。
  7. timeout:默認值爲unset(依賴驅動)。這個設置是在拋出異常以前,驅動程序等待數據庫返回請求結果的秒數。
  8. fetchSize:默認值爲unset(依賴驅動)。這是嘗試影響驅動程序每次批量返回的結果行數和這個設置值相等。
  9. statementType:默認值PREPARED。這會讓mybatis分別使用Statement,PreparedStatement或CallableStatement。
  • 面試官:當實體類中的屬性名和表中的字段名不同,應該怎麼辦?
  • 我:有兩種方法。
  1. 第一種比較簡單粗暴:經過在sql語句中定義別名,強行讓返回的字段名的的別名和實體類中的屬性名一致。
<select id="getByOrderId" parameterType="java.lang.Long" resultType="com.demo.entity.OrderInfo">
select order_id OrderId, order_sn orderSn, total_fee totalFee, create_time createTime
from order_info where order_id=#{orderId}
</select>
複製代碼
  1. 第二種比較優雅:經過resultMap來映射數據表的字段名和實體類的屬性名之間的對應關係。(推薦)
<resultMap id = "BaseResultMap" type="com.demo.entity.OrderInfo">
    <id property="OrderId" column="order_id"/>
    <result property="orderSn" column="order_sn"/>
    <result property="totalFee" column="total_fee"/>
    <result property="createTime" column="create_time"/>
</resultMap>
<select id="getByOrderId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select order_id, order_sn, total_fee, create_time
from order_info where order_id=#{orderId}
</select>

複製代碼
  • 面試官:如何獲取自動生成的主鍵?
  • 我:通常咱們插入數據的話,若是想要知道剛剛插入的數據的主鍵是多少,能夠經過如下方式來獲取。經過LAST_INSERT_ID()獲取剛插入記錄的自增主鍵值,在insert語句執行以後,執行select LAST_INSERT_ID()就能夠獲取自增主鍵。
<insert id='insert' parameterType="com.demo.entity.OrderInfo"
    <selectKey keyProperty="orderId" order="AFTER" resultType="java.lang.Long">
        select LAST_INSERT_ID()
    </selectKey>
    insert into order_info(order_sn,total_fee,create_time)
    values(#{orderSn},#{totalFee},#{createTime)
</insert>
複製代碼
  • 面試官:你知道mybatis的哪些動態sql?
  • 我:
  1. if:作條件判斷的,若是不使用這個標籤,確定要在代碼中作判斷,好比元素是否爲空,字符串是不是空字符串,還好比一些特定的枚舉值須要判斷執行條件。
  2. choose/when/otherwise:這個標籤組合相似於if/else if.../else,就是多個選項中選擇一個,若是都不知足條件,那隻能執行中的內容了。例如
<select id="getStudentListChoose" parameterType="Student" resultMap="BaseResultMap">
    SELECT * from STUDENT WHERE 1=1
    <where>
        <choose>
            <when test="Name!=null and student!='' ">
                AND name LIKE CONCAT(CONCAT('%', #{student}),'%')
            </when>
            <when test="hobby!= null and hobby!= '' ">
                AND hobby = #{hobby}
            </when>
            <otherwise>
                AND AGE = 15
            </otherwise>
        </choose>
    </where>
</select>
複製代碼

3.foreach標籤:用於循環。例如:

<select id="listByOrderIds" resultMap="BaseResultMap">
    select * from order_info where order_id in 
    <foreach collection="list" item="item" open="(" close=")" separator=",">
        #{item}
    </foreach>
</select>
複製代碼

4.另外還有set標籤,where標籤,trim標籤等。

  • 面試官:#{}和${}的區別是什麼?

  • 我:#{}是解析傳進來的參數,而另外一個是拼接參數到SQl中。#{}是預編譯處理,而另外一個是字符串替換。並且#{}能夠防止SQL注入。例如:select * from emp where name=#{empName},參數傳入empName->Smith,解析執行後的SQL是:select * from emp where name=?。可是對於select * from emp where name=${empName},參數傳入empName->Smith,解析執行後的SQL是:select * from emp where name='Smith'。

  • 面試官:在mapper中如何傳遞多個參數?

  • 我:有兩種方法:

  1. 使用佔位符的思想:(1)在映射文件中使用#{0},#{1}表明傳遞進來的第幾個參數。(2)使用@param註解來命名參數(推薦使用) 例如:
//mapper接口
public OrderInfo getByOrderIdAndStatus(Long orderId, String status);

//mapper.xml文件
<select id="getByOrderIdAndStatus" resultMap="BaseResultMap">
    select * from order_info where order_id=#{0} and status=#{1}
</select>
複製代碼
//mapper接口
public OrderInfo getByOrderIdAndStatus(@param("orderId")Long orderId, @param("status")String status);

//mapper.xml文件
<select id="getByOrderIdAndStatus" resultMap="BaseResultMap">
    select * from order_info where order_id=#{orderId} and status=#{status}
</select>
複製代碼

2.使用Map集合做爲參數來裝載

Map<String, Object> map = new HashMap();
map.put("orderId", 1L);
map.put("status", "NORMAL");
OrderInfo orderinfo = getByOrderIdAndStatus(map);

//mapper接口
public OrderInfo getByOrderIdAndStatus(Map<String, Object> map);

//mapper.xml文件
<select id="getByOrderIdAndStatus" parameterType="map" resultMap="BaseResultMap">
    select * from order_info where order_id=#{orderId} and status=#{status}
</select>
複製代碼
  • 面試官:不錯,看來你對mybatis運用的挺熟練的了。今天的面試先到這裏了,下一場面試的時間咱們的hr會通知你的,但願下次你能表現得更好。
  • 我:謝謝,我會的。

往期精彩回顧

今天面試了嗎系列
redis:juejin.im/post/5dccf2…
spring:juejin.im/post/5e6d99…
數據庫系列
mysql索引:juejin.im/post/5d6770…
數據庫鎖:juejin.im/post/5dbbc1…
分庫分表:juejin.im/post/5dc77a…
數據庫事務:juejin.im/post/5dcb9c…
java零零星星系列
juejin.im/post/5d5e26…
juejin.im/post/5d427f…

相關文章
相關標籤/搜索