?昨天剛剛面完 Spring,根據(jù)hr的反饋說(shuō)面試官對(duì)我的整體表現(xiàn)還算滿意,然后又通知我今天有空去再聊聊有關(guān)的技術(shù)。去的路上,我一直在想,今天會(huì)問(wèn)些什么問(wèn)題,JVM?多線程?還是分布式......真是越想心里越?jīng)]底。
想著想著就到了,盡管還是那個(gè)熟悉的面試官,但那張年輕有為的面孔絲毫沒(méi)有讓我放下緊張的情緒。
他先開(kāi)口了:昨天的面試感覺(jué)你挺好的,你說(shuō)你項(xiàng)目中還用的是mybatis框架作為數(shù)據(jù)庫(kù)訪問(wèn),那我們今天就來(lái)聊聊吧。
面試官:你先說(shuō)下你對(duì)mybatis的整體理解。
我:MyBatis是支持定制化SQL、存儲(chǔ)過(guò)程以及高級(jí)映射的優(yōu)秀的持久層框架。它避免了幾乎所有JDBC代碼和手動(dòng)設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis可以對(duì)配置和原生Map使用簡(jiǎn)單的XML或注解,將接口和Java的POJO映射成數(shù)據(jù)庫(kù)中的記錄。
面試官:那你們公司為什么選擇Mybatis,為什么不用Hibernate呢?他兩有什么區(qū)別嗎?
我:mybatis的著力點(diǎn)在于POJO和SQL之間的映射關(guān)系,然后通過(guò)映射配置文件,將SQL所需的參數(shù),以及返回的結(jié)果字段映射到指定POJO。Hibernate的ORM實(shí)現(xiàn)了POJO和數(shù)據(jù)庫(kù)表之間的映射,以及SQl的自動(dòng)生成和執(zhí)行,也就是說(shuō)Hibernate會(huì)根據(jù)制定的存儲(chǔ)邏輯,自動(dòng)生成對(duì)應(yīng)的SQl并調(diào)用JDBC接口加以執(zhí)行。
下面我通過(guò)四個(gè)方面對(duì)比兩者的區(qū)別:
1、開(kāi)發(fā)對(duì)比:
mybatis框架相對(duì)簡(jiǎn)單容易上手,針對(duì)高級(jí)查詢,Mybatis需要手動(dòng)編寫(xiě)SQL語(yǔ)句。
Hibernate的真正掌握要比MyBatis難一些,Hibernate有良好的的映射機(jī)制,開(kāi)發(fā)者無(wú)需關(guān)心SQL的生成與結(jié)果映射,可以更關(guān)注業(yè)務(wù)流程。
2、調(diào)優(yōu)方案:
mybatis可以進(jìn)行詳細(xì)的SQL優(yōu)化設(shè)計(jì),采用合理的session管理機(jī)制。
Hibernate可以指定合理的緩存策略;
盡量采用延遲加載特性;
采用合理的session管理機(jī)制;
采用批量抓取,設(shè)定合理的批處理參數(shù)。
3、擴(kuò)展性方面:
mybatis項(xiàng)目中的所有SQL語(yǔ)句都是依賴所用的數(shù)據(jù)庫(kù)的,所以不同數(shù)據(jù)庫(kù)類型的支持不好。Hibernate與具體數(shù)據(jù)庫(kù)的關(guān)聯(lián)只需在XML文件中配置即可,所有的HQL語(yǔ)句與具體使用的數(shù)據(jù)庫(kù)無(wú)關(guān),移植性很好。
4、緩存機(jī)制:
mybatis默認(rèn)情況下沒(méi)開(kāi)啟緩存;要開(kāi)啟二級(jí)緩存,需要在sql映射文件中加上;映射文件中的所有select語(yǔ)句將會(huì)緩存,映射文件中的所有insert/update/delete會(huì)刷新緩存;
緩存會(huì)使用LRU(最近最少使用)算法來(lái)回收;
緩存會(huì)存儲(chǔ)列表集合會(huì)對(duì)象的1024個(gè)引用;
緩存會(huì)被視為read/write(可讀可寫(xiě))緩存,意味著對(duì)象檢索不是共享的,而且可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。
Hibernate的一級(jí)緩存是Session緩存,利用好一級(jí)緩存就需要對(duì)Session的生命周期進(jìn)行管理好;二級(jí)緩存是SessionFactory級(jí)的緩存,分為內(nèi)置緩存和外置緩存。
另外,有種說(shuō)法,mybatis是半自動(dòng)ORM映射工具,Hibernate是全自動(dòng)的。這主要就是因?yàn)槭褂肏ibernate查詢關(guān)聯(lián)對(duì)象或集合對(duì)象時(shí),可以根據(jù)對(duì)象關(guān)系模型調(diào)用api接口直接獲取。而Mybatis在查詢關(guān)聯(lián)對(duì)象或集合對(duì)象時(shí),需要手動(dòng)編寫(xiě)sql來(lái)完成,所以叫做半自動(dòng)。
至于我們公司為什么選擇半自動(dòng)的mybatis,主要是因?yàn)槲覀兊臉I(yè)務(wù)經(jīng)常需要編寫(xiě)復(fù)雜的sql,比如動(dòng)態(tài)的sql。還有這種更便于我們使用索引來(lái)優(yōu)化sql語(yǔ)句。
面試官:你先說(shuō)下JDBC的執(zhí)行流程吧
我:
1、加載JDBC驅(qū)動(dòng)
2、建立并獲取數(shù)據(jù)庫(kù)連接
3、創(chuàng)建JDBC Statements對(duì)象
4、設(shè)置SQL語(yǔ)句的傳入?yún)?shù)
5、執(zhí)行SQL語(yǔ)句并獲得查詢結(jié)果
6、對(duì)查詢結(jié)果進(jìn)行轉(zhuǎn)換處理并將處理結(jié)果返回
7、釋放相關(guān)資源(關(guān)閉Connection,關(guān)閉Statement,關(guān)閉ResultSet)
面試官:那你能說(shuō)下mybatis執(zhí)行SQL的流程嗎?
我:好的。
1、加載配置并初始化:
加載配置文件,將SQl配置信息加載成為一個(gè)個(gè)MappedStatement對(duì)象(包括傳入?yún)?shù)映射配置,執(zhí)行的sql語(yǔ)句,結(jié)果映射的配置),存儲(chǔ)在內(nèi)存中。
2、傳遞調(diào)用請(qǐng)求:
調(diào)用Mybatis提供的API,傳入SQL的ID和參數(shù)對(duì)象,將請(qǐng)求傳遞給下層的請(qǐng)求處理層進(jìn)行處理。
3、處理請(qǐng)求:
根據(jù)SQL的ID查找到對(duì)應(yīng)的MappedStatement對(duì)象;
根據(jù)傳入的參數(shù)對(duì)象解析MappedStatement對(duì)象,得到最終要執(zhí)行的SQL和執(zhí)行參數(shù);
獲取數(shù)據(jù)庫(kù)連接,根據(jù)得到的SQL語(yǔ)句和執(zhí)行參數(shù)到數(shù)據(jù)庫(kù)中執(zhí)行,并得到執(zhí)行結(jié)果;
根據(jù)MappedStatement對(duì)象中的結(jié)果映射配置對(duì)得到的執(zhí)行結(jié)果進(jìn)行轉(zhuǎn)換處理,得到最終的處理結(jié)果;
釋放連接資源;
將最終的結(jié)果返回。
總之,這個(gè)過(guò)程就是:
加載配置->SQL解析->SQL執(zhí)行->結(jié)果映射->釋放連接
面試官:很好。你剛說(shuō)到初始化,你對(duì)mybatis初始化了解嗎?
我:可以這么說(shuō),Myabtis初始化的過(guò)程就是創(chuàng)建Configuration對(duì)象的過(guò)程。
過(guò)程也很簡(jiǎn)單:
1、加載配置文件mybatis-config.xml到Mybatis內(nèi)部。
2、使用Configuration對(duì)象作為一個(gè)所有配置信息的容器,這個(gè)對(duì)象的組織結(jié)構(gòu)和XML配置文件的組織結(jié)構(gòu)幾乎完全一樣,這樣配置文件的信息就可以存到這個(gè)對(duì)象中,訪問(wèn)起來(lái)很方便。
面試官:那我問(wèn)的再深入一點(diǎn),你看過(guò)mybatis的源碼嗎?
我:沒(méi)看過(guò)。。關(guān)鍵的類還是知道一點(diǎn)的。
面試官:哦,那你說(shuō)下你了解的mybatis的有哪些核心的類?
我:(心想:既然面試前準(zhǔn)備了,還是要說(shuō)的,不然怎么顯得自己nb一些)
第一個(gè)是SqlSessionFactoryBuilder:
通過(guò)類名就看出來(lái)這個(gè)類的主要作用是創(chuàng)建一個(gè)SqlSessionFactory。
可以重用這個(gè)類來(lái)創(chuàng)建多個(gè)SqlSessionFactory實(shí)例,但是最好不要讓其一直存在以保證所有的XML解析資源開(kāi)放給更重要的事情。
這個(gè)類可以被實(shí)例化、使用和丟棄,一旦創(chuàng)建了SqlSessionFactory,就不再需要它了。
第二個(gè)是SqlSessionFactory接口:
它的作用就是sql會(huì)話工廠,用于創(chuàng)建SqlSession。
SqlSessionFactory一旦被創(chuàng)建就應(yīng)該在應(yīng)用的運(yùn)行期間一直存在,它的最佳作用域是應(yīng)用作用域。
第三個(gè)是很重要的SqlSession接口:
他是mybatis的一個(gè)重要接口,定義了數(shù)據(jù)庫(kù)的增刪改查以及事務(wù)管理的常用方法。
SqlSession還提供了查找Mapper接口的有關(guān)方法。
每個(gè)線程都應(yīng)該有自己的SqlSession實(shí)例,因?yàn)檫@個(gè)實(shí)例不是線程安全的,所以它的最佳作用域是請(qǐng)求或方法作用域。
每次收到一個(gè)HTTP請(qǐng)求,就可以打開(kāi)一個(gè)SqlSession,返回了響應(yīng)之后就關(guān)閉它。
第四個(gè)就是我們編碼的主角Mapper接口:
Mapper接口是指程序員自行定義的一個(gè)數(shù)據(jù)操縱接口,類似于通常所說(shuō)的DAO接口。
跟DAO接口不同的地方在于Mapper接口只需要定義不需要實(shí)現(xiàn),mybatis會(huì)自動(dòng)為Mapper接口創(chuàng)建動(dòng)態(tài)代理對(duì)象。
Mapper接口的方法通常與Mapper配置文件中的select、insert、update和delete等XML節(jié)點(diǎn)存在一一對(duì)應(yīng)關(guān)系。
面試官:那你能說(shuō)下mybatis源碼中的主要部件嗎?
我:好的,主要部件如下:
1、SqlSession:作為mybatis工作的主要頂層API,表示和數(shù)據(jù)庫(kù)交互的會(huì)話,完成必要的數(shù)據(jù)庫(kù)增刪改查功能。
2、Executor:mybatis執(zhí)行器,是Mybatis調(diào)度的核心,負(fù)責(zé)SQL語(yǔ)句的生成和查詢緩存的維護(hù)。
3、StatementHandler:封裝了JDBCStatement操作,負(fù)責(zé)對(duì)JDBC Statement的操作。
4、ParameterHandler:負(fù)責(zé)對(duì)用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement所需要的參數(shù)。
5、ResultSetHandler:負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集轉(zhuǎn)換成List類型的集合。
6、TypeHandler:負(fù)責(zé)Java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換。
7、MappedStatement:維護(hù)了一條select/update/delete/insert節(jié)點(diǎn)的封裝。
8、Sqlsource:負(fù)責(zé)根據(jù)用戶傳遞的parameterObject,動(dòng)態(tài)生成SQL語(yǔ)句,將信息封裝在BoundSql對(duì)象中,并返回。
9、BoundSql:表示動(dòng)態(tài)生成的SQL語(yǔ)句以及相應(yīng)的參數(shù)信息。
10、Configuration:Mybatis所有的配置信息都維護(hù)在這個(gè)對(duì)象中。
面試官:原理聊完了,接下來(lái)我們聊下實(shí)戰(zhàn)吧。你在項(xiàng)目中是怎么整合spring和mybatis的?
我:我先說(shuō)下xml的配置方式吧。
1、添加mybatis-spring的包:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>x.x.x</version>
</dependency>
2、配置SqlSessionFactory:
整合后,可以不需要單獨(dú)的mybatis配置文件,全部的配置內(nèi)容可以再spring的上下文中。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 當(dāng)mybatis的xml文件和mapper接口不在相同包下時(shí),需要用mapperLocations屬性指定xml文件的路徑。
*是個(gè)通配符,代表所有的文件,**代表所有目錄下 -->
<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提供的自動(dòng)掃描機(jī)制:
<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">
<!-- 自動(dòng)掃描 -->
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
?
</beans>
我:(接著說(shuō))現(xiàn)在好像大多使用的是注解配置mybatis和數(shù)據(jù)源的方式,也就是使用java代碼和spring提供的注解。(其實(shí)步驟大致差不多,由于涉及安全問(wèn)題代碼不透露,想學(xué)習(xí)的可以網(wǎng)上找。)
面試官:你能寫(xiě)一個(gè)mapper映射文件中select的sql語(yǔ)句嗎?
我:隨手寫(xiě)了一個(gè),接著解釋到:這個(gè)語(yǔ)句被稱作selectPerson,接收一個(gè)int類型的參數(shù),并返回一個(gè)HashMap類型的對(duì)象,其中的鍵是列名,值便是結(jié)果行中的對(duì)應(yīng)值。
<select id="selectPerson" parameterType = "int" resultType="hashmap"
select * from person where id =#{id}
</select>
我(接著說(shuō)):select中有這些屬性可選:
id:必選的,命名空間中唯一的標(biāo)識(shí)符,可以被用來(lái)引用這條語(yǔ)句。
parameterType:可選,將會(huì)傳入這條語(yǔ)句的參數(shù)類的完全限定名或別名。
resultType:從這條語(yǔ)句返回的期望類型的類的完全限定名或別名。
如果是集合,那應(yīng)該是集合包含的類型,而不是集合本身。
resultMap:外部resultMap的命名引用。注意使用resultType或resultMap,不能同時(shí)使用。
flushCache:默認(rèn)false。設(shè)置為true表示只要語(yǔ)句被調(diào)用,都會(huì)導(dǎo)致本地緩存和二級(jí)緩存被清空。
useCache:對(duì)select元素為true。設(shè)置為true會(huì)導(dǎo)致本條語(yǔ)句的結(jié)果被二級(jí)緩存。
timeout:默認(rèn)值為unset(依賴驅(qū)動(dòng))。這個(gè)設(shè)置是在拋出異常之前,驅(qū)動(dòng)程序等待數(shù)據(jù)庫(kù)返回請(qǐng)求結(jié)果的秒數(shù)。
fetchSize:默認(rèn)值為unset(依賴驅(qū)動(dòng))。這是嘗試影響驅(qū)動(dòng)程序每次批量返回的結(jié)果行數(shù)和這個(gè)設(shè)置值相等。
statementType:默認(rèn)值PREPARED。這會(huì)讓mybatis分別使用Statement,PreparedStatement或CallableStatement。
面試官:當(dāng)實(shí)體類中的屬性名和表中的字段名不一樣,應(yīng)該怎么辦?
我:有兩種方法。
第一種比較簡(jiǎn)單粗暴:通過(guò)在sql語(yǔ)句中定義別名,強(qiáng)行讓返回的字段名的的別名和實(shí)體類中的屬性名一致。
<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)雅:
通過(guò)resultMap來(lái)映射數(shù)據(jù)表的字段名和實(shí)體類的屬性名之間的對(duì)應(yīng)關(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>
面試官:如何獲取自動(dòng)生成的主鍵?
我:一般我們插入數(shù)據(jù)的話,如果想要知道剛剛插入的數(shù)據(jù)的主鍵是多少,可以通過(guò)以下方式來(lái)獲取。
通過(guò)LAST_INSERT_ID()獲取剛插入記錄的自增主鍵值,在insert語(yǔ)句執(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的哪些動(dòng)態(tài)sql?
我:
if:做條件判斷的,如果不使用這個(gè)標(biāo)簽,肯定要在代碼中做判斷,比如元素是否為空,字符串是否是空字符串,還比如一些特定的枚舉值需要判斷執(zhí)行條件。
choose/when/otherwise:這個(gè)標(biāo)簽組合類似于if/else if.../else,就是多個(gè)選項(xiàng)中選擇一個(gè),如果都不滿足條件,那只能執(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標(biāo)簽:用于循環(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標(biāo)簽,where標(biāo)簽,trim標(biāo)簽等。
面試官:#{}和${}的區(qū)別是什么?
我:#{}是解析傳進(jìn)來(lái)的參數(shù),而另一個(gè)是拼接參數(shù)到SQl中。#{}是預(yù)編譯處理,而另一個(gè)是字符串替換。而且#{}可以防止SQL注入。
例如:select * from emp where name=#{empName},參數(shù)傳入empName->Smith,解析執(zhí)行后的SQL是:select * from emp where name=?。但是對(duì)于select * from emp where name=${empName},參數(shù)傳入empName->Smith,解析執(zhí)行后的SQL是:select * from emp where name='Smith'。
面試官:在mapper中如何傳遞多個(gè)參數(shù)?
我:有兩種方法:
1、使用占位符的思想:
(1)在映射文件中使用#{0},#{1}代表傳遞進(jìn)來(lái)的第幾個(gè)參數(shù)。
(2)使用@param注解來(lái)命名參數(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ù)來(lái)裝載
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>
面試官:不錯(cuò),看來(lái)你對(duì)mybatis運(yùn)用的挺熟練的了。今天的面試先到這里了,回家等消息吧。
我:好的。
心里有點(diǎn)忐忑,你們覺(jué)得我面試能過(guò)嗎?
文源網(wǎng)絡(luò),僅供學(xué)習(xí)之用,如有侵權(quán),聯(lián)系刪除。
我將優(yōu)質(zhì)的技術(shù)文章和經(jīng)驗(yàn)總結(jié)都匯集在了我的公眾號(hào)【Java圈子】里。
為方便大家學(xué)習(xí),我整理了一套學(xué)習(xí)資料,涵蓋Java虛擬機(jī)、spring框架、Java線程、數(shù)據(jù)結(jié)構(gòu)、設(shè)計(jì)模式等等,免費(fèi)提供給熱愛(ài)Java的同學(xué)!
file