Mybatis的sql mapper映射文件詳解
1、sql 映射文件常見關鍵字
sql 映射文件中需要注意的一些關鍵字:
- parameterType: 指定要求輸入參數的類型,可以指定為基本數據類型(如 int、float 等)、包裝數據類型(如 String、Interger 等)以及用戶自己編寫的 JavaBean 封裝類。不管參數是基本數據類型還是JavaBean,parameterType 都可以不填,mybatis 通過反射機制能夠自動識別到調用 sql 語句的參數并進行填充。一般情況下可以不指定該關鍵字。
- resultType: 指定輸出的結果類型。 resultType 就是用來指定數據庫返回的信息對應的 Java 的數據類型。
- #{}:表示參數占位符,在大括號中書寫參數名稱來接受對應參數。#{} 實際上是通過 prepareStatement 對象來執行 sql 的,可以避免 SQL 注入問題。當傳入的參數是java對象時,#{} 會自動根據參數名來獲取對象的屬性值。當 “#{}” 接受的是基本數據類型時可以用
value或者其他任意名稱來獲取,比如#{value}。 - ${}:${} 為字符串替換,是通過拼接字符串的方式來拼接成 sql 的。使用 “${}” 拼接符號拼接 SQL ,會引起 SQL 注入,所以一般不建議使用 “${}”。在 SQL 配置中,有時候需要拼接 sql 語句(例如模糊查詢時),用 “#{}” 是無法達到目的的。
mybatis 的 sql 語句如果需要接收多個參數,我們可以通過以下方式來傳遞多個參數:
- 通過 java 對象進行傳參。比如一個 java 類的對象
- 使用 @param 傳遞命名參數,sql 語句中通過 #{自定義參數名} 的形式來獲取參數
- 可以使用 #{arg0} #{arg1} 的形式來根據參數的位置來獲取參數。注意,mybatis 3.4之前的版本是通過 #{0}、#{1} 這種形式來使用的
- 通過 map 集合傳遞參數
2、關鍵字#{}和${}
#{}:表示參數占位符,在大括號中書寫參數名稱來接受對應參數。#{} 實際上是通過 prepareStatement 對象來執行 sql 的,可以避免 SQL 注入問題。當傳入的參數是java對象時,#{} 會自動根據參數名來獲取對象的屬性值。當 “#{}” 接受的是基本數據類型時可以用 value 或者其他任意名稱來獲取,比如#{value}。
${}:${} 為字符串替換,是通過拼接字符串的方式來拼接成 sql 的。使用 “${}” 拼接符號拼接 SQL ,會引起 SQL 注入,所以一般不建議使用 “${}”。在 SQL 配置中,有時候需要拼接 sql 語句(例如模糊查詢時),用 “#{}” 是無法達到目的的。
2.1、#{}和${}的區別
在MyBatis 的映射配置文件中,動態傳遞參數有兩種方式:#{} 占位符、${} 拼接符。
兩者的區別如下:
- #{} 為參數占位符 ?,即sql 預編譯;${} 為字符串替換,即 sql 拼接
- #{} 為動態解析 -> 預編譯 -> 執行;${} 為動態解析 -> 編譯 -> 執行
- #{} 的變量替換是在DBMS 中;${} 的變量替換是在 DBMS 外
- 變量替換后,#{} 對應的變量自動加上單引號 ' ';變量替換后,${} 對應的變量不會加上單引號 ' '
- #{} 能防止sql 注入;${} 不能防止sql 注入
兩者使用示例:
# 示例#{}的使用,
# 定義語法
select * from t_user where uid=#{uid}
# 然后
select * from t_user where uid= ?
# 不管傳入的數值為數字類型或者字符串類型,最后都會自動給加上單引號
# 假設傳入參數為10或者你好的中文,最后結果為:
select * from t_user where uid= '1'
select * from t_user where uid= '你好'
# 示例${}的使用,假設傳入參數為1
# 定義語法,如果最后結果需要有單引號,我們需要主動加上單引號:
select * from t_user where uid= '${uid}'
# 最終結果
select * from t_user where uid= '1'
2.2、關鍵字#{}自動給變量加單引號的問題
使用 #{} 占位符時,MyBatis 為了防止SQL注入會為變量值自動加單引號。
示例:
# 定義語句
select * from table_a where name=#{name}
# 假設變量name="abc",則實際執行的SQL語句為:
select * from table_a where name='abc'
正常情況下,加上單引號可以避免 sql 注入問題,但是在有些情況下加入單引號會導致 sql 語句直接報錯,比如如果變量名是表名或者列名時。
示例:
create table if not exists #{tableName}
# 如果變量tableName="abc",則實際執行的SQL如下,該SQL將無法執行
create table if not exists 'abc'
# 此時我們可以使用${}來替代#{}:
create table if not exists ${tableName}
# 則實際執行SQL為
create table if not exists abc
在變量是表名或者列名時,我們可以使用 ${},在使用 ${} 時需注意傳入的變量值一定需保證是可靠的。
3、resultType
resultType指定輸出的結果類型,就是用來指定數據庫返回的信息對應的 Java 的數據類型。該 java 數據類型可以是任意的數據類型,不一定是一個實體類。如果 resultType 指定的是一個實體類,mybatis 執行 sql 語句后,會調用指定數據類型的類的無參構造函數來創建對象,將指定的列值賦值給同名的類屬性。
mybatis 中 resultType 可選類型有如下幾種:
- java 的基礎類型及其包裝類 int(java.lang.Integer)、string(java.lang.String)、double(java.lang.Double)等。
- 實體類,自己定義的實體類。
- map類型,如果使用resultMap這里可以使用自定義map。
- 集合,即返回的時一個List集合,其中該集合的類型可以為1,2,3中提到的類型。
注意如果是集合情形,那應該是集合可以包含的類型,而不能是集合本身。使用 resultType 或 resultMap,但不能同時使用。
3.1、返回基本數據類型
如果是基本數據類型,則 resultType 的值可以是基本數據類型的類的全限定名稱,或者也可以直接寫別名即可。比如:java.lang.Integer 的別名是 int。
基本數據類型的類的別名映射關系如下:

示例:
<mapper namespace="user"> <!-- 返回值為int,resultType可以寫成int或者java.lang.Integer--> <select id="countUser" resultType="int"> select count(*) from user </select> </mapper>
3.2、返回一個 JavaBean
當返回一個 JavaBean 時,數據庫中的列名需跟 JavaBean 的屬性名一一對應,否則該屬性將無法賦值成功。
比如根據某個字段獲得數據庫中的信息,把查詢的結果信息封裝成某個 JavaBean 類型的數據。
示例:
<mapper namespace="dao.StudentDao"> <select id="findStudentById" parameterType="int" resultType="entity.Student"> select * from student where id = #{id} </select> </mapper>
測試代碼:
//mapper接口類 public interface StudentDao { public Student findStudentById(int id); } //測試代碼 Student student = studentDao.findStudentById(2); System.out.println(student);
mybatis 執行 sql 語句后,會調用指定數據類型的類的無參構造函數來創建對象,將指定的列值賦值給同名的類屬性,即自動將查詢的結果映射成 JavaBean 中的屬性。
3.2.2、自定義別名
當返回一個自定義的 JavaBean 對象時,我們可以在 mybatis 的配置文件中為該 JavaBean 定義一個別名,然后就可以在 resultType 中使用該別名了。
先在 mybatis 配置文件中定義別名:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> ... <typeAliases> <!--可以為多個類定義別名--> <typeAlias type="entity.Student" alias="myStu"></typeAlias> <!--或者也可以使用package來為該包中的所有類定義別名,類名就是別名(不區分大小寫)--> <package name="entity"/> </typeAliases> ... </configuration>
使用 package 標簽定義別名比較方便,但如果為多個包都定義了別名,而不同包中有相同類名,則可能在 sql 映射文件中使用別名可能會報錯,因為 mybatis 不知道你這個別名指定的是哪個類。
定義別名后就可以在sql mapper 文件中使用了??梢允褂?typeAlias 中指定的別名,如果是通過 package 標簽定義的別名,則可以直接使用類名的全小寫作為作為別名:
<mapper namespace="dao.StudentDao"> <select id="findStudentById" parameterType="int" resultType="myStu"> select * from student where id = #{id} </select> <select id="findStudentById" parameterType="int" resultType="student"> select * from student where id = #{id} </select> </mapper>
后面使用該 sql 跟使用普通 sql 一樣。
3.3、返回 List 集合
有時候我們要查詢的數據不止一條,比如:模糊查詢,全表查詢等,這時候返回的數據可能不止是一條數據,對于多數據的處理可以存放在List集合中。
返回 list 集合時,resultType 返回值類型是集合內元素的數據類型,而不是 list。
示例:
<mapper namespace="dao.StudentDao"> <select id="findStudentAll" resultType="entity.Student"> select * from Student </select> <select id="selectMultiStudent" resultType="entity.Student"> select * from Student where id = #{myId} or name = #{myName} </select> </mapper>
測試代碼:
//Mapper接口類 public interface StudentDao { public List<Student> findStudentAll(); public List selectMultiStudent(@Param("myId") Integer id, @Param("myName") String name); } //測試代碼 List list = studentDao.selectMultiStudent(1, "李四"); List list2 = studentDao.findStudentAll();
3.4、返回Map類型
MyBatis 還支持將查詢的數據封裝成Map。
如果查詢的結果是一條,mybatis 會將查詢出來的結果以數據庫列名作為 key,列值作為 value 存入到 Map 中。
示例:
<mapper namespace="dao.StudentDao"> <select id="getStuAsMap" resultType="map"> select * from Student where id = #{id} </select> </mapper>
測試代碼:
//mapper接口類: public interface StudentDao { public Map<String, Object> getStuAsMap(int id); } //測試代碼: Map<String, Object> map = studentDao.getStuAsMap(1); System.out.println(map); //輸出結果:{name=wen, id=1, age=12}
如果查詢的結果是多條數據,我們也可以把查詢的數據以{表中某一字段名, JavaBean}方式來封裝成Map。
示例:
<mapper namespace="dao.StudentDao"> <!--注意 resultType 返回值類型,不再是 'map',而是 Map 的 value 對應的 JavaBean 類型 --> <select id="getAllEmpsAsMap" resultType="employee"> select * from t_employee </select> </mapper>
測試代碼:
// mapper接口類: // 查詢所有員工的信息,把數據庫中的id字段作為key,對應的 value 封裝成 Employee 對象。@MapKey 中的值表示用數據庫中的哪個字段名作 key @MapKey("id") Map<Integer, Employee> getAllEmpsAsMap();
當 resultType 為 map 類型時,我們在 Java 接口端一般以 list (建議)接收。當然也可以以 map (不推薦)接收,當以 map 接收時,需指定數據庫中的字段名且該字段需是唯一鍵作為 key,這樣才能把所有的結果都查詢出來,因為在 map 中,key 是不能重復的,否則查出來的數據可能有遺漏。
4、resultMap
當數據庫的列名和類的屬性名能一一對應時,查詢返回的數據能正確賦值給對應類的屬性。但是當查詢的返回的數據中列名和類的屬性名不同時,此時類無法被正確賦值,此時我們可以使用 resultMap 屬性。使用 resultMap 可以將指定的列名映射到特定的 java 類的屬性。
示例:
<mapper namespace="dao.StudentDao">
<resultMap id="studentMap" type="entity.Student"> <!--column指定的是數據庫的列名,property指定的是對應的實體類的屬性。也就是希望將column指定的列名賦值給該實體類的property指定的屬性--> <!--主鍵列使用id標簽--> <id column="id" property="myid"></id> <!--非主鍵列使用result標簽--> <result column="name" property="myname"></result> <result column="age" property="myage"></result> </resultMap>
<select id="findStudentAll" resultMap="studentMap"> select * from Student </select> </mapper>
測試代碼:
//mapper接口類: public interface StudentDao { public Student findStudentById(int id); } //測試代碼: System.out.println(studentDao.findStudentAll()); //輸出結果:[Student{id=1, name='wen', age=12}, Student{id=2, name='李四', age=14}]
resultType 和 resultMap 不要一起用,二選一。
使用 resultMap 可以將指定的列名映射到特定的 java 類的屬性。或者是我們也可以簡單地在 sql 語句中使用 as 來為返回結果起個別名,以便對應上類的屬性名。
比如:
<mapper namespace="dao.StudentDao"> <select id="findStudentById" parameterType="int" resultType="myStu"> select id, name as myname, age as myage from student where id = #{id} </select> </mapper>
5、給sql映射文件傳遞參數
5.1、通過 @param() 傳遞命名參數
在 sql 映射文件中,如果需要給 sql 語句傳遞多個參數,我們可以定義參數為一個 java 類,除以之外,我們也可以通過 @param("參數名") 的形式來傳遞多個參數。
下面以 mapper代理開發模式使用為例:
sql 映射文件內容:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dao.StudentDao"> <!--通過#{}來獲取自定義參數名--> <select id="selectMultiStudent" resultType="entity.Student"> select * from Student where id = #{myId} or name = #{myName} </select> </mapper>
接口類:
package dao; import entity.Student; import org.apache.ibatis.annotations.Param; import java.util.List; public interface StudentDao { //通過@Param命名參數來傳遞多個參數,并且給參數命名 public List selectMultiStudent(@Param("myId") Integer id, @Param("myName") String name); }
使用:
public class Test02 { @Test public void test01() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = factory.openSession(); StudentDao studentDao = session.getMapper(StudentDao.class); List list = studentDao.selectMultiStudent(1, "李四"); System.out.println(list); session.close(); } }
5.2、通過 map 集合傳遞多個參數
我們可以通過一個 map 集合來給 sql 語句傳遞多個參數,在 sql 映射中通過 #{key} 的形式來獲取參數。
下面以 mapper代理開發模式使用為例。sql 映射文件內容:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dao.StudentDao"> <select id="selectByMap" resultType="entity.Student"> select * from Student where id = #{myId} or name = #{myName} </select> </mapper>
接口類:
package dao; import entity.Student; import org.apache.ibatis.annotations.Param; import java.util.List; import java.util.Map; public interface StudentDao { public List selectByMap(Map map); }
使用:
public class Test02 { @Test public void test01() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = factory.openSession(); StudentDao studentDao = session.getMapper(StudentDao.class); Map map = new HashMap(); map.put("myId", 1); map.put("myName", "李四"); List list = studentDao.selectByMap(map); System.out.println(list); session.close(); } }
6、動態SQL
動態 SQL 是 MyBatis 的強大特性之一。在實際應用開發過程中,我們往往需要寫復雜的 SQL 語句,需要拼接,而拼接SQL語句又稍微不注意,由于引號,空格等缺失可能都會導致錯誤。Mybatis提供了動態SQL,也就是可以根據用戶提供的參數,動態決定查詢語句依賴的查詢條件或SQL語句的內容。
常見的動態SQL標簽有:
- if
- where
- foreach
- choose (when, otherwise)
6.1、if 標簽
語法:
<if test="條件">SQL語句</test> <!-- 示例--> <if test="age != null"> age > #{age} </if>
當 test 里面的條件為 true 時,會將該標簽里面包含的 SQL 語句拼接到最終生成的 SQL 語句中。
示例:
<mapper namespace="dao.StudentDao"> <select id="selectByIf" resultType="entity.Student"> select * from Student where <if test="name != null and name != ''"> name = #{name} </if>
<if test="age != null"> and age > #{age} </if> </select> </mapper>
測試代碼:
//接口類 public interface StudentDao { public Student selectByIf(Student Student); } //測試使用 Student student = new Student(); student.setName("wen"); student.setAge(10); studentDao.selectByIf(student);
上面的 sql 映射文件的寫法,當 if 的判斷為 true 時,才會將 if 標簽里面的 SQL 語句拼接到最終的 SQL 語句當中。
6.1.1、if 標簽的存在問題
使用 if 標簽會有個問題,例如上面的判斷當中,當第一個條件不滿足,而第二個條件滿足時,或者是都不滿足時,此時語法變成了:
select * from Student where and age > ? select * from Student where
上面的兩個 SQL 語句將直接報錯。
此時我們可以在 where 條件后面加一個永遠為真的判斷以此來解決該問題,如下:
<mapper namespace="dao.StudentDao"> <select id="selectByIf" resultType="entity.Student"> select * from Student where 1=1 <if test="name != null and name != ''"> and name = #{name} </if> <if test="age != null"> and age > #{age} </if> </select> </mapper>
或者是使用 <where> 標簽來解決也行。
6.1.2、mybatis傳單個參數和<if>標簽同時使用的問題
如果 mybatis 的 SQL mappper 中只接收了單個參數,而且是基本數據類型的,比如一個字符串、一個整型數據等,這時候使用 if 標簽來判斷該參數是否為空時,可能會報錯,提示信息類似:nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'xxx' in 'class java.lang.Integer' 。
解決方法:此時需要去掉 if 標簽,或者將該參數放在 map 或者一個 JavaBean 中都行。
可參考:http://www.rzrgm.cn/laxiag-4u/p/9088267.html
6.2、where 標簽
為了避免 if 標簽存在的問題,我們可以將 where 標簽和 if 標簽配合使用。
<where> 標簽里面可以包含多個 if 標簽,當這些 if 標簽中有一個為 true 時,則 where 會自動往 SQL 語句中增加 where 關鍵字,并且會自動剔除掉 if 標簽返回的多余的 and、or 等關鍵字。否則的話,where 關鍵字不會增加到 SQL 語句當中。這樣就能完美解決 if 標簽可能存在的問題。
語法:
<where> <if test="條件"> SQL語句 </if> <if test="條件"> SQL語句 </if> </where>
示例:
<mapper namespace="dao.StudentDao"> <select id="selectByIfAndWhere" resultType="entity.Student"> select * from Student <where> <if test="name != null and name != ''"> name = #{name} </if> <if test="age != null"> or age > #{age} </if> </where> </select> </mapper>
測試代碼:
//接口類 public interface StudentDao { public List selectByIfAndWhere(Student student); } //測試使用: Student student = new Student(); student.setAge(13); studentDao.selectByIfAndWhere(student)
<where> 標簽會自動判斷是否需要補全 where 關鍵字,并且會自動剔除多余的 and、or 等 sql 關鍵字,通過 where 標簽就可以正常使用 if 標簽了。
6.3、foreach 標簽
mybatis 的 foreach 標簽經常用于遍歷集合,構建 in 條件語句或者批量操作語句。
<foreach> 標簽的屬性主要有 item,index,collection,open,separator,close。
- collection:表示迭代集合的名稱。必選
- 如果 collection 類型是一個List的時候,則 collection 的值為 list;如果是一個 array 數組的時候,collection 的值為 array;如果collection類型為map,則 collection 的值可以是該 map 的某個 key 值,foreach 將該 key 的 value 值作為遍歷對象進行遍歷
- item:表示集合中的每一個元素。必選
- index:該元素的索引??蛇x
- open:該語句開始時的字符。可選
- separator:每個元素之間的分隔符??蛇x
- close:該語句結束時的字符??蛇x
使用示例:
參數為 list 類型:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dao.StudentDao"> <!--下面代碼最終實際會生成SQL語句:select * from Student where id in (1,2...)--> <select id="selectByForeach" resultType="entity.Student"> select * from Student where id in <foreach collection="list" index="index" item="myid" open="(" close=")" separator=","> #{myid} </foreach> </select> </mapper>
<foreach> 標簽包含的語句,如果 item 的值是一個對象的話,則應該取對象的屬性值,例如 #{itemObj.name}
測試代碼:
//接口類 public interface StudentDao { public List selectByForeach(List list); } //測試代碼 List list = new ArrayList(); list.add(1); list.add(2); System.out.println( studentDao.selectByForeach(list) );
參數為 map 類型:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dao.StudentDao"> <!--下面代碼最終實際會生成SQL語句:select * from Student where name like ... and id in (1,2...) --> <select id="selectByForeach2" resultType="entity.Student"> select * from Student where name like "%"#{myname}"%" and id in <foreach collection="idlist" index="index" item="myid" open="(" close=")" separator=","> #{myid} </foreach> </select> </mapper>
參數為 map 類型時,collection 的值是選定的需要用來遍歷的 value 值的 key,即上面會將 map 的 idlist 的值來進行 foreach 遍歷。
測試代碼:
//接口類 public interface StudentDao { public List selectByForeach2(Map map); } //測試代碼 List list = new ArrayList(); list.add(1); list.add(2); list.add(3); Map<String, Object> map = new HashMap<>(); map.put("idlist", list); map.put("myname", "wen"); System.out.println(studentDao.selectByForeach2(map));
6.4、代碼片段(復用sql代碼)
如果有一段 sql 代碼在多個地方都會出現,我們可以將該段代碼獨立為 sql 代碼片段,這樣就可以在其他地方直接引用該片段而不用重復書寫。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dao.StudentDao"> <!-- 定義代碼片段 --> <sql id="mysql"> select id,name,email,age from student </sql> <!-- 下面使用代碼片段 --> <select id="findStudentById" parameterType="int" resultType="myStu"> <include refid="mysql" /> </select> </mapper>

浙公網安備 33010602011771號