- 簡介
Mapper.xml映射文件中定義了操作數據庫的sql,每個sql是一個statement,映射文件是mybatis的核心。
映射文件中有很多屬性,常用的就是parameterType(輸入類型)、resultType(輸出類型)、resultMap()、rparameterMap()。
- parameterType(輸入類型)
1、#{}與${}
#{}實現的是向prepareStatement中的預處理語句中設置參數值,sql語句中#{}表示一個占位符即?。
1 <!-- 根據id查詢用戶信息 --> 2 <select id="findUserById" parameterType="int" resultType="user"> 3 select * from user where id = #{id} 4 </select>
使用占位符#{}可以有效防止sql注入,在使用時不需要關心參數值的類型,mybatis會自動進行java類型和jdbc類型的轉換。#{}可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,#{}括號中可以是value或其它名稱。
${}和#{}不同,通過${}可以將parameterType 傳入的內容拼接在sql中且不進行jdbc類型轉換, ${}可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,${}括號中只能是value。使用${}不能防止sql注入,但是有時用${}會非常方便,如下的例子:
1 <!-- 根據名稱模糊查詢用戶信息 --> 2 <select id="selectUserByName" parameterType="string" resultType="user"> 3 select * from user where username like '%${value}%' 4 </select>
如果本例子使用#{}則傳入的字符串中必須有%號,而%是人為拼接在參數中,顯然有點麻煩,如果采用${}在sql中拼接為%的方式則在調用mapper接口傳遞參數就方便很多。
//如果使用占位符號則必須人為在傳參數中加%
List<User> list = userMapper.selectUserByName("%張三%");
//如果使用${}原始符號則不用人為在參數中加%
List<User>list = userMapper.selectUserByName("張三");
再比如order by排序,如果將列名通過參數傳入sql,根據傳的列名進行排序,應該寫為:
ORDER BY ${columnName},如果使用#{}將無法實現此功能。
2、傳遞簡單類型
傳遞簡單類型只需要注意#{}與${}的使用就可以。
3、傳遞pojo對象
Mybatis使用ognl表達式解析對象字段的值,如下例子:
1 <!—傳遞pojo對象綜合查詢用戶信息 --> 2 <select id="findUserByUser" parameterType="user" resultType="user"> 3 select * from user where id=#{id} and username like '%${username}%' 4 </select>
測試代碼:
1 Public void testFindUserByUser()throws Exception{ 2 //獲取session 3 SqlSession session = sqlSessionFactory.openSession(); 4 //獲限mapper接口實例 5 UserMapper userMapper = session.getMapper(UserMapper.class); 6 //構造查詢條件user對象 7 User user = new User(); 8 user.setId(1); 9 user.setUsername("管理員"); 10 //傳遞user對象查詢用戶列表 11 List<User>list = userMapper.findUserByUser(user); 12 //關閉session 13 session.close(); 14 }
如果將username寫錯后,會報以下異常
1 org.apache.ibatis.exceptions.PersistenceException: 2 ### 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' 3 ### 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)以類組合的方式包裝起來。
1 public class QueryVo { 2 private User user; 3 //自定義用戶擴展類 4 private UserCustom custom; 5 public User getUser() { 6 return user; 7 } 8 public void setUser(User user) { 9 this.user = user; 10 } 11 public UserCustom getCustom() { 12 return custom; 13 } 14 public void setCustom(UserCustom custom) { 15 this.custom = custom; 16 } 17 }
(2)、 mapper.xml映射文件
1 <select id="findUser" parameterType="com.luchao.mybatis.first.po.QueryVo" resultType="com.luchao.mybatis.first.po.User"> 2 select * from user where username like '%${user.username}%' and sex = #{user.sex} 3 </select>
說明:mybatis底層通過ognl從pojo中獲取屬性值:#{user.username},user即是傳入的包裝對象的屬性。
5、傳遞hashmap
Sql映射文件定義如下:
1 <select id="findUserByIdMap" parameterType="hashmap" resultType="com.luchao.mybatis.first.po.User"> 2 select * from user where id = #{id} 3 </select>
測試代碼:
1 Public void testFindUserByHashmap()throws Exception{ 2 //獲取session 3 SqlSession session = sqlSessionFactory.openSession(); 4 //獲限mapper接口實例 5 UserMapper userMapper = session.getMapper(UserMapper.class); 6 //構造查詢條件Hashmap對象 7 HashMap<String, Object> map = new HashMap<String, Object>(); 8 map.put("id", 1); 9 //傳遞Hashmap對象查詢用戶列表 10 List<User>list = userMapper.findUserByHashmap(map); 11 //關閉session 12 session.close(); 13 }
傳遞的map中的key和sql中解析的key不一致。測試結果沒有報錯,只是通過key獲取值為空。這種用法一般用在POJO與數據庫字段不一致的時候。
- parameterMap和resultMap
resultType可以指定pojo將查詢結果映射為pojo,但需要pojo的屬性名和sql查詢的列名一致方可映射成功。如果sql查詢字段名和pojo的屬性名不一致,可以通過resultMap將字段名和屬性名作一個對應關系 ,resultMap實質上還需要將查詢結果映射到pojo對象中。resultMap可以實現將查詢結果映射為復雜類型的pojo,比如在查詢結果映射對象中包括pojo和list實現一對一查詢和一對多查詢。
下面是在數據庫列於POJO不一致的時候,將輸入參數映射到數據庫列的一種方式
1 <resultMap type="Book.dao.Book" id="BookResultMap"> 2 <id column="id" property="id"/> 3 <result column="name" property="bookName"/> 4 <result column="price" property="bookPrice"/> 5 </resultMap> 6 7 <!-- resultMap:resultMap的id ,bookName:resultMap的property,即實體類中的屬性 --> 8 <parameterMap type="Book.dao.Book" id="BookParameterMap"> 9 <parameter property="bookName" resultMap="BookResultMap" /> 10 <parameter property="bookPrice" resultMap="BookResultMap" /> 11 </parameterMap> 12 <!-- 保存一個Book --> 13 <insert id="saveBook" parameterMap="BookParameterMap"> 14 insert into BOOK_MANAGE 15 (ID,NAME,PRICE) 16 values 17 (Bookmanage_Seq.Nextval,#{bookName},#{bookPrice}) 18 </insert> 19 20 <!-- 根據ID修改Book --> 21 <update id="updatePersnById" parameterMap="BookParameterMap"> 22 update BOOK_MANAGE 23 set 24 NAME=#{bookName}, 25 PRICE=#{bookPrice} 26 WHERE id=#{id} 27 </update>
當查詢的結果與POJO名字不一致的時候,用resultMap來實現映射。
1 <resultMap type="user" id="userMap"> 2 <id column="id_" property="id" /> 3 <result column="username_" property="username" /> 4 </resultMap> 5 <select id="findUserMapById" parameterType="java.lang.Integer" resultMap="userMap" > 6 select id id_,username username_ from user where id = #{id} 7 </select>
<id />:此屬性表示查詢結果集的唯一標識,非常重要。如果是多個字段為復合唯一約束則定義多個<id />。
Property:表示person類的屬性。
Column:表示sql查詢出來的字段名。
Column和property放在一塊兒表示將sql查詢出來的字段映射到指定的pojo類屬性上。
<result />:普通結果,即pojo的屬性。
使用resultType進行輸出映射,只有查詢出來的列名和pojo中的屬性名一致,該列才可以映射成功。如果查詢出來的列名和pojo的屬性名不一致,通過定義一個resultMap對列名和pojo屬性名之間作一個映射關系。
- resultType(輸出類型)
1、輸出簡單類型
映射文件:
1 <select id="findUserCount" parameterType="user" resultType="int"> 2 select count(1) from user 3 </select>
輸出簡單類型必須查詢出來的結果集有一條記錄,最終將第一個字段的值轉換為輸出類型。使用session的selectOne可查詢單條記錄。
2、輸出pojo對象
映射文件
1 <!-- 根據id查詢用戶信息 --> 2 <select id="findUserById" parameterType="int" resultType="user"> 3 select * from user where id = #{id} 4 </select>
3、輸出pojo列表
映射文件:
1 <!-- 根據名稱模糊查詢用戶信息 --> 2 <select id="findUserByUsername" parameterType="string" resultType="user"> 3 select * from user where username like '%${value}%' 4 </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<pojo>對象作為方法返回值。
- 動態SQl
mybatis核心 對sql語句進行靈活操作,通過表達式進行判斷,對sql進行靈活拼接、組裝。對查詢條件進行判斷,如果輸入參數不為空才進行查詢條件拼接。
1、if
1 <!-- 傳遞pojo綜合查詢用戶信息 --> 2 <select id="findUserList" parameterType="user" resultType="user"> 3 select * from user 4 where 1=1 5 <if test="id!=null and id!=''"> 6 and id=#{id} 7 </if> 8 <if test="username!=null and username!=''"> 9 and username like '%${username}%' 10 </if> 11 </select>
2、Where
上面的配置也可以按如下來寫:
1 <select id="findUserList" parameterType="user" resultType="user"> 2 select * from user 3 <where> 4 <if test="id!=null and id!=''"> 5 and id=#{id} 6 </if> 7 <if test="username!=null and username!=''"> 8 and username like '%${username}%' 9 </if> 10 </where> 11 </select>
<where />可以自動處理第一個and。
3、foreach
向sql傳遞數組或List,mybatis使用foreach解析,如下:
如果我們需要傳入多個ID來查詢多個用戶的信息,這也就可以使用foreach。我們先考慮下如果只寫sql語句是如下:
1 SELECT * FROM USERS WHERE username LIKE '%張%' AND (id =10 OR id =89 OR id=16) 2 SELECT * FROM USERS WHERE username LIKE '%張%' id IN (10,89,16)
index:為數組的下標。
item:為數組每個元素的名稱,名稱隨意定義
open:循環開始
close:循環結束
separator:中間分隔輸出
通過POJO傳入List,映射文件如下:
1 <if test="ids!=null and ids.size>0"> 2 <foreach collection="ids" open=" and id in(" close=")" item="id" separator="," > 3 #{id} 4 </foreach> 5 </if>
或者:
1 <if test="ids!=null and ids.size>0"> 2 <foreach collection="ids" open=" and (" close=")" item="id" separator="," > 3 id = #{id} 4 </foreach> 5 </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中的屬性名。
傳遞單個數組(數組中是簡單類型)
配置文件如下:
1 <!-- 傳遞數組綜合查詢用戶信息 --> 2 <select id="selectUserByArray" parameterType="Object[]" resultType="user"> 3 select * from user 4 <where> 5 <!-- 傳遞數組 --> 6 <if test="array!=null"> 7 <foreach collection="array"index="index"item="item"open="and id in("separator=","close=")"> 8 #{item} 9 </foreach> 10 </if> 11 </where> 12 </select>
如果數組中是簡單類型則寫為#{item},不用再通過ognl獲取對象屬性值了。
Sql片段
Sql中可將重復的sql提取出來,使用時用include引用即可,最終達到sql重用的目的,如下:
映射文件如下:
1 <!-- 傳遞pojo綜合查詢用戶信息 --> 2 <select id="findUserList" parameterType="user" resultType="user"> 3 select * from user 4 <where> 5 <if test="id!=null and id!=''"> 6 and id=#{id} 7 </if> 8 <if test="username!=null and username!=''"> 9 and username like '%${username}%' 10 </if> 11 </where> 12 </select>
如果有多個statement都使用相同的查詢條件,那么就可以把查詢條件抽取出來作為單獨的Sql片段。
Sql片段配置:
1 <sql id="query_user_where"> 2 <if test="id!=null and id!=''"> 3 and id=#{id} 4 </if> 5 <if test="username!=null and username!=''"> 6 and username like '%${username}%' 7 </if> 8 </sql>
使用include引用:
1 <select id="findUserList" parameterType="user" resultType="user"> 2 select * from user 3 <where> 4 <include refid="query_user_where"/> 5 </where> 6 </select>
注意:如果引用其它mapper.xml的sql片段,則在引用時需要加上namespace,如下:<include refid="namespace.sql片段”/>
Mapper配置文件中常用的基本屬性就是這些,如果還有其他的特殊需求可以根據需要來進行修改配置。另外,在我們的設計中,如果已經定義好了基本的POJO在引用的時候可以在定義一個視圖查詢層的POJO在其中封裝基本的POJO和自定義的POJO(繼承基本的POJO),這樣就可以較容易實現擴展。當數據庫需求有變化的時候可以不修改基本POJO,而修改自定義的POJO,這樣就可以實現較好的擴展,而不影響其他模塊。如果前端需求有變動,可以通過修改前端的POJO來實現較小的改動。如下實現:
基本的POJO類型:
1 public class User { 2 private int id; 3 private String username;// 用戶姓名 4 private String sex;// 性別 5 private Date birthday;// 生日 6 private String address;// 地址 7 public int getId() { 8 return id; 9 } 10 public void setId(int id) { 11 this.id = id; 12 } 13 public String getUsername() { 14 return username; 15 } 16 public void setUsername(String username) { 17 this.username = username; 18 } 19 public String getSex() { 20 return sex; 21 } 22 public void setSex(String sex) { 23 this.sex = sex; 24 } 25 public Date getBirthday() { 26 return birthday; 27 } 28 public void setBirthday(Date birthday) { 29 this.birthday = birthday; 30 } 31 public String getAddress() { 32 return address; 33 } 34 public void setAddress(String address) { 35 this.address = address; 36 } 37 @Override 38 public String toString() { 39 // TODO Auto-generated method stub 40 return this.id+"-"+this.username+"-"+this.sex+"-"+this.address+"-"+this.birthday.toString(); 41 } 42 43 }
自定義的POJO繼承基本的POJO:
1 public class UserCustom extends User{ 2 3 }
如果我們的數據庫有變動,我們可以在UserCustom添加屬性,只滿足當前修改。
前端POJO實現:
1 public class QueryVo { 2 private User user; 3 //自定義用戶擴展類 4 private UserCustom custom; 5 public User getUser() { 6 return user; 7 } 8 public void setUser(User user) { 9 this.user = user; 10 } 11 public UserCustom getCustom() { 12 return custom; 13 } 14 public void setCustom(UserCustom custom) { 15 this.custom = custom; 16 } 17 }
可以滿足基本的需求,如果我們在查詢中需要加入其他查詢條件,如:商品、訂單等,只需要修改QueryVo,這樣就可以實現較好的可擴展性。