1. 程式人生 > >筆記:MyBatis Mapper XML文件詳解 - 映射和參數

筆記:MyBatis Mapper XML文件詳解 - 映射和參數

gin server 頂級 ctp columns ref acl 目標 對象傳遞

MyBatis 的真正強大在於它的映射語句,也是它的魔力所在。由於它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。MyBatis 就是針對 SQL 構建的,並且比普通的方法做的更好。

SQL 映射文件有很少的幾個頂級元素(按照它們應該被定義的順序):

  • cache – 給定命名空間的緩存配置。
  • cache-ref – 其他命名空間緩存配置的引用。
  • resultMap – 是最復雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
  • sql – 可被其他語句引用的可重用語句塊。
  • insert – 映射插入語句
  • update – 映射更新語句
  • delete – 映射刪除語句
  • select – 映射查詢語句
  1. select(查詢)

    查詢語句是 MyBatis 中最常用的元素之一,光能把數據存到數據庫中價值並不大,如果還能重新取出來才有用,多數應用也都是查詢比修改要頻繁。對每個插入、更新或刪除操作,通常對應多個查詢操作。這是 MyBatis 的基本原則之一,也是將焦點和努力放到查詢和結果映射的原因。簡單查詢的 select 元素是非常簡單的。比如:

    <select id="selectPerson" parameterType="int" resultType="hashmap">

    ????SELECT * FROM PERSON WHERE ID = #{id}

    </select>

    這個語句被稱作 selectPerson,接受一個 int(或 Integer)類型的參數,並返回一個 HashMap 類型的對象,其中的鍵是列名,值便是結果行中的對應值。

    註意參數符號:

    #{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);

    當然,這需要很多單獨的 JDBC 的代碼來提取結果並將它們映射到對象實例中,這就是 MyBatis 節省你時間的地方。我們需要深入了解參數和結果映射,細節部分我們下面來了解,select 元素有很多屬性允許你配置,來決定每條語句的作用細節:

屬性

描述

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 中的一個,默認值為 unset (依賴驅動)。

databaseId

如果配置了 databaseIdProvider,MyBatis 會加載所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。

resultOrdered

這個設置僅針對嵌套結果 select 語句適用:如果為 true,就是假設包含了嵌套結果集或是分組了,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。這就使得在獲取嵌套的結果集的時候不至於導致內存不夠用。默認值:false

resultSets

這個設置僅對多結果集的情況適用,它將列出語句執行後返回的結果集並每個結果集給一個名稱,名稱是逗號分隔的。

  1. 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 可以通過 TypeHandler 推斷出具體傳入語句的參數,默認值為 unset。

flushCache

將其設置為 true,任何時候只要語句被調用,都會導致本地緩存和二級緩存都會被清空,默認值:true(對應插入、更新和刪除語句)。

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 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。

首先,如果你的數據庫支持自動生成主鍵的字段(比如 MySQL 和 SQL Server),那麽你可以設置 useGeneratedKeys="true",然後再把 keyProperty 設置到目標屬性上就OK了。例如,如果上面的 Author 表已經對 id 使用了自動生成的列類型,那麽語句可以修改為:

<insert id="insertAuthor" useGeneratedKeys="true"

????????keyProperty="id">

????insert into Author (username,password,email,bio)

????values (#{username},#{password},#{email},#{bio})

</insert>

如果你的數據庫還支持多行插入, 你也可以傳入一個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>

對於不支持自動生成類型的數據庫或可能不支持自動生成主鍵 JDBC 驅動來說,MyBatis 有另外一種方法來生成主鍵。 這裏有一個簡單(甚至很傻)的示例,它可以生成一個隨機 ID(你最好不要這麽做,但這裏展示了 MyBatis 處理問題的靈活性及其所關心的廣度):

<insert id="insertAuthor">

????<selectKey keyProperty="id" resultType="int" order="BEFORE">

????????select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1

????</selectKey>

????insert into Author

????????(id, username, password, email,bio, favourite_section)

????values

????????(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})

</insert>

在上面的示例中,selectKey 元素將會首先運行,Author 的 id 會被設置,然後插入語句會被調用。這給你了一個和數據庫中來處理自動生成的主鍵類似的行為,避免了使 Java 代碼變得復雜。 selectKey 元素描述如下:

屬性

描述

keyProperty

selectKey 語句結果應該被設置的目標屬性。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。

keyColumn

匹配屬性的返回結果集中的列名稱。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。

resultType

結果的類型。MyBatis 通常可以推算出來,但是為了更加確定寫上也不會有什麽問題。MyBatis 允許任何簡單類型用作主鍵的類型,包括字符串。如果希望作用於多個生成的列,則可以使用一個包含期望屬性的 Object 或一個 Map。

order

這可以被設置為 BEFORE 或 AFTER。如果設置為 BEFORE,那麽它會首先選擇主鍵,設置 keyProperty 然後執行插入語句。如果設置為 AFTER,那麽先執行插入語句,然後是 selectKey 元素 - 這和像 Oracle 的數據庫相似,在插入語句內部可能有嵌入索引調用。

statementType

與前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 語句的映射類型,分別代表 PreparedStatement 和 CallableStatement 類型。

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

    屬性值可以用於包含的refid屬性或者包含的字句裏面的屬性值,例如:

    <sql id="sometable">

    ????${prefix}Table

    </sql>

    <sql id="someinclude">

    ????from

    ????????<include refid="${include_target}"/>

    </sql>

    <select id="select" resultType="map">

    ????select

    ????????field1, field2, field3

    ????<include refid="someinclude">

    ????????<property name="prefix" value="Some"/>

    ????????<property name="include_target" value="sometable"/>

    ????</include>

    </select>

  2. 參數(Parameters)

    前面的所有語句中你所見到的都是簡單參數的例子,實際上參數是 MyBatis 非常強大的元素,對於簡單的做法,大概 90% 的情況參數都很少,比如:

    ????<select id="selectUsers" resultType="User">

    ???? select id, username, password

    ???? from users

    ???? where id = #{id}

    ????</select>

    上面的這個示例說明了一個非常簡單的命名參數映射。參數類型被設置為 int,這樣這個參數就可以被設置成任何內容。原生的類型或簡單數據類型(比如整型和字符串)因為沒有相關屬性,它會完全用參數值來替代。然而,如果傳入一個復雜的對象,行為就會有一點不同了。比如:

    ????<insert id="insertUser" parameterType="User">

    ???? insert into users (id, username, password)

    ???? values (#{id}, #{username}, #{password})

    ????</insert>

    如果 User 類型的參數對象傳遞到了語句中,id、username 和 password 屬性將會被查找,然後將它們的值傳入預處理語句的參數中。

    這點對於向語句中傳參是比較好的而且又簡單,不過參數映射的功能遠不止於此。首先,像 MyBatis 的其他部分一樣,參數也可以指定一個特殊的數據類型,配置示例:

    ????#{property,javaType=int,jdbcType=NUMERIC}

    像 MyBatis 的typeHandler部分一樣,javaType 通常可以從參數對象中來去確定,前提是只要對象不是一個 HashMap。那麽 javaType 應該被確定來保證使用正確類型處理器。為了以後定制類型處理方式,你也可以指定一個特殊的類型處理器類(或別名),比如:

    #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

    對於數值類型,還有一個小數保留位數的設置,來確定小數點後保留的位數。

    #{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

    最後,mode 屬性允許你指定 IN,OUT 或 INOUT 參數。如果參數為 OUT 或 INOUT,參數對象屬性的真實值將會被改變,就像你在獲取輸出參數時所期望的那樣。如果 mode 為 OUT(或 INOUT),而且 jdbcType 為 CURSOR(也就是 Oracle 的 REFCURSOR),你必須指定一個 resultMap 來映射結果集到參數類型。要註意這裏的 javaType 屬性是可選的,如果左邊的空白是 jdbcType 的 CURSOR 類型,它會自動地被設置為結果集。 盡管所有這些強大的選項很多時候你只簡單指定屬性名,其他的事情 MyBatis 會自己去推斷,最多你需要為可能為空的列名指定 jdbcType。

  • 字符串替換

    默認情況下,使用#{}格式的語法會導致 MyBatis 創建預處理語句屬性並安全地設置值(比如?)。這樣做更安全,更迅速,通常也是首選做法,不過有時你只是想直接在 SQL 語句中插入一個不改變的字符串。比如,像 ORDER BY,你可以這樣來使用:

    ORDER BY ${columnName}

    這裏 MyBatis 不會修改或轉義字符串,以這種方式接受從用戶輸出的內容並提供給語句中不變的字符串是不安全的,會導致潛在的 SQL 註入攻擊,因此要麽不允許用戶輸入這些字段,要麽自行轉義並檢驗。

  1. 多參數傳遞

    MyBatis中的映射語句有一個parameterType屬性來制定輸入參數的類型。如果我們想給映射語句傳入多個參數的話,我們可以將所有的輸入參數放到HashMap中,將HashMap傳遞給映射語句。MyBatis 還提供了另外一種傳遞多個輸入參數給映射語句的方法。假設我們想通過給定的name和email信息查找學生信息,定義查詢接口如下:

    Public interface StudentMapper

    {

    ????????List<Student> findAllStudentsByNameEmail(String name, String email);

    }

    MyBatis 支持將多個輸入參數傳遞給映射語句,並以#{param}的語法形式引用它們:

    <select id="findAllStudentsByNameEmail" resultMap="StudentResult">

    ????????select stud_id, name,email, phone from Students

    ????????????????where name=#{param1} and email=#{param2}

    </select>

    這裏#{param1}引用第一個參數name,而#{param2}引用了第二個參數email

    還可以使用 @Param 註解來給參數命名,定義查詢接口如下:

    Public interface StudentMapper

    {

    ????????List<Student> findAllStudentsByNameEmail(@Param("name") String name, @Param("email") String email);

    }

    MyBatis 配置文件可以直接使用命名的參數,如下配置:

    <select id="findAllStudentsByNameEmail" resultMap="StudentResult">

    ????????select stud_id, name,email, phone from Students

    ????????????????where name=#{name} and email=#{email}

    </select>

  2. 存儲過程和輸入參數

    存儲過程的調用必須設置 statementType="CALLABLE",並且存儲過程的調用語法為 { call proc_name} 如果有輸出參數,則還需要設置參數的 mode,示例如下:

    1. 存儲過程:

      CREATE DEFINER=`dev`@`%` PROCEDURE `procBuild_BillNo`(in prefix varchar(8),in serial varchar(10),out billNo varchar(20))

      BEGIN

      ????SET billNo = CONCAT(prefix,serial);

      END

    2. 方法定義:

      void BuildBillNo(HashMap<String,Object> map);

      註意:存儲過程的輸出參數,只能通過傳入的 HashMap 來獲取

    3. Mapper配置:

      <select id="BuildBillNo" parameterType="map" statementType="CALLABLE">

      ????????{call procBuild_BillNo(#{prefix},#{serial},#{billNo,mode=OUT,jdbcType=VARCHAR})}

      </select>

      註意:存儲過程調用,其 statementType 必須設置為 CALLABLE;存儲過程/函數的返回結果需要使用?#{參數}=call procName(?,?...) 參數接收,並且需要指定對應的mode為 OUT 類型和 jdbcType 類型

    4. Java代碼調用:

      HashMap<String, Object> map = new HashMap<String, Object>();

      map.put("prefix", "H");

      map.put("serial", "223232323");

      mapper.BuildBillNo(map);

      System.out.println("BuildBillNo is " + map.get("billNo"));

  3. 自動映射

    當自動映射查詢結果時,MyBatis會獲取sql返回的列名並在java類中查找相同名字的屬性(忽略大小寫)。 這意味著如果Mybatis發現了ID列和id屬性,Mybatis會將ID的值賦給id。 通常數據庫列使用大寫單詞命名,單詞間用下劃線分隔;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動映射,需要將 mapUnderscoreToCamelCase設置為true。 自動映射甚至在特定的result map下也能工作。在這種情況下,對於每一個result map,所有的ResultSet提供的列, 如果沒有被手工映射,則將被自動映射。自動映射處理完畢後手工映射才會被處理。

    通過配置 autoMappingBehavior 來設置自動映射等級,有三種自動映射等級:

  • NONE - 禁用自動映射。僅設置手動映射屬性。
  • PARTIAL - 將自動映射結果除了那些有內部定義內嵌結果映射的(joins)(默認)
  • FULL - 自動映射所有。

默認值是PARTIAL,這是有原因的。當使用FULL時,自動映射會在處理join結果時執行,並且join取得若幹相同行的不同實體數據,因此這可能導致非預期的映射。

? ?

筆記:MyBatis Mapper XML文件詳解 - 映射和參數