1. 程式人生 > 其它 >框架篇(一)Mybatis基礎面試題

框架篇(一)Mybatis基礎面試題

Mybatis面試題

相關文件:

https://mybatis.org/mybatis-3/zh/configuration.html

bearbrick0/mybatis: mybatis原始碼中文註釋 (github.com)

dynamic-sql.xml — mybatis/mybatis-3 — GitHub1s

Mybatis框架入門教程 (biancheng.net)一個很不錯的學習網站!可以練手!

1. 什麼是Mybatis

  1. Mybatis是一個半ORM(物件關係對映)框架,它內部封裝了JDBC,載入驅動、建立連線、建立statement等繁雜的過程,開發者開發時只需要關注如何編寫SQL語句,可以嚴格控制sql

    執行效能,靈活度高。

  2. 作為一個半ORM框架,MyBatis 可以使用 XML 或註解來配置和對映原生資訊,將 POJO對映成資料庫中的記錄,避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。

  3. 通過xml 檔案或註解的方式將要執行的各種 statement 配置起來,並通過java物件和 statementsql的動態引數進行對映生成最終執行的sql語句,最後由mybatis框架執行sql並將結果對映為java物件並返回。(從執行sql到返回result的過程)。

  4. 由於MyBatis專注於SQL本身,靈活度高,所以比較適合對效能的要求很高,或者需求變化較多的專案,如網際網路專案。

優點:

  1. 基於SQL語句程式設計,相當靈活,不會對應用程式或者資料庫的現有設計造成任何影響,SQL寫在XML裡,解除sql與程式程式碼的耦合,便於統一管理;提供XML標籤,支援編寫動態SQL語句,並可重用。

  2. 與JDBC相比,減少了50%以上的程式碼量,消除了JDBC大量冗餘的程式碼,不需要手動開關連線;

  3. 很好的與各種資料庫相容(因為MyBatis使用JDBC來連線資料庫,所以只要JDBC支援的資料庫MyBatis都支援)。

  4. 能夠與Spring很好的整合;

  5. 提供對映標籤,支援物件與資料庫的ORM欄位關係對映;提供物件關係對映標籤,支援物件關係元件維護。

缺點:

  1. SQL語句的編寫工作量較大,尤其當欄位多、關聯表多時,對開發人員編寫SQL語句的功底有一定要求。

  2. SQL語句依賴於資料庫,導致資料庫移植性差,不能隨意更換資料庫。

2. 簡述Mybatis的工作原理

  1. 讀取Mybatis的配置檔案mybatis-config.xml 和資料庫建立連線。mybatis-config.xml為Mybatis的全域性配置檔案,配置了,Mybatis執行環境等資訊,例如資料庫連線資訊。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--配置-->
<configuration>
    <!-- 引入外部配置檔案-->
    <properties resource="database.properties"/>

    <!--配置日誌-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--        <setting name="logImpl" value="LOG4J"/>-->
<!--        <setting name="LazyLoadingEnabled" value=""></setting>-->
    </settings>
    <!--類型別名-->
    <typeAliases>
        <typeAlias alias="User" type="com.uin.pojo.User"/>
        <typeAlias alias="Role" type="com.uin.pojo.Role"/>
        <typeAlias alias="Provider" type="com.uin.pojo.Provider"/>
        <typeAlias alias="Bill" type="com.uin.pojo.Bill"/>
        <typeAlias alias="Address" type="com.uin.pojo.Address"/>
    </typeAliases>
    <!--配置mybatis的執行環境-->
    <environments default="development">
        <environment id="development">
            <!--使用JDBC的事務管理-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- MySQL資料庫驅動 -->
                <property name="driver" value="${driver}"/>
                <!-- 連線資料庫的URL -->
                <property name="url" value="${url}"/>
                <!--username-->
                <property name="username" value="${username}"/>
                <!--password-->
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 將mapper檔案加入到配置檔案中 -->
    <mappers>
        <mapper resource="com/uin/mapper/UserMapper.xml"/>
        <mapper resource="com/uin/mapper/AddressMapper.xml"/>
        <mapper resource="com/uin/mapper/BillMapper.xml"/>
        <mapper resource="com/uin/mapper/ProviderMapper.xml"/>
        <mapper resource="com/uin/mapper/RoleMapper.xml"/>
    </mappers>
</configuration>
  1. 載入SQL對映檔案。對映檔案就是SQL對映檔案,該檔案中儲存了操作資料庫的SQL語句,需要在Mybatis配置檔案mybatis-config.xml中配置載入。mybatis-config.xml檔案可以載入多個對映檔案,每個對映檔案對應資料庫中的一張表。

  2. 構造會話工廠。通過Mybatis的環境等配置資訊構建會話工廠SqlSessionFactory

  3. 建立會話物件。通過會話工廠建立一個SqlSession物件,該物件中包含了執行sql的所有方法。

  4. Executor執行器。Mybatis底層定義了一個Executor介面來操作資料庫。它將根據SqlSession傳遞的引數動態的生成需要執行的sql語句,同時負責查詢快取的維護。

  5. MappedStatement物件:在Executor介面的執行方法中有一個MappedStatement型別的引數,該引數是對對映資訊的封裝,用於儲存要對映的SQL語句的id、引數等資訊。

  6. 輸入引數對映:輸入引數的型別可以是MapList等集合型別,也可以是基本資料型別和POJO型別。輸入引數對映的過程類似於JDBC對PrepareStatement物件設定引數的過程。

  7. 輸出結果的對映。輸出結果型別可以是MapList等集合型別,也可以是基本資料型別和POJO型別。輸出結果就類似於JDBC對結果集解析過程。

3. #{}和${}的區別是什麼?

${ }是拼接符,字串替換,#{ }是佔位符,預編譯處理;

Mybatis在處理${}時,就是把${}直接替換成變數的值。

而Mybatis在處理#{}時,會對sql語句進行預處理,將sql中的#{}替換為?號,呼叫PreparedStatementset方法來賦值;

使用#{}可以有效的防止SQL注入,提高系統安全性。

--Mybatis在處理#{}時
select id,name,age from student where id =#{id}
當前端把id值1傳入到後臺的時候,就相當於:
select id,name,age from student where id ='1'

--Mybatis在處理${}時
select id,name,age from student where id =${id}
當前端把id值1傳入到後臺的時候,就相當於:
select id,name,age from student where id = 1

4. Mybatis是否支援延遲載入?如果支援,它的實現原理是什麼?

  1. Mybatis僅支援association關聯物件和collection關聯集合物件的延遲載入,association指的就是一對一,collection指的就是一對多查詢。

    在Mybatis配置檔案中,可以配置是否啟用延遲載入lazyLoadingEnabled=true|false。

  2. 它的原理是,使用CGLIB建立目標物件的代理物件,當呼叫目標方法時,進入攔截器方法,比如呼叫A.getB().getName(),攔截器invoke()方法發現A.getB()null值。

    那麼就會單獨傳送事先儲存好的查詢關聯 B 物件的sql,把B查詢上來,然後呼叫A.setB(b),於是A的物件 b 屬性就有值了,接著完成A.getB().getName()方法的呼叫。

    這就是延遲載入的基本原理。

  3. 當然了,不光是Mybatis,幾乎所有的包括Hibernate,支援延遲載入的原理都是一樣的。

5. Mybatis和Hibernate的區別

  1. Mybatishibernate 不同 ,它不完全是一個 ORM 框架 ,因為 MyBatis 需要程式設計師自己編寫 Sql 語句。

  2. Mybatis 直接編寫原生態 sql, 可以嚴格控制 sql 執行效能, 靈活度高, 非常適合對關係資料模型要求不高的軟體開發,因為這類軟體需求變化頻繁,一但需求變化要求迅速輸出成果。 但是靈活的前提是 mybatis 無法做到資料庫無關性,如果需要實現支援多種資料庫的軟體,則需要自定義多套 sql 對映檔案,工作量大 。

  3. Hibernate 物件/關係對映能力強, 資料庫無關性好,對於關係模型要求高的軟體,如果用 · 開發可以節省很多程式碼, 提高效率。

6. 模糊查詢like語句該怎麼寫

  1. 在Java程式碼中新增 sql 萬用字元。也是比較推薦用的一種。
	<!--直接在程式碼中拼接%, 不存在sql注入-->
 <select id="findUserByLikeName2" parameterType="java.lang.String" resultMap="user">
      select * from t_user where name like #{name,jdbcType=VARCHAR}
  </select>
@Test
public void findUserByLikeName2(){
    String name = "Cloud";
    List<User> test = userMapper.findUserByLikeName2("%" +name+"%");
    // select * from t_user where name like ?
    // %Cloud%(String)
    System.out.println(test.size());
}
  1. sql 語句中拼接萬用字元, 會引起 sql 注入
String wildcardname = “smi”;
List<name> names = mapper.selectlike(wildcardname);

<select id=”selectlike”>
	select * from foo where bar like "%"#{value}"%"
</select>
  1. concat()函式。在實際開發最常用的一種。
 <!--concat Mysql和 Oracle區別 ,不存在sql注入-->
  <select id="findUserByLikeName3" parameterType="java.lang.String" resultMap="user">
      select * from t_user where name like concat('%',#{name,jdbcType=VARCHAR},'%')
  </select>
@Test
public void findUserByLikeName3(){
    String name = "Cloud";
    List<User> test = userMapper.findUserByLikeName3(name);
    // select * from t_user where name like concat('%',?,'%')
    // Cloud(String)
    System.out.println(test.size());
}

當使用方式三的時候,如果查詢的關鍵字就是% ,那情況會是什麼?

查詢的sql如下:

select * from t_user where name like concat('%','%','%')

而是查出來全部的資料,並不是只包含了%的資料,如果查詢_也是一樣的。

那這種情況肯定是不滿足查詢需求的,則需要調整。

  1. 在程式碼中進行轉義
@Test
public void findUserByLikeName3(){
    String name = "%";
    name = name.replaceAll("_", "\\\\_");
    name = name.replaceAll("%", "\\\\%");

    List<User> test = userMapper.findUserByLikeName3(name);
    System.out.println(test.size());
}
  1. 使用ESCAPE
<select id="findUserByLikeName4" parameterType="java.lang.String" resultMap="user">
	select * from t_user where name like concat('%',#{name,jdbcType=VARCHAR},'%') ESCAPE '/'
</select>

測試:

@Test
public void findUserByLikeName4(){
    // replaceAll("%", "/%").replaceAll("_", "/_")
    String name = "%";
    List<User> test = userMapper.findUserByLikeName4(name);
    System.out.println(test.size());// 查到全部
    List<User> test1 = userMapper.findUserByLikeName4("/" +name);
    System.out.println(test1.size());//查到匹配%的記錄
}

7. Mybatis的Xml對映檔案中,不同的Xml對映檔案,id是否可以重複?

不同的Xml對映檔案,如果配置了namespace,那麼id可以重複;

如果沒有配置namespace,那麼id不能重複;

原因就是namespace+id是作為Mapkey使用的,如果沒有namespace,就剩下id,那麼,id重複會導致資料互相覆蓋。

有了namespace,自然id就可以重複,namespace不同,namespace+id自然也就不同。

備註:在舊版本的Mybatis中,namespace是可選的,不過新版本的namespace已經是必須的了。

8. 當實體類中的屬性名和表中的欄位名不一樣 ,怎麼辦 ?

  1. 通過在查詢的 sql 語句中定義欄位名的別名 , 讓欄位名的別名和實體類的屬性名一致。
<select id="selectorder"  parametertype="int" resultetype="me.gacl.domain.order">
		select 
  			order_id as id, 
  			order_no as orderno,
  			order_price as price 
  	form 
  			orders 
  	where 
  			order_id=#{id};
</select>
  1. 通過來對映欄位名和實體類屬性名的一一對應的關係。
<select id="getOrder" parameterType="int" resultMap="orderresultmap">
		select 
  				* 
  	from 
  				orders 
  	where 
  			order_id=#{id}
</select>

<resultMap type="me.gacl.domain.order" id="orderresultmap">
    <!–用 id 屬性來對映主鍵欄位–>
    <id property="id" column="order_id">
    <!–用 result 屬性來對映非主鍵欄位,property 為實體類屬性名,column 為資料表中的屬性–>
    <result property="orderno" column="order_no"/>
    <result property="price"  column="order_price"/>
</reslutMap>

9. Mybatis是如何將sql執行結果封裝為目標物件並返回的?都有哪些對映形式?

  1. 第一種是使用 標籤,逐一定義列名和物件屬性名之間的對映關係。

  2. 第二種是使用sql列的別名功能,將列別名書寫為物件屬性名,比如T_NAME as NAME,物件屬性名一般是name,小寫,但是列名不區分大小寫,Mybatis會忽略列名大小寫,智慧找到與之對應物件屬性名,你甚至可以寫成T_NAME as NaMe,Mybatis一樣可以正常工作。

10. 在mapper中如何傳遞多個引數

  1. 順序傳參法
public interface UserMapper{
		User UserselectUser(String name,String area);  
}
<select id="selectUser"resultMap="BaseResultMap">
	select 
  			*	
  from
  		user_user_t	
  where
  		user_name = #{0} and user_area=#{1}
</select>
<!--#{0},#{1}代表的是引數的順序,並不是什麼引數-->
  1. 使用@Param註解傳參
public interface UserMapper {
		User selectuser(@param("username") String username,
                    @param("hashedpassword") tring hashedpassword);
}

然後 ,就可以在 xml 像下面這樣使用:

<select id="selectuser" resulttype="user">
	select 
  		id, 
  		username, 
  		hashedpassword 
 from 
  		some_table
 where 
  		username = #{username} and hashedpassword = #{hashedpassword};
</select>

#{}裡面的名稱對應的是註解@Param括號裡面修飾的名稱,這種方法在引數不多的情況話,推薦使用。

  1. Map傳參法
public interface UserMapper{
		User selectUser(Map<String,Object> map);  
}
<select id="selectuser" resulttype="User" parameterType="java.util.Map">
	select 
  		id, 
  		username, 
  		hashedpassword 
 from 
  		some_table
 where 
  		username = #{username} and hashedpassword = #{hashedpassword};
</select>

#{}裡面的名稱對應的是Map裡面的key,這種方法適合傳遞多個引數,且能靈活變化。

  1. Java Bean傳參法
public interface UserMapper{
		User selectUser(User user);  
}
<select id="selectuser" resulttype="User" parameterType="User">
  	select 
  				* 
  	from 
  				user
  	where 
  				username=#{username} and password=#{password};
</select>

這種方法比較直觀,就是需要建立一個實體類(VO),擴充套件不容易,但程式碼的可讀性高,業務邏輯處理方便,推薦使用。

11. 什麼是MyBatis的介面繫結?有哪些實現方式?

  1. 介面繫結,就是在 MyBatis中任意定義介面 ,然後把接口裡面的方法和 SQL語句繫結, 我們直接呼叫介面方法就可以 ,這樣比起原來 了 SqlSession提供的方法我們可以有更加靈活的選擇和設定。
  2. 介面繫結有兩種實現方式,
    • 一種是通過註解繫結, 就是在介面的方法上面加上@Select、@Update 等註解, 裡面包含 Sql 語句來繫結;
    • 另外一種就是通過 xml 裡面寫 SQL 來繫結, 在這種情況下,要指定 xml 對映檔案裡面的 namespace 必須為介面的全路徑名。 當 Sql 語句比較簡單時候 ,用註解繫結 , 當 SQL 語句比較複雜 時候 ,用 xml 繫結 ,一般用 xml 繫結的比較多。

12. 通常一個 Xml 對映檔案,都會寫一個Mapper 介面與之對應,請問,這個 Mapper 介面的工作原理是什麼?Mapper 接口裡的方法,引數不同時,方法能過載嗎?

Dao介面即Mapper介面。介面的全限名,就是對映檔案中的 namespace 的值;

介面的方法名, 就是對映檔案中 MapperStatementid 值;

介面方法內的引數, 就是傳遞給 sql 的引數。

Mapper 介面是沒有實現類的,當呼叫介面方法時 ,介面全限名 +方法名拼接字元 串作為 key 值, 可唯一定位一個 MapperdStatement

在 Mybatis 中, 每一個<select>、<insert>、<update>、<delete>標籤, 都會被解析為一 個 MappedStatement 物件。

舉例: com.mybatis3.mappers.StudentDao.findStudentById, 可以唯一找到 namespacecom.mybatis3.mappers.StudentDao 下面 idfindStudentById 的 MapperdStatement。

Mapper 接口裡的方法 ,是不能過載的 ,因為是使用全限名 +方法名的儲存和尋找策略。

Mapper 介面的工作原理是JDK動態代理 , Mybatis 執行時會使用 JDK 動態代理為 Mapper介面生成代理物件 proxy,代理物件會攔截介面方法,轉而執行 MapperdStatement 所代表的 sql, 然後將 sql 執行結果返回。

13. 一對一、一對多的關聯查詢

推薦練習:

MyBatis學習總結(五)——實現關聯表查詢 - 孤傲蒼狼 - 部落格園 (cnblogs.com)

<!--根據班級的Id查詢對應的老師 一個班級對應一個老師 -->
<mapper namespace="com.lcb.mapping.userMapper">
		<!--association	一對一關聯查詢 -->
		<select id="getClass" parameterType="int" resultMap="ClassesResultMap">
				select 
      				* 
      	from 
      				class c,
      				teacher t 
      	where 
      				c.teacher_id=t.t_id and c.c_id=#{id}
		</select>

<resultMap type="com.lcb.user.Classes" id="ClassesResultMap">
		<!--實體類的欄位名和資料表的欄位名對映 -->
		<id property="id" column="c_id"/>
		<result property="name" column="c_name"/>
		<association property="teacher" javaType="com.lcb.user.Teacher">
				<id property="id" column="t_id"/>
				<result property="name" column="t_name"/>
		</association>
</resultMap>


<!--根據班級的Id查詢班級下面的老師和學生 一個班級對應一個老師,一個班級對應很多個學生 -->
<!--collection	一對多關聯查詢 -->
<select id="getClass2" parameterType="int" resultMap="ClassesResultMap2">
		select 
  				* 
  	from 
  				class c,
  				teacher t,
  				student s 
  	where 
  				c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}
</select>

	<resultMap type="com.lcb.user.Classes" id="ClassesResultMap2">
			<id property="id" column="c_id"/>
			<result property="name" column="c_name"/>
			<association property="teacher" javaType="com.lcb.user.Teacher">
					<id property="id" column="t_id"/>
					.......
					<result property="name" column="t_name"/>
			</association>

			<collection property="student" ofType="com.lcb.user.Student">
					<id property="id" column="s_id"/>
					<result property="name" column="s_name"/>
			</collection>
	</resultMap>
</mapper>

14. MyBatis實現一對一有幾種方式?具體怎麼操作的

有聯合查詢和巢狀查詢。

聯合查詢是幾個表聯合查詢,只查詢一次, 通過在resultMap裡面配置association節點配置一對一的類就可以完成;

巢狀查詢是先查一個表,根據這個表裡面的結果的外來鍵id,去再另外一個表裡面查詢資料,也是通過association配置,但另外一個表的查詢通過select屬性配置。

例如一個大學生只有一個學號,一個學號只屬於一個學生。同樣,人與身份證也是一對一的級聯關係。

Mybatis_test: mybatis的一對一和一對多的巢狀查詢和聯合查詢 的測試案例。 (gitee.com)

自己寫的一個demo能夠很好的理解!

14. 說一下Mybatis的一級、二級快取

  1. 一級快取: 基於 PerpetualCacheHashMap 本地快取,其儲存作用域為 Session,當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空,預設開啟一級快取

  2. 二級快取與一級快取其機制相同,預設也是採用 PerpetualCacheHashMap 儲存,不同在於其儲存作用域為 Mapper(Namespace),並且可自定義儲存源,如 Ehcache。

    預設不開啟二級快取,要開啟二級快取,使用二級快取屬性類需要實現Serializable序列化介面(可用來儲存物件的狀態),可在它的對映檔案中配置 ;

  3. 對於快取資料更新機制,當某一個作用域(一級快取 Session/二級快取Namespaces)的進行了C/U/D 操作後,預設該作用域下所有 select 中的快取將被 clear。如果開啟了二級快取,則只根據配置判斷是否重新整理。

15. Mybatis是如何進行分頁的?分頁外掛的原理是什麼?

【MyBatis】實現分頁功能_小皮豬的部落格-CSDN部落格

先回顧一下,平時Mysql中是怎麼做分頁的:

#其中office指相對於首行的偏移量(首行為0),rows指返回記錄條數
limit [office,] rows 

在JDBC中我們做分頁:

JavaWeb實現分頁的四種方法 - 光何 - 部落格園 (cnblogs.com)

這個值得一看!提供了很多的思路,並分析了各種的好處以及不好之處。

RowBounds物件有2個屬性,offsetlimit

offset:起始行數。

limit:需要的資料行數。

因此,取出來的資料就是:從第offset+1行開始,取limit行。

簡單上手使用。和 【MyBatis】實現分頁功能_小皮豬的部落格-CSDN部落格

Mybatis使用RowBounds物件進行分頁,它是針對ResultSet結果集執行的記憶體分頁,而非物理分頁。

可以在sql內直接書寫帶有物理分頁的引數來完成物理分頁功能,也可以使用分頁外掛來完成物理分頁。

分頁外掛的基本原理是使用Mybatis提供的外掛介面,實現自定義外掛,在外掛的攔截方法內攔截待執行的sql,然後重寫sql,根據dialect方言,新增對應的物理分頁語句和物理分頁引數。

平時還有一個比較推薦的PageHelper.如何使用分頁外掛 (pagehelper.github.io)

16. Mybatis動態sql有什麼用?執行原理?有哪些動態sql?

Mybatis動態sql可以在Xml對映檔案內,以標籤的形式編寫動態sql,執行原理是根據表示式的值 完成邏輯判斷 並動態拼接sql的功能。

Mybatis提供了9種動態sql標籤:trim | where | set | foreach | if | choose | when | otherwise | bind

跟著官方文件淺看一下:

if

使用動態 SQL 最常見情景是根據條件包含 where 子句的一部分

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

這條語句提供了可選的查詢文字功能。如果不傳入 “title”,那麼所有處於 “ACTIVE” 狀態的 BLOG 都會返回;如果傳入了 “title” 引數,那麼就會對 “title” 一列進行模糊查詢並返回對應的 BLOG 結果(細心的讀者可能會發現,“title” 的引數值需要包含查詢掩碼或萬用字元字元)。

如果希望通過 “title” 和 “author” 兩個引數進行可選搜尋該怎麼辦呢?

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

choose、when、otherwise

有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。

還是上面的例子,但是策略變為:傳入了 “title” 就按 “title” 查詢,傳入了 “author” 就按 “author” 查詢的情形。若兩者都沒有傳入,就返回標記為 featured 的 BLOG(這可能是管理員認為,與其返回大量的無意義隨機 Blog,還不如返回一些由管理員精選的 Blog)。

<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

前面幾個例子已經方便地解決了一個臭名昭著的動態 SQL 問題。現在回到之前的 “if” 示例,這次我們將 “state = ‘ACTIVE’” 設定成動態條件,看看會發生什麼。

<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>
</select>

如果沒有匹配的條件會怎麼樣?最終這條 SQL 會變成這樣:

SELECT * FROM BLOG
WHERE

這會導致查詢失敗。如果匹配的只是第二個條件又會怎樣?這條 SQL 會是這樣:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

這個查詢也會失敗。這個問題不能簡單地用條件元素來解決。這個問題是如此的難以解決,以至於解決過的人不會再想碰到這種問題。

MyBatis 有一個簡單且適合大多數場景的解決辦法。而在其他場景中,可以對其進行自定義以符合需求。而這,只需要一處簡單的改動:

<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>

where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句。

而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除。

#如果走的是第二個 title
SELECT * FROM BLOG WHERE title like 'sometitle'

如果 where 元素與你期望的不太一樣,你也可以通過自定義 trim 元素來定製where元素的功能。

比如,和 where 元素等價的自定義 trim 元素為:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 屬性會忽略通過管道符分隔的文字序列(注意此例中的空格是必要的)。

上述例子會移除所有 prefixOverrides 屬性中指定的內容,並且插入 prefix 屬性中指定的內容。

用於動態更新語句的類似解決方案叫做 set

set 元素可以用於動態包含需要更新的列,忽略其它不更新的列。比如:

<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>

這個例子中,set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。

來看看與 set 元素等價的自定義 trim 元素吧:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意,我們覆蓋了字尾值設定,並且自定義了字首值。

foreach

動態 SQL 的另一個常見使用場景是對集合進行遍歷(尤其是在構建 IN 條件語句的時候)。比如:

<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>

foreach 元素的功能非常強大,它允許你指定一個集合,宣告可以在元素體內使用的集合項(item)和索引(index)變數。它也允許你指定開頭與結尾的字串以及集合項迭代之間的分隔符。這個元素也不會錯誤地新增多餘的分隔符,看它多智慧!