深入理解MyBatis(二)--Mapper

GitHub:https://github.com/JDawnF

一、Mapper接口的工作原理

Mapper 接口,對應的關係如下:

  • 接口的全限名,就是映射文件中的 "namespace" 的值。

  • 接口的方法名,就是映射文件中 MappedStatement 的 "id" 值。

  • 接口方法內的參數,就是傳遞給 SQL 的參數。

Mapper 接口是沒有實現類的,當調用接口方法時,接口全限名 + 方法名拼接字符串作爲 key 值,可唯一定位一個對應的 MappedStatement 。舉例:com.mybatis3.mappers.StudentDao.findStudentById ,可以唯一找到 namespace 爲 com.mybatis3.mappers.StudentDao 下面 id爲 findStudentById 的 MappedStatement 。

總結來說,在 Mybatis 中,每一個 <select /><insert /><update /><delete /> 標籤,都會被解析爲一個 MappedStatement 對象。

Mapper 接口的實現類,通過 MyBatis 使用 JDK Proxy 自動生成其代理對象 Proxy ,而代理對象 Proxy 會攔截接口方法,從而“調用”對應的 MappedStatement 方法,最終執行 SQL ,返回執行結果。整體流程如下圖:

其中,SqlSession 在調用 Executor 之前,會獲得對應的 MappedStatement 方法。例如:DefaultSqlSession#select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) 方法,代碼如下:

 // DefaultSqlSession.java
 @Override
 public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
     try {
         // 獲得 MappedStatement 對象
         MappedStatement ms = configuration.getMappedStatement(statement);
         // 執行查詢
         executor.query(ms, wrapCollection(parameter), rowBounds, handler);
     } catch (Exception e) {
         throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
     } finally {
         ErrorContext.instance().reset();
     }
 }

Mapper 接口裏的方法,是不能重載的,因爲是全限名 + 方法名的保存和尋找策略。

二、Mapper接口綁定的實現方式

接口綁定有三種實現方式:

第一種,通過 XML Mapper 裏面寫 SQL 來綁定。在這種情況下,要指定 XML 映射文件裏面的 "namespace" 必須爲接口的全路徑名。

如:<mapper namespace="com.taotao.mapper.TbContentCategoryMapper" >

第二種,通過註解綁定,就是在接口的方法上面加上 @Select@Update@Insert@Delete 註解,裏面包含 SQL 語句來綁定。

第三種,是第二種的特例,也是通過註解綁定,在接口的方法上面加上 @SelectProvider@UpdateProvider@InsertProvider@DeleteProvider 註解,通過 Java 代碼,生成對應的動態 SQL 。

獲取自動生成的(主)鍵值

不同的數據庫,獲取自動生成的(主)鍵值的方式是不同的。

MySQL 有兩種方式,但是自增主鍵,代碼如下:

 <!--方式一,使用 useGeneratedKeys + keyProperty 屬性-->
 <insert id="insert" parameterType="Person" useGeneratedKeys="true" keyProperty="id">
     INSERT INTO person(name, pswd)
     VALUE (#{name}, #{pswd})
 </insert>
     
 <!--方式二,使用 <selectKey /> 標籤,返回主鍵-->
 <insert id="insert" parameterType="Person" useGeneratedKeys="true" keyProperty="id">
     <selectKey keyProperty="id" resultType="long" order="AFTER">
         SELECT LAST_INSERT_ID()
     </selectKey>
     INSERT INTO person(name, pswd)
     VALUE (#{name}, #{pswd})
 </insert>

SELECT LAST_INSERT_ID()也可以換成select @@identity

  • resultType:指出獲取的主鍵的類型。

  • keyProperty:指出主鍵在 Java 類中對應的屬性名。此處會將獲取的主鍵值直接封裝到被插入的實體類對象中。

  • order:指出 id 的生成相對於 insert 語句的執行是在前還是在後。MySql 數據庫表 中的 id,均是先執行 insert 語句,而後生成 id,所以需要設置爲 AFTER;Oracle 數據庫表中 的 id,則是在 insert 執行之前先生成,所以需要設置爲 BEFORE。當前的 MyBatis 版本,不指 定 order 屬性,則會根據所用 DBMS,自動選擇其值。

無論插入操作是提交還是回滾,DB 均會爲 insert 的記錄分配 id,即使發生回滾,這個 id 也已經被使用。後面再插入並提交的記錄數據,此 id 已經不能再使用,被分配的 id 是跳 過此 id 後的 id。

另外,從前面<selectKey/>中 order 屬性值的設置講解可知,MySql 在 insert 語句執行後 會自動生成該新插入記錄的主鍵值。主鍵值的生成只與 insert 語句是否執行有關,而與最終 是否提交無關。

Oracle 有兩種方式,序列觸發器。基於序列,根據 <selectKey /> 執行的時機,也有兩種方式,代碼如下:

<!--這個是創建表的自增序列
CREATE SEQUENCE student_sequence
INCREMENT BY 1
NOMAXVALUE
NOCYCLE
CACHE 10;
-->
<!--方式一,使用 `<selectKey />` 標籤 + BEFORE-->
<insert id="add" parameterType="Student">
  <selectKey keyProperty="student_id" resultType="int" order="BEFORE">
      select student_sequence.nextval FROM dual
    </selectKey>
    
     INSERT INTO student(student_id, student_name, student_age)
     VALUES (#{student_id},#{student_name},#{student_age})
</insert>

<!--方式二,使用 `<selectKey />` 標籤 + AFTER-->
<insert id="save" parameterType="com.threeti.to.ZoneTO" >
    <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER" >
      SELECT SEQ_ZONE.CURRVAL AS id FROM dual
    </selectKey>
    
    INSERT INTO TBL_ZONE (ID, NAME ) 
    VALUES (SEQ_ZONE.NEXTVAL, #{name,jdbcType=VARCHAR})
</insert>

三、在 Mapper 中如何傳遞多個參數

第一種,直接使用 Map 集合封裝,裝載多個參數進行傳遞。代碼如下:

// 調用方法
Map<String, Object> map = new HashMap();
map.put("start", start);
map.put("end", end);
return studentMapper.selectStudents(map);

// Mapper 接口
List<Student> selectStudents(Map<String, Object> map);

// Mapper XML 代碼
<select id="selectStudents" parameterType="Map" resultType="Student">
    SELECT * 
    FROM students 
    LIMIT #{start}, #{end}
</select>

第二種,保持傳遞多個參數,使用 @Param 註解。(推薦使用)代碼如下:

 // 調用方法
 return studentMapper.selectStudents(0, 10);
 ​
 // Mapper 接口
 List<Student> selectStudents(@Param("start") Integer start, @Param("end") Integer end);
 ​
 // Mapper XML 代碼
 <select id="selectStudents" resultType="Student">
     SELECT * 
     FROM students 
     LIMIT #{start}, #{end}
 </select>

第三種,保持傳遞多個參數,不使用 @Param 註解。代碼如下:

 // 調用方法
 return studentMapper.selectStudents(0, 10);
 ​
 // Mapper 接口
 List<Student> selectStudents(Integer start, Integer end);
 ​
 // Mapper XML 代碼
 <select id="selectStudents" resultType="Student">
     SELECT * 
     FROM students 
     LIMIT #{param1}, #{param2}
 </select>

其中,按照參數在方法方法中的位置,從 1 開始,逐個爲 #{param1}#{param2}#{param3} 不斷向下排列,即通過#{param1}對應Mapper接口方法中的參數位置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章