?昨天剛剛面完 Spring,根據(jù)hr的反饋說面試官對我的整體表現(xiàn)還算滿意铡恕,然后又通知我今天有空去再聊聊有關(guān)的技術(shù)琢感。去的路上,我一直在想没咙,今天會問些什么問題猩谊,JVM?多線程祭刚?還是分布式......真是越想心里越?jīng)]底牌捷。
想著想著就到了墙牌,盡管還是那個熟悉的面試官,但那張年輕有為的面孔絲毫沒有讓我放下緊張的情緒暗甥。
他先開口了:昨天的面試感覺你挺好的喜滨,你說你項目中還用的是mybatis框架作為數(shù)據(jù)庫訪問,那我們今天就來聊聊吧撤防。
面試官:你先說下你對mybatis的整體理解虽风。
我:MyBatis是支持定制化SQL、存儲過程以及高級映射的優(yōu)秀的持久層框架寄月。它避免了幾乎所有JDBC代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集辜膝。MyBatis可以對配置和原生Map使用簡單的XML或注解,將接口和Java的POJO映射成數(shù)據(jù)庫中的記錄漾肮。
面試官:那你們公司為什么選擇Mybatis厂抖,為什么不用Hibernate呢?他兩有什么區(qū)別嗎克懊?
我:mybatis的著力點在于POJO和SQL之間的映射關(guān)系忱辅,然后通過映射配置文件,將SQL所需的參數(shù)谭溉,以及返回的結(jié)果字段映射到指定POJO墙懂。Hibernate的ORM實現(xiàn)了POJO和數(shù)據(jù)庫表之間的映射,以及SQl的自動生成和執(zhí)行扮念,也就是說Hibernate會根據(jù)制定的存儲邏輯损搬,自動生成對應的SQl并調(diào)用JDBC接口加以執(zhí)行。
下面我通過四個方面對比兩者的區(qū)別:
1柜与、開發(fā)對比:
mybatis框架相對簡單容易上手场躯,針對高級查詢,Mybatis需要手動編寫SQL語句旅挤。
Hibernate的真正掌握要比MyBatis難一些,Hibernate有良好的的映射機制伞鲫,開發(fā)者無需關(guān)心SQL的生成與結(jié)果映射粘茄,可以更關(guān)注業(yè)務(wù)流程。
2秕脓、調(diào)優(yōu)方案:
mybatis可以進行詳細的SQL優(yōu)化設(shè)計柒瓣,采用合理的session管理機制。
Hibernate可以指定合理的緩存策略吠架;
盡量采用延遲加載特性芙贫;
采用合理的session管理機制;
采用批量抓取傍药,設(shè)定合理的批處理參數(shù)磺平。
3魂仍、擴展性方面:
mybatis項目中的所有SQL語句都是依賴所用的數(shù)據(jù)庫的,所以不同數(shù)據(jù)庫類型的支持不好拣挪。Hibernate與具體數(shù)據(jù)庫的關(guān)聯(lián)只需在XML文件中配置即可擦酌,所有的HQL語句與具體使用的數(shù)據(jù)庫無關(guān),移植性很好菠劝。
4赊舶、緩存機制:
mybatis默認情況下沒開啟緩存;要開啟二級緩存赶诊,需要在sql映射文件中加上笼平;映射文件中的所有select語句將會緩存,映射文件中的所有insert/update/delete會刷新緩存舔痪;
緩存會使用LRU(最近最少使用)算法來回收寓调;
緩存會存儲列表集合會對象的1024個引用;
緩存會被視為read/write(可讀可寫)緩存辙喂,意味著對象檢索不是共享的捶牢,而且可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改巍耗。
Hibernate的一級緩存是Session緩存秋麸,利用好一級緩存就需要對Session的生命周期進行管理好;二級緩存是SessionFactory級的緩存炬太,分為內(nèi)置緩存和外置緩存灸蟆。
另外,有種說法亲族,mybatis是半自動ORM映射工具炒考,Hibernate是全自動的。這主要就是因為使用Hibernate查詢關(guān)聯(lián)對象或集合對象時霎迫,可以根據(jù)對象關(guān)系模型調(diào)用api接口直接獲取斋枢。而Mybatis在查詢關(guān)聯(lián)對象或集合對象時,需要手動編寫sql來完成知给,所以叫做半自動瓤帚。
至于我們公司為什么選擇半自動的mybatis,主要是因為我們的業(yè)務(wù)經(jīng)常需要編寫復雜的sql涩赢,比如動態(tài)的sql戈次。還有這種更便于我們使用索引來優(yōu)化sql語句。
面試官:你先說下JDBC的執(zhí)行流程吧
我:
1筒扒、加載JDBC驅(qū)動
2怯邪、建立并獲取數(shù)據(jù)庫連接
3、創(chuàng)建JDBC Statements對象
4花墩、設(shè)置SQL語句的傳入?yún)?shù)
5悬秉、執(zhí)行SQL語句并獲得查詢結(jié)果
6澄步、對查詢結(jié)果進行轉(zhuǎn)換處理并將處理結(jié)果返回
7、釋放相關(guān)資源(關(guān)閉Connection,關(guān)閉Statement,關(guān)閉ResultSet)
面試官:那你能說下mybatis執(zhí)行SQL的流程嗎搂捧?
我:好的驮俗。
1、加載配置并初始化:
加載配置文件允跑,將SQl配置信息加載成為一個個MappedStatement對象(包括傳入?yún)?shù)映射配置王凑,執(zhí)行的sql語句,結(jié)果映射的配置)聋丝,存儲在內(nèi)存中索烹。
2、傳遞調(diào)用請求:
調(diào)用Mybatis提供的API弱睦,傳入SQL的ID和參數(shù)對象百姓,將請求傳遞給下層的請求處理層進行處理。
3况木、處理請求:
根據(jù)SQL的ID查找到對應的MappedStatement對象垒拢;
根據(jù)傳入的參數(shù)對象解析MappedStatement對象,得到最終要執(zhí)行的SQL和執(zhí)行參數(shù)火惊;
獲取數(shù)據(jù)庫連接求类,根據(jù)得到的SQL語句和執(zhí)行參數(shù)到數(shù)據(jù)庫中執(zhí)行,并得到執(zhí)行結(jié)果屹耐;
根據(jù)MappedStatement對象中的結(jié)果映射配置對得到的執(zhí)行結(jié)果進行轉(zhuǎn)換處理尸疆,得到最終的處理結(jié)果;
釋放連接資源惶岭;
將最終的結(jié)果返回寿弱。
總之,這個過程就是:
加載配置->SQL解析->SQL執(zhí)行->結(jié)果映射->釋放連接
面試官:很好按灶。你剛說到初始化症革,你對mybatis初始化了解嗎?
我:可以這么說鸯旁,Myabtis初始化的過程就是創(chuàng)建Configuration對象的過程地沮。
過程也很簡單:
1、加載配置文件mybatis-config.xml到Mybatis內(nèi)部羡亩。
2、使用Configuration對象作為一個所有配置信息的容器危融,這個對象的組織結(jié)構(gòu)和XML配置文件的組織結(jié)構(gòu)幾乎完全一樣畏铆,這樣配置文件的信息就可以存到這個對象中,訪問起來很方便吉殃。
面試官:那我問的再深入一點辞居,你看過mybatis的源碼嗎楷怒?
我:沒看過。瓦灶。關(guān)鍵的類還是知道一點的鸠删。
面試官:哦,那你說下你了解的mybatis的有哪些核心的類贼陶?
我:(心想:既然面試前準備了刃泡,還是要說的,不然怎么顯得自己nb一些)
第一個是SqlSessionFactoryBuilder:
通過類名就看出來這個類的主要作用是創(chuàng)建一個SqlSessionFactory碉怔。
可以重用這個類來創(chuàng)建多個SqlSessionFactory實例烘贴,但是最好不要讓其一直存在以保證所有的XML解析資源開放給更重要的事情。
這個類可以被實例化撮胧、使用和丟棄桨踪,一旦創(chuàng)建了SqlSessionFactory,就不再需要它了芹啥。
第二個是SqlSessionFactory接口:
它的作用就是sql會話工廠锻离,用于創(chuàng)建SqlSession。
SqlSessionFactory一旦被創(chuàng)建就應該在應用的運行期間一直存在墓怀,它的最佳作用域是應用作用域汽纠。
第三個是很重要的SqlSession接口:
他是mybatis的一個重要接口,定義了數(shù)據(jù)庫的增刪改查以及事務(wù)管理的常用方法捺疼。
SqlSession還提供了查找Mapper接口的有關(guān)方法疏虫。
每個線程都應該有自己的SqlSession實例,因為這個實例不是線程安全的啤呼,所以它的最佳作用域是請求或方法作用域卧秘。
每次收到一個HTTP請求,就可以打開一個SqlSession官扣,返回了響應之后就關(guān)閉它翅敌。
第四個就是我們編碼的主角Mapper接口:
Mapper接口是指程序員自行定義的一個數(shù)據(jù)操縱接口,類似于通常所說的DAO接口惕蹄。
跟DAO接口不同的地方在于Mapper接口只需要定義不需要實現(xiàn),mybatis會自動為Mapper接口創(chuàng)建動態(tài)代理對象蚯涮。
Mapper接口的方法通常與Mapper配置文件中的select、insert卖陵、update和delete等XML節(jié)點存在一一對應關(guān)系遭顶。
面試官:那你能說下mybatis源碼中的主要部件嗎?
我:好的泪蔫,主要部件如下:
1棒旗、SqlSession:作為mybatis工作的主要頂層API,表示和數(shù)據(jù)庫交互的會話撩荣,完成必要的數(shù)據(jù)庫增刪改查功能铣揉。
2饶深、Executor:mybatis執(zhí)行器,是Mybatis調(diào)度的核心逛拱,負責SQL語句的生成和查詢緩存的維護敌厘。
3、StatementHandler:封裝了JDBCStatement操作朽合,負責對JDBC Statement的操作俱两。
4、ParameterHandler:負責對用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement所需要的參數(shù)旁舰。
5锋华、ResultSetHandler:負責將JDBC返回的ResultSet結(jié)果集轉(zhuǎn)換成List類型的集合。
6箭窜、TypeHandler:負責Java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換毯焕。
7、MappedStatement:維護了一條select/update/delete/insert節(jié)點的封裝磺樱。
8纳猫、Sqlsource:負責根據(jù)用戶傳遞的parameterObject,動態(tài)生成SQL語句竹捉,將信息封裝在BoundSql對象中芜辕,并返回。
9块差、BoundSql:表示動態(tài)生成的SQL語句以及相應的參數(shù)信息侵续。
10、Configuration:Mybatis所有的配置信息都維護在這個對象中憨闰。
面試官:原理聊完了状蜗,接下來我們聊下實戰(zhàn)吧。你在項目中是怎么整合spring和mybatis的鹉动?
我:我先說下xml的配置方式吧轧坎。
1、添加mybatis-spring的包:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>x.x.x</version>
</dependency>
2泽示、配置SqlSessionFactory:
整合后缸血,可以不需要單獨的mybatis配置文件,全部的配置內(nèi)容可以再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>
datasource:是數(shù)據(jù)源配置,常用的有DBCP,C3P0,Druid等渔呵。
mapperLocations:是指接口xml的文件配置,如果不配置的話映射接口類文件(mapper接口)和映射xml文件(mapper.xml)需要放在相同的包下砍鸠。
3扩氢、配置數(shù)據(jù)映射器類:
利用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>
我:(接著說)現(xiàn)在好像大多使用的是注解配置mybatis和數(shù)據(jù)源的方式,也就是使用java代碼和spring提供的注解爷辱。(其實步驟大致差不多录豺,由于涉及安全問題代碼不透露,想學習的可以網(wǎng)上找饭弓。)
面試官:你能寫一個mapper映射文件中select的sql語句嗎双饥?
我:隨手寫了一個,接著解釋到:這個語句被稱作selectPerson弟断,接收一個int類型的參數(shù)咏花,并返回一個HashMap類型的對象,其中的鍵是列名阀趴,值便是結(jié)果行中的對應值昏翰。
<select id="selectPerson" parameterType = "int" resultType="hashmap"
select * from person where id =#{id}
</select>
我(接著說):select中有這些屬性可選:
id:必選的,命名空間中唯一的標識符刘急,可以被用來引用這條語句棚菊。
parameterType:可選,將會傳入這條語句的參數(shù)類的完全限定名或別名叔汁。
resultType:從這條語句返回的期望類型的類的完全限定名或別名统求。
如果是集合,那應該是集合包含的類型据块,而不是集合本身码邻。
resultMap:外部resultMap的命名引用。注意使用resultType或resultMap瑰钮,不能同時使用冒滩。
flushCache:默認false。設(shè)置為true表示只要語句被調(diào)用浪谴,都會導致本地緩存和二級緩存被清空开睡。
useCache:對select元素為true。設(shè)置為true會導致本條語句的結(jié)果被二級緩存苟耻。
timeout:默認值為unset(依賴驅(qū)動)篇恒。這個設(shè)置是在拋出異常之前,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)凶杖。
fetchSize:默認值為unset(依賴驅(qū)動)胁艰。這是嘗試影響驅(qū)動程序每次批量返回的結(jié)果行數(shù)和這個設(shè)置值相等。
statementType:默認值PREPARED。這會讓mybatis分別使用Statement腾么,PreparedStatement或CallableStatement奈梳。
面試官:當實體類中的屬性名和表中的字段名不一樣,應該怎么辦解虱?
我:有兩種方法攘须。
第一種比較簡單粗暴:通過在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>
第二種比較優(yōu)雅:
通過resultMap來映射數(shù)據(jù)表的字段名和實體類的屬性名之間的對應關(guān)系于宙。
(推薦)
<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>
面試官:如何獲取自動生成的主鍵?
我:一般我們插入數(shù)據(jù)的話悍汛,如果想要知道剛剛插入的數(shù)據(jù)的主鍵是多少捞魁,可以通過以下方式來獲取。
通過LAST_INSERT_ID()獲取剛插入記錄的自增主鍵值离咐,在insert語句執(zhí)行之后谱俭,執(zhí)行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>
面試官:你知道m(xù)ybatis的哪些動態(tài)sql健霹?
我:
if:做條件判斷的旺上,如果不使用這個標簽,肯定要在代碼中做判斷糖埋,比如元素是否為空宣吱,字符串是否是空字符串,還比如一些特定的枚舉值需要判斷執(zhí)行條件瞳别。
choose/when/otherwise:這個標簽組合類似于if/else if.../else征候,就是多個選項中選擇一個,如果都不滿足條件祟敛,那只能執(zhí)行中的內(nèi)容了疤坝。
例如:
<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標簽:用于循環(huán)。例如:
<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標簽等。
面試官:#{}和${}的區(qū)別是什么埠巨?
我:#{}是解析傳進來的參數(shù)历谍,而另一個是拼接參數(shù)到SQl中。#{}是預編譯處理辣垒,而另一個是字符串替換望侈。而且#{}可以防止SQL注入。
例如:select * from emp where name=#{empName}勋桶,參數(shù)傳入empName->Smith脱衙,解析執(zhí)行后的SQL是:select * from emp where name=?侥猬。但是對于select * from emp where name=${empName},參數(shù)傳入empName->Smith捐韩,解析執(zhí)行后的SQL是:select * from emp where name='Smith'退唠。
面試官:在mapper中如何傳遞多個參數(shù)?
我:有兩種方法:
1荤胁、使用占位符的思想:
(1)在映射文件中使用#{0},#{1}代表傳遞進來的第幾個參數(shù)铜邮。
(2)使用@param注解來命名參數(shù)(推薦使用) 例如:
//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集合作為參數(shù)來裝載
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運用的挺熟練的了。今天的面試先到這里了扔茅,回家等消息吧已旧。
我:好的。
心里有點忐忑召娜,你們覺得我面試能過嗎运褪?
文源網(wǎng)絡(luò),僅供學習之用玖瘸,如有侵權(quán)秸讹,聯(lián)系刪除。
我將優(yōu)質(zhì)的技術(shù)文章和經(jīng)驗總結(jié)都匯集在了我的公眾號【Java圈子】里雅倒。
為方便大家學習璃诀,我整理了一套學習資料,涵蓋Java虛擬機蔑匣、spring框架劣欢、Java線程、數(shù)據(jù)結(jié)構(gòu)裁良、設(shè)計模式等等凿将,免費提供給熱愛Java的同學!
file