框架篇(一)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
-
Mybatis是一個半ORM(物件關係對映)框架,它內部封裝了JDBC,載入驅動、建立連線、建立statement等繁雜的過程,開發者開發時只需要關注如何編寫SQL語句,可以嚴格控制
sql
-
作為一個半ORM框架,MyBatis 可以使用 XML 或註解來配置和對映原生資訊,將 POJO對映成資料庫中的記錄,避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。
-
通過xml 檔案或註解的方式將要執行的各種
statement
配置起來,並通過java物件和statement
中sql
的動態引數進行對映生成最終執行的sql
語句,最後由mybatis框架執行sql並將結果對映為java物件並返回。(從執行sql到返回result的過程)。 -
由於MyBatis專注於SQL本身,靈活度高,所以比較適合對效能的要求很高,或者需求變化較多的專案,如網際網路專案。
優點:
-
基於SQL語句程式設計,相當靈活,不會對應用程式或者資料庫的現有設計造成任何影響,SQL寫在XML裡,解除sql與程式程式碼的耦合,便於統一管理;提供XML標籤,支援編寫動態SQL語句,並可重用。
-
與JDBC相比,減少了50%以上的程式碼量,消除了JDBC大量冗餘的程式碼,不需要手動開關連線;
-
很好的與各種資料庫相容(因為MyBatis使用JDBC來連線資料庫,所以只要JDBC支援的資料庫MyBatis都支援)。
-
能夠與Spring很好的整合;
-
提供對映標籤,支援物件與資料庫的ORM欄位關係對映;提供物件關係對映標籤,支援物件關係元件維護。
缺點:
-
SQL語句的編寫工作量較大,尤其當欄位多、關聯表多時,對開發人員編寫SQL語句的功底有一定要求。
-
SQL語句依賴於資料庫,導致資料庫移植性差,不能隨意更換資料庫。
2. 簡述Mybatis的工作原理
- 讀取
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>
-
載入
SQL
對映檔案。對映檔案就是SQL
對映檔案,該檔案中儲存了操作資料庫的SQL
語句,需要在Mybatis配置檔案mybatis-config.xml
中配置載入。mybatis-config.xml
檔案可以載入多個對映檔案,每個對映檔案對應資料庫中的一張表。 -
構造會話工廠。通過Mybatis的環境等配置資訊構建會話工廠
SqlSessionFactory
。 -
建立會話物件。通過會話工廠建立一個
SqlSession
物件,該物件中包含了執行sql
的所有方法。 -
Executor
執行器。Mybatis底層定義了一個Executor
介面來操作資料庫。它將根據SqlSession
傳遞的引數動態的生成需要執行的sql
語句,同時負責查詢快取的維護。 -
MappedStatement
物件:在Executor
介面的執行方法中有一個MappedStatement
型別的引數,該引數是對對映資訊的封裝,用於儲存要對映的SQL
語句的id
、引數等資訊。 -
輸入引數對映:輸入引數的型別可以是
Map
、List
等集合型別,也可以是基本資料型別和POJO
型別。輸入引數對映的過程類似於JDBC對PrepareStatement
物件設定引數的過程。 -
輸出結果的對映。輸出結果型別可以是
Map
、List
等集合型別,也可以是基本資料型別和POJO
型別。輸出結果就類似於JDBC對結果集解析過程。
3. #{}和${}的區別是什麼?
${ }
是拼接符,字串替換,#{ }
是佔位符,預編譯處理;
Mybatis在處理${}
時,就是把${}
直接替換成變數的值。
而Mybatis在處理#{}
時,會對sql語句進行預處理,將sql中的#{}
替換為?
號,呼叫PreparedStatement
的set
方法來賦值;
使用#{}
可以有效的防止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是否支援延遲載入?如果支援,它的實現原理是什麼?
-
Mybatis僅支援
association
關聯物件和collection
關聯集合物件的延遲載入,association
指的就是一對一,collection
指的就是一對多查詢。在Mybatis配置檔案中,可以配置是否啟用延遲載入
lazyLoadingEnabled=true|false。
-
它的原理是,使用
CGLIB
建立目標物件的代理物件,當呼叫目標方法時,進入攔截器方法,比如呼叫A.getB().getName()
,攔截器invoke()
方法發現A.getB()
是null
值。那麼就會單獨傳送事先儲存好的查詢關聯 B 物件的
sql
,把B查詢上來,然後呼叫A.setB(b)
,於是A的物件 b 屬性就有值了,接著完成A.getB().getName()
方法的呼叫。這就是延遲載入的基本原理。
-
當然了,不光是Mybatis,幾乎所有的包括
Hibernate
,支援延遲載入的原理都是一樣的。
5. Mybatis和Hibernate的區別
-
Mybatis
和hibernate
不同 ,它不完全是一個 ORM 框架 ,因為 MyBatis 需要程式設計師自己編寫 Sql 語句。 -
Mybatis
直接編寫原生態sql
, 可以嚴格控制sql
執行效能, 靈活度高, 非常適合對關係資料模型要求不高的軟體開發,因為這類軟體需求變化頻繁,一但需求變化要求迅速輸出成果。 但是靈活的前提是 mybatis 無法做到資料庫無關性,如果需要實現支援多種資料庫的軟體,則需要自定義多套 sql 對映檔案,工作量大 。 -
Hibernate
物件/關係對映能力強, 資料庫無關性好,對於關係模型要求高的軟體,如果用 · 開發可以節省很多程式碼, 提高效率。
6. 模糊查詢like
語句該怎麼寫
- 在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());
}
- 在
sql
語句中拼接萬用字元, 會引起sql
注入
String wildcardname = “smi”;
List<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like "%"#{value}"%"
</select>
-
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('%','%','%')
而是查出來全部的資料,並不是只包含了%
的資料,如果查詢_
也是一樣的。
那這種情況肯定是不滿足查詢需求的,則需要調整。
- 在程式碼中進行轉義
@Test
public void findUserByLikeName3(){
String name = "%";
name = name.replaceAll("_", "\\\\_");
name = name.replaceAll("%", "\\\\%");
List<User> test = userMapper.findUserByLikeName3(name);
System.out.println(test.size());
}
- 使用
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
是作為Map
的key
使用的,如果沒有namespace
,就剩下id
,那麼,id
重複會導致資料互相覆蓋。
有了namespace
,自然id
就可以重複,namespace
不同,namespace+id
自然也就不同。
備註:在舊版本的Mybatis中,namespace
是可選的,不過新版本的namespace
已經是必須的了。
8. 當實體類中的屬性名和表中的欄位名不一樣 ,怎麼辦 ?
- 通過在查詢的
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>
- 通過來對映欄位名和實體類屬性名的一一對應的關係。
<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執行結果封裝為目標物件並返回的?都有哪些對映形式?
-
第一種是使用
標籤,逐一定義列名和物件屬性名之間的對映關係。 -
第二種是使用
sql
列的別名功能,將列別名書寫為物件屬性名,比如T_NAME as NAME
,物件屬性名一般是name
,小寫,但是列名不區分大小寫,Mybatis會忽略列名大小寫,智慧找到與之對應物件屬性名,你甚至可以寫成T_NAME as NaMe
,Mybatis一樣可以正常工作。
10. 在mapper中如何傳遞多個引數
- 順序傳參法
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}代表的是引數的順序,並不是什麼引數-->
- 使用
@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
括號裡面修飾的名稱,這種方法在引數不多的情況話,推薦使用。
-
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,這種方法適合傳遞多個引數,且能靈活變化。
-
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的介面繫結?有哪些實現方式?
- 介面繫結,就是在 MyBatis中任意定義介面 ,然後把接口裡面的方法和 SQL語句繫結, 我們直接呼叫介面方法就可以 ,這樣比起原來 了
SqlSession
提供的方法我們可以有更加靈活的選擇和設定。 - 介面繫結有兩種實現方式,
- 一種是通過註解繫結, 就是在介面的方法上面加上@Select、@Update 等註解, 裡面包含 Sql 語句來繫結;
- 另外一種就是通過
xml
裡面寫SQL
來繫結, 在這種情況下,要指定xml
對映檔案裡面的namespace
必須為介面的全路徑名。 當Sql
語句比較簡單時候 ,用註解繫結 , 當 SQL 語句比較複雜 時候 ,用 xml 繫結 ,一般用 xml 繫結的比較多。
12. 通常一個 Xml 對映檔案,都會寫一個Mapper 介面與之對應,請問,這個 Mapper 介面的工作原理是什麼?Mapper 接口裡的方法,引數不同時,方法能過載嗎?
Dao介面即Mapper介面。介面的全限名,就是對映檔案中的 namespace
的值;
介面的方法名, 就是對映檔案中 Mapper
的 Statement
的 id
值;
介面方法內的引數, 就是傳遞給 sql
的引數。
Mapper 介面是沒有實現類的,當呼叫介面方法時 ,介面全限名 +方法名拼接字元 串作為 key
值, 可唯一定位一個 MapperdStatement
。
在 Mybatis 中, 每一個<select>、<insert>、<update>、<delete>
標籤, 都會被解析為一 個 MappedStatement
物件。
舉例: com.mybatis3.mappers.StudentDao.findStudentById, 可以唯一找到 namespace
為 com.mybatis3.mappers.StudentDao
下面 id
為 findStudentById
的 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的一級、二級快取
-
一級快取: 基於
PerpetualCache
的HashMap
本地快取,其儲存作用域為Session
,當Session
flush 或 close 之後,該Session
中的所有Cache
就將清空,預設開啟一級快取。 -
二級快取與一級快取其機制相同,預設也是採用
PerpetualCache
,HashMap
儲存,不同在於其儲存作用域為Mapper(Namespace)
,並且可自定義儲存源,如 Ehcache。預設不開啟二級快取,要開啟二級快取,使用二級快取屬性類需要實現
Serializable
序列化介面(可用來儲存物件的狀態),可在它的對映檔案中配置 ; -
對於快取資料更新機制,當某一個作用域(一級快取
Session
/二級快取Namespaces
)的進行了C/U/D
操作後,預設該作用域下所有 select 中的快取將被clear
。如果開啟了二級快取,則只根據配置判斷是否重新整理。
15. Mybatis是如何進行分頁的?分頁外掛的原理是什麼?
【MyBatis】實現分頁功能_小皮豬的部落格-CSDN部落格
先回顧一下,平時Mysql中是怎麼做分頁的:
#其中office指相對於首行的偏移量(首行為0),rows指返回記錄條數
limit [office,] rows
在JDBC中我們做分頁:
JavaWeb實現分頁的四種方法 - 光何 - 部落格園 (cnblogs.com)
這個值得一看!提供了很多的思路,並分析了各種的好處以及不好之處。
RowBounds
物件有2個屬性,offset
和limit
。
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)變數。它也允許你指定開頭與結尾的字串以及集合項迭代之間的分隔符。這個元素也不會錯誤地新增多餘的分隔符,看它多智慧!