1. 程式人生 > >Mybatis學習--Mapper.xml對映檔案

Mybatis學習--Mapper.xml對映檔案

簡介
Mapper.xml對映檔案中定義了操作資料庫的sql,每個sql是一個statement,對映檔案是mybatis的核心。

對映檔案中有很多屬性,常用的就是parameterType(輸入型別)、resultType(輸出型別)、resultMap()、rparameterMap()。

parameterType(輸入型別)
1、#{}${}

#{}實現的是向prepareStatement中的預處理語句中設定引數值,sql語句中#{}表示一個佔位符即?。

<!-- 根據id查詢使用者資訊 -->
<select id="findUserById" parameterType="int" resultType="user">
     select * from user where id = #{id}
</select>        

使用佔位符#{}可以有效防止sql注入,在使用時不需要關心引數值的型別,mybatis會自動進行java型別和jdbc型別的轉換。#{}可以接收簡單型別值或pojo屬性值,如果parameterType傳輸單個簡單型別值,#{}括號中可以是value或其它名稱。
${}#{}不同,通過${}可以將parameterType 傳入的內容拼接在sql中且不進行jdbc型別轉換, ${}可以接收簡單型別值或pojo屬性值,如果parameterType傳輸單個簡單型別值,${}括號中只能是value。使用${}不能防止sql注入,但是有時用${}會非常方便,如下的例子:

<!-- 根據名稱模糊查詢使用者資訊 -->
<select id="selectUserByName" parameterType="string" resultType="user">
     select * from user where username like '%${value}%'
</select>

如果本例子使用#{}則傳入的字串中必須有%號,而%是人為拼接在引數中,顯然有點麻煩,如果採用${}在sql中拼接為%的方式則在呼叫mapper介面傳遞引數就方便很多。

如果使用佔位符號則必須人為在傳引數中加%

List list = userMapper.selectUserByName("%張三%");

如果使用${}原始符號則不用人為在引數中加%

Listlist = userMapper.selectUserByName(“張三”);

再比如order by排序,如果將列名通過引數傳入sql,根據傳的列名進行排序,應該寫為:

ORDER BY ${columnName},如果使用#{}將無法實現此功能。

2、傳遞簡單型別

傳遞簡單型別只需要注意#{}與${}的使用就可以。

3、傳遞pojo物件

Mybatis使用ognl表示式解析物件欄位的值,如下例子:

<!—傳遞pojo物件綜合查詢使用者資訊 -->
<select id="findUserByUser" parameterType="user" resultType="user">
     select * from user where id=#{id} and username like '%${username}%'
 </select>

測試程式碼:

Public void testFindUserByUser()throws Exception{
        //獲取session
        SqlSession session = sqlSessionFactory.openSession();
        //獲限mapper介面例項
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //構造查詢條件user物件
        User user = new User();
        user.setId(1);
        user.setUsername("管理員");
        //傳遞user物件查詢使用者列表
        List<User>list = userMapper.findUserByUser(user);
        //關閉session
        session.close();
}

如果將username寫錯後,會報以下異常

org.apache.ibatis.exceptions.PersistenceException:
Error querying database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘dusername’ in ‘class com.luchao.mybatis.first.po.User’
Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘dusername’ in ‘class com.luchao.mybatis.first.po.User’

可以看出MyBatis是通過反射來講java物件對映到查詢引數中的。

4、傳遞pojo包裝物件

開發中通過pojo傳遞查詢條件 ,查詢條件是綜合的查詢條件,不僅包括使用者查詢條件還包括其它的查詢條件(比如將使用者購買商品資訊也作為查詢條件),這時可以使用包裝物件傳遞輸入引數。

(1)、定義包裝物件

定義包裝物件將查詢條件(pojo)以類組合的方式包裝起來。

public class QueryVo {
    private User user;
    //自定義使用者擴充套件類
    private UserCustom custom;
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public UserCustom getCustom() {
        return custom;
    }
    public void setCustom(UserCustom custom) {
        this.custom = custom;
    }
}

(2)mapper.xml對映檔案

<select id="findUser" parameterType="com.luchao.mybatis.first.po.QueryVo" resultType="com.luchao.mybatis.first.po.User">
     select * from user where username like '%${user.username}%' and sex = #{user.sex}
</select>

說明:mybatis底層通過ognl從pojo中獲取屬性值:#{user.username},user即是傳入的包裝物件的屬性。

5、傳遞hashmap

Sql對映檔案定義如下:

<select id="findUserByIdMap" parameterType="hashmap" resultType="com.luchao.mybatis.first.po.User">
     select * from user where id = #{id}
</select>

測試程式碼:

Public void testFindUserByHashmap()throws Exception{
        //獲取session
        SqlSession session = sqlSessionFactory.openSession();
        //獲限mapper介面例項
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //構造查詢條件Hashmap物件
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("id", 1);        
        //傳遞Hashmap物件查詢使用者列表
        List<User>list = userMapper.findUserByHashmap(map);
        //關閉session
        session.close();
}

傳遞的map中的key和sql中解析的key不一致。測試結果沒有報錯,只是通過key獲取值為空。這種用法一般用在POJO與資料庫欄位不一致的時候。

parameterMap和resultMap
resultType可以指定pojo將查詢結果對映為pojo,但需要pojo的屬性名和sql查詢的列名一致方可對映成功。如果sql查詢欄位名和pojo的屬性名不一致,可以通過resultMap將欄位名和屬性名作一個對應關係 ,resultMap實質上還需要將查詢結果對映到pojo物件中。resultMap可以實現將查詢結果對映為複雜型別的pojo,比如在查詢結果對映物件中包括pojo和list實現一對一查詢和一對多查詢。

下面是在資料庫列於POJO不一致的時候,將輸入引數對映到資料庫列的一種方式

<resultMap type="Book.dao.Book" id="BookResultMap">
  <id column="id" property="id"/>
  <result column="name" property="bookName"/>
  <result column="price" property="bookPrice"/>
 </resultMap>
 
 <!-- resultMap:resultMap的id ,bookName:resultMap的property,即實體類中的屬性 -->
 <parameterMap type="Book.dao.Book" id="BookParameterMap">
  <parameter property="bookName" resultMap="BookResultMap" />  
  <parameter property="bookPrice" resultMap="BookResultMap" />  
 </parameterMap>
<!-- 儲存一個Book -->
 <insert id="saveBook" parameterMap="BookParameterMap">
  insert into BOOK_MANAGE
   (ID,NAME,PRICE)
    values
   (Bookmanage_Seq.Nextval,#{bookName},#{bookPrice})
 </insert>
 
 <!-- 根據ID修改Book -->
 <update id="updatePersnById" parameterMap="BookParameterMap">
  update BOOK_MANAGE 
   set 
    NAME=#{bookName},
    PRICE=#{bookPrice}
   WHERE id=#{id}
 </update>

當查詢的結果與POJO名字不一致的時候,用resultMap來實現對映。

<resultMap type="user" id="userMap">
        <id column="id_" property="id" />
        <result column="username_" property="username" />
</resultMap>
<select id="findUserMapById" parameterType="java.lang.Integer" resultMap="userMap" >
        select id id_,username username_ from user where id = #{id}
</select>

:此屬性表示查詢結果集的唯一標識,非常重要。如果是多個欄位為複合唯一約束則定義多個。

Property:表示person類的屬性。

Column:表示sql查詢出來的欄位名。

Column和property放在一塊兒表示將sql查詢出來的欄位對映到指定的pojo類屬性上。

<result />:普通結果,即pojo的屬性。

使用resultType進行輸出對映,只有查詢出來的列名和pojo中的屬性名一致,該列才可以對映成功。如果查詢出來的列名和pojo的屬性名不一致,通過定義一個resultMap對列名和pojo屬性名之間作一個對映關係。

resultType(輸出型別)
  1、輸出簡單型別

對映檔案:

<select id="findUserCount" parameterType="user" resultType="int">
     select count(1) from user
</select>

輸出簡單型別必須查詢出來的結果集有一條記錄,最終將第一個欄位的值轉換為輸出型別。使用session的selectOne可查詢單條記錄。

2、輸出pojo物件

對映檔案

  <!-- 根據id查詢使用者資訊 -->
<select id="findUserById" parameterType="int" resultType="user">
     select * from user where id = #{id}
 </select>    

3、輸出pojo列表

對映檔案:

<!-- 根據名稱模糊查詢使用者資訊 -->
<select id="findUserByUsername" parameterType="string" resultType="user">
     select * from user where username like '%${value}%'
</select>

注意:MyBatis會根據Mapper介面方法的返回型別來選擇呼叫selectOne還是selectList方法,如果是List這呼叫selectList方法,如果是POJO則呼叫selectOne方法。

4、輸出hashmap

輸出pojo物件可以改用hashmap輸出型別,將輸出的欄位名稱作為map的key,value為欄位值。

resultType總結:

輸出pojo物件和輸出pojo列表在sql中定義的resultType是一樣的。返回單個pojo物件要保證sql查詢出來的結果集為單條,內部使用session.selectOne方法呼叫,mapper介面使用pojo物件作為方法返回值。返回pojo列表表示查詢出來的結果集可能為多條,內部使用session.selectList方法,mapper介面使用List物件作為方法返回值。
  
動態SQl
mybatis核心 對sql語句進行靈活操作,通過表示式進行判斷,對sql進行靈活拼接、組裝。對查詢條件進行判斷,如果輸入引數不為空才進行查詢條件拼接。

1、if

<!-- 傳遞pojo綜合查詢使用者資訊 -->
    <select id="findUserList" parameterType="user" resultType="user">
        select * from user 
        where 1=1 
        <if test="id!=null and id!=''">
        and id=#{id}
        </if>
        <if test="username!=null and username!=''">
        and username like '%${username}%'
        </if>
</select>

2、Where

上面的配置也可以按如下來寫:

<select id="findUserList" parameterType="user" resultType="user">
        select * from user 
        <where>
        <if test="id!=null and id!=''">
        and id=#{id}
        </if>
        <if test="username!=null and username!=''">
        and username like '%${username}%'
        </if>
        </where>
</select>

可以自動處理第一個and。

3、foreach

向sql傳遞陣列或List,mybatis使用foreach解析,如下:

如果我們需要傳入多個ID來查詢多個使用者的資訊,這也就可以使用foreach。我們先考慮下如果只寫sql語句是如下:

SELECT * FROM USERS WHERE username LIKE '%張%' AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE '%張%'  id IN (10,89,16)

index:為陣列的下標。

item:為陣列每個元素的名稱,名稱隨意定義

open:迴圈開始

close:迴圈結束

separator:中間分隔輸出

通過POJO傳入List,對映檔案如下:

<if test="ids!=null and ids.size>0">
    <foreach collection="ids" open=" and id in(" close=")" item="id" separator="," >                 #{id}
    </foreach>
 </if>

或者:

<if test="ids!=null and ids.size>0">
    <foreach collection="ids" open=" and (" close=")" item="id" separator="," >
        id = #{id}
    </foreach>
</if>

傳遞單個List

傳遞List型別在編寫mapper.xml沒有區別,唯一不同的是隻有一個List引數時它的引數名為list。

配置檔案如下:

<select id="selectUserByList" parameterType="java.util.List" resultType="user">
        select * from user 
        <where>
        <!-- 傳遞List,List中是pojo -->
        <if test="list!=null">
        <foreach collection="list" item="item" open="and id in("separator=","close=")">
            #{item.id} 
        </foreach>
        </if>
        </where>
</select>

傳遞單個數組(陣列中是POJO)

<!-- 傳遞陣列綜合查詢使用者資訊 -->
    <select id="selectUserByArray" parameterType="Object[]" resultType="user">
        select * from user 
        <where>
        <!-- 傳遞陣列 -->
        <if test="array!=null">
        <foreach collection="array" index="index" item="item" open="and id in("separator=","close=")">
            #{item.id} 
        </foreach>
        </if>
        </where>
</select>

sql只接收一個數組引數,這時sql解析引數的名稱mybatis固定為array,如果陣列是通過一個pojo傳遞到sql則引數的名稱為pojo中的屬性名。

傳遞單個數組(陣列中是簡單型別)

配置檔案如下:

<!-- 傳遞陣列綜合查詢使用者資訊 -->
<select id="selectUserByArray" parameterType="Object[]" resultType="user">
        select * from user 
        <where>
        <!-- 傳遞陣列 -->
        <if test="array!=null">
        <foreach collection="array"index="index"item="item"open="and id in("separator=","close=")">
            #{item} 
        </foreach>
        </if>
        </where>
</select>

如果陣列中是簡單型別則寫為#{item},不用再通過ognl獲取物件屬性值了。

Sql片段

Sql中可將重複的sql提取出來,使用時用include引用即可,最終達到sql重用的目的,如下:

對映檔案如下:

<!-- 傳遞pojo綜合查詢使用者資訊 -->
    <select id="findUserList" parameterType="user" resultType="user">
        select * from user 
        <where>
        <if test="id!=null and id!=''">
        and id=#{id}
        </if>
        <if test="username!=null and username!=''">
        and username like '%${username}%'
        </if>
        </where>
    </select>

如果有多個statement都使用相同的查詢條件,那麼就可以把查詢條件抽取出來作為單獨的Sql片段。

Sql片段配置:

<sql id="query_user_where">
    <if test="id!=null and id!=''">
        and id=#{id}
    </if>
    <if test="username!=null and username!=''">
        and username like '%${username}%'
    </if>
</sql>

使用include引用:

<select id="findUserList" parameterType="user" resultType="user">
        select * from user 
        <where>
        <include refid="query_user_where"/>
        </where>
</select>

注意:如果引用其它mapper.xml的sql片段,則在引用時需要加上namespace,如下:<include refid="namespace.sql片段”/>

Mapper配置檔案中常用的基本屬性就是這些,如果還有其他的特殊需求可以根據需要來進行修改配置。另外,在我們的設計中,如果已經定義好了基本的POJO在引用的時候可以在定義一個檢視查詢層的POJO在其中封裝基本的POJO和自定義的POJO(繼承基本的POJO),這樣就可以較容易實現擴充套件。當資料庫需求有變化的時候可以不修改基本POJO,而修改自定義的POJO,這樣就可以實現較好的擴充套件,而不影響其他模組。如果前端需求有變動,可以通過修改前端的POJO來實現較小的改動。如下實現:

基本的POJO型別:

public class User {
    private int id;
    private String username;// 使用者姓名
    private String sex;// 性別
    private Date birthday;// 生日
    private String address;// 地址
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return this.id+"-"+this.username+"-"+this.sex+"-"+this.address+"-"+this.birthday.toString();
    }
    
}

自定義的POJO繼承基本的POJO:

public class UserCustom extends User{
}

如果我們的資料庫有變動,我們可以在UserCustom新增屬性,只滿足當前修改。

前端POJO實現:

public class QueryVo {
    private User user;
    //自定義使用者擴充套件類
    private UserCustom custom;
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public UserCustom getCustom() {
        return custom;
    }
    public void setCustom(UserCustom custom) {
        this.custom = custom;
    }
}

可以滿足基本的需求,如果我們在查詢中需要加入其他查詢條件,如:商品、訂單等,只需要修改QueryVo,這樣就可以實現較好的可擴充套件性。