如何優雅的使用MyBatis?

2022-06-19 15:00:55

​本文目錄

什麼是 MyBatis ?

對映器(mappers)

typeAliases 型別別名減少類完全限制名的冗餘

處理列舉型別

多行插入

重用 SQL 程式碼段,消除重複

字串替換#{}和${}的區別

Result Maps,表的列名和類的屬性名不對應怎麼處理?

MyBatis關聯的巢狀查詢

MyBatis集合的巢狀查詢

動態 SQL,如何優雅的構建動態Sql

Where 構建動態查詢條件

choose, when, otherwise 從條件中選其一項

set 動態包含需要更新的列

foreach 構建 IN 條件語句

bind 構建like 查詢


什麼是 MyBatis ?

MyBatis 是一款優秀的持久層框架,它支援客製化化 SQL、儲存過程以及高階對映。MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。MyBatis 可以使用簡單的 XML 或註解來設定和對映原生資訊,將介面和 Java 的 POJOs(Plain Old Java Objects,普通的 Java物件)對映成資料庫中的記錄。

對映器(mappers)

你需要告訴 MyBatis 到哪裡去找對映檔案。你可以使用相對於類路徑的資源參照, 或完全限定資源定位符(包括 file:/// 的 URL),或類名和包名等。

<!-- 使用相對於類路徑的資源參照 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

<!-- 使用對映器介面實現類的完全限定類名  -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<!-- 將包內的對映器介面實現全部註冊為對映器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

typeAliases 型別別名減少類完全限制名的冗餘

型別別名是為 Java 型別設定一個短的名字。它只和 XML 設定有關,存在的意義僅在於用來減少類完全限定名的冗餘。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

當這樣設定時,Blog可以用在任何使用domain.blog.Blog的地方。

也可以指定一個包名,MyBatis 會在包名下面搜尋需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

處理列舉型別

若想對映列舉型別 Enum,則需要從 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中選一個來使用。

比如說我們想儲存取近似值時用到的舍入模式。預設情況下,MyBatis 會利用 EnumTypeHandler 來把 Enum 值轉換成對應的名字。

注意 EnumTypeHandler 在某種意義上來說是比較特別的,其他的處理器只針對某個特定的類,而它不同,它會處理任意繼承了 Enum 的類。

不過,我們可能不想儲存名字,相反我們的 DBA 會堅持使用整形值程式碼。那也一樣輕而易舉: 在組態檔中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 這樣每個 RoundingMode 將通過他們的序數值來對映成對應的整形

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>

多行插入

 可以傳入一個Authors陣列或集合,並返回自動生成的主鍵。

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

重用 SQL 程式碼段,消除重複

sql這個元素可以被用來定義可重用的 SQL 程式碼段,可以包含在其他語句中。它可以被靜態地(在載入引數) 引數化. 不同的屬性值通過包含的範例變化. 比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

這個 SQL 片段可以被包含在其他語句中,例如:

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

字串替換#{}和${}的區別

預設情況下,使用 #{} 格式的語法會導致 MyBatis 建立 PreparedStatement 引數並安全地設定引數(就像使用 ? 一樣,會有'')。這樣做更安全,更迅速,通常也是首選做法。#{id},它告訴 MyBatis 建立一個預處理語句引數,通過 JDBC,這樣的一個引數在 SQL 中會由一個「?」來標識,並被傳遞到一個新的預處理語句中,就像這樣:

// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

不過有時你就是想直接在 SQL 語句中插入一個不跳脫的字串。比如,像 ORDER BY,你可以這樣來使用:

ORDER BY ${columnName}

這裡用${} MyBatis 不會修改或跳脫字串。

NOTE: 用這種方式接受使用者的輸入,並將其用於語句中的引數是不安全的,會導致潛在的 SQL 注入攻擊,因此要麼不允許使用者輸入這些欄位,要麼自行跳脫並檢驗。

Result Maps,表的列名和類的屬性名不對應怎麼處理?

MyBatis 會在幕後自動建立一個 ResultMap,再基於屬性名來對映列到 JavaBean 的屬性上。如果列名和屬性名沒有精確匹配,可以在 SELECT 語句中對列使用別名(這是一個 基本的 SQL 特性)來匹配標籤。比如:

方法一:

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

ResultMap 最優秀的地方在於,雖然你已經對它相當瞭解了,但是根本就不需要顯式地用到他們。 上面這些簡單的範例根本不需要下面這些繁瑣的設定。 出於示範的原因,讓我們來看看最後一個範例中,如果使用外部的 resultMap 會怎樣,這也是解決列名不匹配的另外一種方式。方法二:

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

MyBatis關聯的巢狀查詢

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>

在上面的範例中你可以看到部落格的作者關聯代表著「authorResult」結果對映來載入作 者範例。

非常重要: id元素在巢狀結果對映中扮演著非 常重要的角色。你應該總是指定一個或多個可以唯一標識結果的屬性。實際上如果你不指定它的話, MyBatis仍然可以工作,但是會有嚴重的效能問題。在可以唯一標識結果的情況下, 儘可能少的選擇屬性。主鍵是一個顯而易見的選擇(即使是複合主鍵)。

MyBatis集合的巢狀查詢

繼續上面的範例,一個部落格只有一個作者。但是部落格有很多文章。在部落格類中, 這可以由下面這樣的寫法來表示:

private List<Post> posts;

它和關聯完全相同,除了它應用了一個「ofType」屬性

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>

動態 SQL,如何優雅的構建動態Sql

MyBatis 的強大特性之一便是它的動態 SQL。如果你有使用 JDBC 或其它類似框架的經驗,你就能體會到根據不同條件拼接 SQL 語句的痛苦。例如拼接時要確保不能忘記新增必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL 這一特性可以徹底擺脫這種痛苦。

Where 構建動態查詢條件

where 元素只會在至少有一個子元素的條件返回 SQL 子句的情況下才去插入「WHERE」子句。而且,若語句的開頭為「AND」或「OR」,where 元素也會將它們去除。

<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 元素沒有按正常套路出牌,我們可以通過自定義 trim 元素來客製化 where 元素的功能。比如,和 where 元素等價的自定義 trim 元素為下面程式碼:(prefixOverrides 屬性會忽略通過管道分隔的文字序列(注意此例中的空格也是必要的)。它的作用是移除所有指定在 prefixOverrides 屬性中的內容,並且插入 prefix 屬性中指定的內容。)

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

choose, when, otherwise 從條件中選其一項

有時我們不想應用到所有的條件語句,而只想從中擇其一項。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。例如:提供了「title」就按「title」查詢,提供了「author」就按「author」查詢的情形,若兩者都沒有提供,就返回所有符合條件的 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>

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>

foreach 構建 IN 條件語句

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

bind 構建like 查詢

bind 元素可以從 OGNL 表示式中建立一個變數並將其繫結到上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>