XML 映射文件
本文參考mybatis中文官網進行學習總結:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
MyBatis 的真正強大在於它的映射語句,這是它的魔力所在。由於它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。MyBatis 為聚焦於 SQL 而構建,以盡可能地為你減少麻煩。
SQL 映射文件只有很少的幾個頂級元素(按照應被定義的順序列出):
- cache – 對給定命名空間的緩存配置。
- cache-ref – 對其他命名空間緩存配置的引用。
- resultMap – 是最復雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
- sql – 可被其他語句引用的可重用語句塊。
- insert – 映射插入語句
- update – 映射更新語句
- delete – 映射刪除語句
- select – 映射查詢語句
select
查詢語句是 MyBatis 中最常用的元素之一,光能把數據存到數據庫中價值並不大,只有還能重新取出來才有用,多數應用也都是查詢比修改要頻繁。對每個插入、更新或刪除操作,通常間隔多個查詢操作。這是 MyBatis 的基本原則之一,也是將焦點和努力放在查詢和結果映射的原因。簡單查詢的 select 元素是非常簡單的。比如:
<resultMap id="BaseResultMap" type="blog"> <id column="bid" property="bid" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <result column="author_id" property="authorId" jdbcType="INTEGER"/> </resultMap> <!--****************************************************************************************--> <!--簡單查詢--> <select id="selectBlogById" resultMap="BaseResultMap" statementType="PREPARED" useCache="false"> select * from blog where bid = #{bid} </select>
#{bid} 這就告訴 MyBatis 創建一個預處理語句(PreparedStatement)參數,在 JDBC 中,這樣的一個參數在 SQL 中會由一個“?”來標識,並被傳遞到一個新的預處理語句中,就像這樣:
// 近似的 JDBC 代碼,非 MyBatis 代碼... String selectBlogById= "SELECT * FROM BLOG WHERE BID=?"; PreparedStatement ps = conn.prepareStatement(selectBlogById); ps.setInt(1,bid);
當然,使用 JDBC 意味着需要更多的代碼來提取結果並將它們映射到對象實例中,而這就是 MyBatis 節省你時間的地方。select 元素允許你配置很多屬性來配置每條語句的作用細節。
<select id="selectPerson" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY">
相關屬性描述:
屬性 | 描述 |
---|---|
id | 在命名空間中唯一的標識符,可以被用來引用這條語句。 |
parameterType | 將會傳入這條語句的參數類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器(TypeHandler) 推斷出具體傳入語句的參數,默認值為未設置(unset)。 |
resultType | 從這條語句中返回的期望類型的類的完全限定名或別名。 注意如果返回的是集合,那應該設置為集合包含的類型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同時使用。 |
resultMap | 外部 resultMap 的命名引用。結果集的映射是 MyBatis 最強大的特性,如果你對其理解透徹,許多復雜映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同時使用。 |
flushCache | 將其設置為 true 后,只要語句被調用,都會導致本地緩存和二級緩存被清空,默認值:false。 |
useCache | 將其設置為 true 后,將會導致本條語句的結果被二級緩存緩存起來,默認值:對 select 元素為 true。 |
timeout | 這個設置是在拋出異常之前,驅動程序等待數據庫返回請求結果的秒數。默認值為未設置(unset)(依賴驅動)。 |
fetchSize | 這是一個給驅動的提示,嘗試讓驅動程序每次批量返回的結果行數和這個設置值相等。 默認值為未設置(unset)(依賴驅動)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 中的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等價於 unset) 中的一個,默認值為 unset (依賴驅動)。 |
databaseId | 如果配置了數據庫廠商標識(databaseIdProvider),MyBatis 會加載所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
resultOrdered | 這個設置僅針對嵌套結果 select 語句適用:如果為 true,就是假設包含了嵌套結果集或是分組,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。 這就使得在獲取嵌套的結果集的時候不至於導致內存不夠用。默認值:false。 |
resultSets | 這個設置僅對多結果集的情況適用。它將列出語句執行后返回的結果集並給每個結果集一個名稱,名稱是逗號分隔的。 |
insert, update 和 delete:
數據變更語句 insert,update 和 delete 的實現非常接近:
<insert id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20"> <update id="updateAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20"> <delete id="deleteAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20">
相關屬性描述:
屬性 | 描述 |
---|---|
id | 命名空間中的唯一標識符,可被用來代表這條語句。 |
parameterType | 將要傳入語句的參數的完全限定類名或別名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器推斷出具體傳入語句的參數,默認值為未設置(unset)。 |
flushCache | 將其設置為 true 后,只要語句被調用,都會導致本地緩存和二級緩存被清空,默認值:true(對於 insert、update 和 delete 語句)。 |
timeout | 這個設置是在拋出異常之前,驅動程序等待數據庫返回請求結果的秒數。默認值為未設置(unset)(依賴驅動)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。 |
useGeneratedKeys | (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數據庫內部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關系數據庫管理系統的自動遞增字段),默認值:false。 |
keyProperty | (僅對 insert 和 update 有用)唯一標記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設置它的鍵值,默認值:未設置(unset)。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
keyColumn | (僅對 insert 和 update 有用)通過生成的鍵值設置表中的列名,這個設置僅在某些數據庫(像 PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候需要設置。如果希望使用多個生成的列,也可以設置為逗號分隔的屬性名稱列表。 |
databaseId | 如果配置了數據庫廠商標識(databaseIdProvider),MyBatis 會加載所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
下面就是 insert,update 和 delete 語句的示例:
<insert id="insertAuthor"> insert into Author (id,username,password,email,bio) values (#{id},#{username},#{password},#{email},#{bio}) </insert> <update id="updateAuthor"> update Author set username = #{username}, password = #{password}, email = #{email}, bio = #{bio} where id = #{id} </update> <delete id="deleteAuthor"> delete from Author where id = #{id} </delete>
如前所述,插入語句的配置規則更加豐富,在插入語句里面有一些額外的屬性和子元素用來處理主鍵的生成,而且有多種生成方式。首先,如果你的數據庫支持自動生成主鍵的字段(比如 MySQL 和 SQL Server),那么你可以設置 useGeneratedKeys=”true”,然后再把 keyProperty 設置到目標屬性上就 OK 了。如果你的數據庫還支持多行插入, 你也可以傳入一個 Blog數組或集合,並返回自動生成的主鍵。
<!--批量插入--> <insert id="insertBlogs" useGeneratedKeys="true" keyProperty="bid"> insert into blog (name, author_id) values <foreach item="item" collection="list" separator=","> (#{item.name}, #{item.authorId}) </foreach>
</insert>
sql
這個元素可以被用來定義可重用的 SQL 代碼段,這些 SQL 代碼可以被包含在其他語句中。它可以(在加載的時候)被靜態地設置參數。 在不同的包含語句中可以設置不同的值到參數占位符上。mybatis中sql
標簽與include
標簽進行配合,靈活的查詢需要的數據。
<sql id="ref"> bid,name,authorId </sql> <select id="selectbyId" resultMap="BaseResultMap"> select <include refid="ref"/> from blog where bid = #{bid} </select>
sql標簽
中id屬性對應include標簽
中的refid屬性。通過include標簽將sql片段
和原sql片段進行拼接成一個完成的sql語句進行執行。include標簽中還可以用property標簽
,用以指定自定義屬性。
<select id="selectbyId" resultMap="BaseResultMap"> select <include refid="ref"> <property name="abc" value="bid"/> </include> from blog where bid = #{bid} </select>
此時,可以在sql標簽
中取出對應設置的自定義屬性中的值,例如接上代碼例子:
<sql id="ref"> ${abc},name,authorId </sql> <select id="selectbyId" resultMap="BaseResultMap"> select <include refid="ref"> <property name="abc" value="bid"/> </include> from blog where bid = #{bid} </select>
在sql
標簽中通過${}
取出對應include標簽中
設置的屬性值。
關聯(association)元素處理“有一個”類型的關系。 比如,在我們的示例中,一個博客有一個用戶。關聯結果映射和其它類型的映射工作方式差不多。 你需要指定目標屬性名以及屬性的javaType(很多時候 MyBatis 可以自己推斷出來),在必要的情況下你還可以設置 JDBC 類型,如果你想覆蓋獲取結果值的過程,還可以設置類型處理器。關聯的不同之處是,你需要告訴 MyBatis 如何加載關聯。MyBatis 有兩種不同的方式加載關聯:
- 嵌套 Select 查詢:通過執行另外一個 SQL 映射語句來加載期望的復雜類型。
- 嵌套結果映射:使用嵌套的結果映射來處理連接結果的重復子集。
關聯的嵌套 Select 查詢
<!-- 另一種聯合查詢(一對一)的實現,但是這種方式有“N+1”的問題 --> <resultMap id="BlogWithAuthorQueryMap" type="com.wuzz.demo.associate.BlogAndAuthor"> <id column="bid" property="bid" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <association property="author" javaType="com.wuzz.demo.entity.Author" column="author_id" select="selectAuthor"/> </resultMap> <!-- 嵌套查詢 --> <select id="selectAuthor" parameterType="int" resultType="com.wuzz.demo.entity.Author"> select author_id authorId, author_name authorName from author where author_id = #{authorId} </select> <!-- 根據文章查詢作者,一對一,嵌套查詢,存在N+1問題,可通過開啟延遲加載解決 --> <select id="selectBlogWithAuthorQuery" resultMap="BlogWithAuthorQueryMap" > select b.bid, b.name, b.author_id, a.author_id , a.author_name from blog b left join author a on b.author_id=a.author_id where b.bid = #{bid, jdbcType=INTEGER} </select>
就是這么簡單。我們有兩個 select 查詢語句:一個用來加載博客(Blog),另外一個用來加載作者(Author),而且博客的結果映射描述了應該使用 selectAuthor 語句加載它的 author 屬性。其它所有的屬性將會被自動加載,只要它們的列名和屬性名相匹配。這種方式雖然很簡單,但在大型數據集或大型數據表上表現不佳。這個問題被稱為“N+1 查詢問題”。 概括地講,N+1 查詢問題是這樣子的:
- 你執行了一個單獨的 SQL 語句來獲取結果的一個列表(就是“+1”)。
- 對列表返回的每條記錄,你執行一個 select 查詢語句來為每條記錄加載詳細信息(就是“N”)。
這個問題會導致成百上千的 SQL 語句被執行。有時候,我們不希望產生這樣的后果。MyBatis 能夠對這樣的查詢進行延遲加載,因此可以將大量語句同時運行的開銷分散開來。mybatis.configuration.lazy-loading-enabled=true 可以開啟延時加載 mybatis.configuration.aggressive-lazy-loading=true 可以指定哪些方法調用查詢, 然而,如果你加載記錄列表之后立刻就遍歷列表以獲取嵌套的數據,就會觸發所有的延遲加載查詢,性能可能會變得很糟糕。所以還有另外一種方法。
關聯的嵌套結果映射
<!-- 根據文章查詢作者,一對一查詢的結果,嵌套查詢 --> <resultMap id="BlogWithAuthorResultMap" type="com.wuzz.demo.associate.BlogAndAuthor"> <id column="bid" property="bid" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <!-- 聯合查詢,將author的屬性映射到ResultMap --> <association property="author" javaType="com.wuzz.demo.entity.Author"> <id column="author_id" property="authorId"/> <result column="author_name" property="authorName"/> </association> </resultMap> <!-- 根據文章查詢作者,一對一,嵌套結果,無N+1問題 --> <select id="selectBlogWithAuthorResult" resultMap="BlogWithAuthorResultMap" > select b.bid, b.name, b.author_id, a.author_id , a.author_name from blog b,author a where b.author_id=a.author_id and b.bid = #{bid, jdbcType=INTEGER} </select>
查詢文章帶評論的結果(一對多)映射:
<!-- 查詢文章帶評論的結果(一對多) --> <resultMap id="BlogWithCommentMap" type="com.wuzz.demo.associate.BlogAndComment" extends="BaseResultMap" > <collection property="comment" ofType="com.wuzz.demo.entity.Comment"> <id column="comment_id" property="commentId" /> <result column="content" property="content" /> <result column="bid" property="bid" /> </collection> </resultMap> <!-- 根據文章查詢評論,一對多 --> <select id="selectBlogWithCommentById" resultMap="BlogWithCommentMap" > select b.bid, b.name, b.author_id , c.comment_id , c.content,c.bid from blog b, comment c where b.bid = c.bid and b.bid = #{bid} </select>
按作者查詢文章評論的結果(多對多):
<!-- 按作者查詢文章評論的結果(多對多) --> <resultMap id="AuthorWithBlogMap" type="com.wuzz.demo.associate.AuthorAndBlog" > <id column="author_id" property="authorId" jdbcType="INTEGER"/> <result column="author_name" property="authorName" jdbcType="VARCHAR"/> <collection property="blog" ofType="com.wuzz.demo.associate.BlogAndComment"> <id column="bid" property="bid" /> <result column="name" property="name" /> <result column="author_id" property="authorId" /> <collection property="comment" ofType="com.wuzz.demo.entity.Comment"> <id column="comment_id" property="commentId" /> <result column="content" property="content" /> <result column="bid" property="bid" /> </collection> </collection> </resultMap> <!-- 根據作者文章評論,多對多 --> <select id="selectAuthorWithBlog" resultMap="AuthorWithBlogMap" > select b.bid, b.name, a.author_id , a.author_name , c.comment_id , c.content,c.bid from blog b, author a, comment c where b.author_id = a.author_id and b.bid = c.bid </select>
動態 SQL
MyBatis 的強大特性之一便是它的動態 SQL。如果你有使用 JDBC 或其它類似框架的經驗,你就能體會到根據不同條件拼接 SQL 語句的痛苦。例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態 SQL 這一特性可以徹底擺脫這種痛苦。雖然在以前使用動態 SQL 並非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射語句中的強大的動態 SQL 語言得以改進這種情形。動態 SQL 元素和 JSTL 或基於類似 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多元素需要花時間了解。MyBatis 3 大大精簡了元素種類,現在只需學習原來一半的元素便可。MyBatis 采用功能強大的基於 OGNL 的表達式來淘汰其它大部分元素。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
其中 choose再實際開發中應用的較少,我們這里就其他3個標簽進行測試
<!--動態sql--> <select id="selectBlogById2" resultMap="BaseResultMap" statementType="PREPARED" useCache="false" parameterType="blog"> select * from blog <trim prefix="WHERE" prefixOverrides="AND |OR "> <if test="bid != null and bid !='' "> bid = #{bid} </if> <if test="name != null and name !='' "> AND name = #{name} </if> <if test="authorId != null and authorId != ''"> AND author_id = #{author_id} </if> </trim> </select>
foreach:動態 SQL 的另外一個常用的操作需求是對一個集合進行遍歷,通常是在構建 IN 條件語句的時候。比如:
<select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach> </select>