MyBatis XML對映檔案詳解
一 MyBatis XML配置
MyBatis真正的強大,在於其對映語句的魔力。
SQL 對映檔案有很少的幾個頂級元素(按照它們應該被定義的順序):
1)cache 給定名稱空間的配置快取。
2)cache-ref 其他名稱空間快取配置的引用。
3)resultMap 是最複雜也是最強大的元素,用來描述如何從資料庫結果集中來載入物件
4)sql 可被其他語句引用的可重用語句塊。
5)insert 對映插入語句
6)update 對映更新語句
7)delete 對映刪除語句
8)select 對映查詢語句
在我們描述每個元素細節之前,我們先感受一些元素的使用例項,以下例項分為UserMainMapper.XML配置的XML版本
和Java註解版本,也可以根據實際情況兩種都用,但是同名方法只能用XML或Java註解。
二 MyBatis XML配置例項
UserMainMapper.java介面修改為如下內容:
package com.lanhuigu.mybatis.mapper; import com.lanhuigu.mybatis.entity.User; import org.apache.ibatis.annotations.*; import java.util.List; import java.util.Map; /** * Mapper介面 * @author yihonglei * @date 2018/11/20 19:00 */ public interface UserMainMapper { //=======================XML版======================= /** * xml實現方式 * @author yihonglei * @date 2018/11/20 19:00 */ User queryUserMainById(int id); /** * 查詢使用者物件--返回多個--List<User> */ List<User> queryUserMainList(); /** * 查詢使用者物件--返回map */ Map<String,Object> queryUserMainResultMap(int id); /** * 查詢使用者物件--返回多個--List<Map<String,Object>> */ List<Map<String,Object>> queryUserMainResultListMap(); /** * 插入使用者物件 */ int insertUser(User user); /** * 更新使用者物件 */ int updateUser(User user); /** * 刪除使用者物件 */ int deleteUser(int id); //=======================註解版========================== /** * Java註解實現方式--查詢使用者物件--返回User物件 * @author yihonglei * @date 2018/11/20 19:01 */ @Select(" SELECT f_id id,f_username username,f_age age FROM t_user_main WHERE f_id = ${id} ") User queryUserMainByIdNew(@Param("id") int id); /** * 查詢使用者物件--返回多個--List<User> */ @Select("select f_id id,f_username username,f_age age from t_user_main ") List<User> queryUserMainListNew(); /** * 查詢使用者物件--返回map */ @Select("select f_id id,f_username username,f_age age from t_user_main where f_id = #{id}") Map<String,Object> queryUserMainResultMapNew(@Param("id") int id); /** * 查詢使用者物件--返回多個--List<Map<String,Object>> */ @Select("select f_id id,f_username username,f_age age from t_user_main") List<Map<String,Object>> queryUserMainResultListMapNew(); /** * 插入使用者物件 */ @Insert("insert into t_user_main (f_id,f_username,f_age) " + " values(#{id},#{username},#{age}) ") int insertUserNew(User user); /** * 更新使用者物件 */ @Update("update t_user_main set f_username=#{username},f_age=#{age} " + " where f_id=#{id} ") int updateUserNew(User user); /** * 刪除使用者物件 */ @Delete("delete from t_user_main where f_id=#{id}") int deleteUserNew(@Param("id") int id); }
UserMainMapper.xml修改為如下內容:
<?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,namespace的值習慣上設定成包名+去掉sql對映檔案字尾的檔名, 這樣就能夠保證namespace的值是唯一的,例如namespace="com.lanhuigu.mybatis.mapper.UserMainMapper" 就是com.lanhuigu.mybatis.mapper(包名)+UserMainMapper(UserMainMapper.xml檔案去除字尾)。 --> <mapper namespace="com.lanhuigu.mybatis.mapper.UserMainMapper"> <!-- 在select標籤中編寫查詢的SQL語句,設定select標籤的id屬性為queryUserMainById,id屬性值必須是唯一的, 不能夠重複使用parameterType屬性指明查詢時使用的引數型別,resultType屬性指明查詢返回的結果集型別 resultType="com.lanhuigu.mybatis.entity.User"就表示將查詢結果封裝成一個User類的物件返回 User類就是users表所對應的實體類。 --> <select id="queryUserMainById" parameterType="int" resultType="com.lanhuigu.mybatis.entity.User"> select f_id id, f_username username, f_age age from t_user_main where f_id = #{id} </select> <!-- 查詢使用者物件,返回list<User> --> <select id="queryUserMainList" flushCache="true" resultType="com.lanhuigu.mybatis.entity.User"> select f_id id, f_username username, f_age age from t_user_main </select> <!-- 查詢使用者物件,返回Map --> <select id="queryUserMainResultMap" parameterType="int" flushCache="true" resultType="java.util.HashMap"> select f_id id, f_username username, f_age age from t_user_main where f_id = #{id} </select> <!-- 查詢使用者物件,返回List<Map<String,Object>> --> <select id="queryUserMainResultListMap" flushCache="true" resultType="java.util.HashMap"> select f_id id, f_username username, f_age age from t_user_main </select> <!-- 插入使用者物件(insert) --> <insert id="insertUser" parameterType="com.lanhuigu.mybatis.entity.User" useGeneratedKeys="false"> insert into t_user_main (f_id,f_username,f_age) values(#{id},#{username},#{age}) </insert> <!-- 更新使用者物件(update) --> <update id="updateUser" parameterType="com.lanhuigu.mybatis.entity.User" flushCache="true"> update t_user_main set f_username=#{username},f_age=#{age} where f_id=#{id} </update> <!-- 刪除使用者物件(delete) --> <delete id="deleteUser" parameterType="int" flushCache="true"> delete from t_user_main where f_id=#{id} </delete> </mapper>
MyBatisXMLConfigTest.java
package com.lanhuigu.mybatis;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import com.lanhuigu.mybatis.entity.User;
import com.lanhuigu.mybatis.mapper.UserMainMapper;
/**
* MyBatis XML配置測試
* @author yihonglei
* @date 2018/11/21 17:34
*/
public class MyBatisXMLConfigTest {
@Test
public void testMyBatis() {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = null;
SqlSession session = null;
try {
//讀取資原始檔
InputStream is = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
session = sqlSessionFactory.openSession();
//mapper呼叫
UserMainMapper userMainMapper = session.getMapper(UserMainMapper.class);
//================XML測試=================
//查詢--select--返回User物件
User user = userMainMapper.queryUserMainById(1);
System.out.println("user="+user);
System.out.println(user.getUsername());
//查詢--select--返回多個User物件--List<User>
/*List<User> list = userMainMapper.queryUserMainList();
for(int i=0;i<list.size();i++ ){
User user = list.get(i);
System.out.println(user.getUsername());
}*/
//查詢--select--返回map
/*Map<String,Object> userMap = userMainMapper.queryUserMainResultMap(1);
System.out.println("userMap="+userMap);
System.out.println(userMap.get("USERNAME"));*/
//查詢--select--返回List<Map<String,Object>>
/*List<Map<String,Object>> list = userMainMapper.queryUserMainResultListMap();
System.out.println("ListMap="+list);
for (int i=0;i<list.size();i++) {
Map<String,Object> userMap = list.get(i);
System.out.println(userMap.get("USERNAME"));
}*/
//插入--insert
/*User user = new User();
user.setId(3);
user.setUsername("three");
user.setAge(25);
int result = userMainMapper.insertUser(user);
if (result > 0) {
System.out.println("新增使用者成功");
//事務提交
session.commit();
} else {
System.out.println("新增使用者失敗");
//事務回滾
session.rollback();
}*/
//更新--update
/*User user = new User();
user.setId(3);
user.setUsername("this three update");
user.setAge(25);
int result = userMainMapper.updateUser(user);
if (result > 0) {
System.out.println("更新成功");
//事務提交
session.commit();
} else {
System.out.println("更新失敗");
//事務回滾
session.rollback();
}*/
//刪除--delete
/*int result = userMainMapper.deleteUser(3);
if (result > 0) {
System.out.println("刪除成功");
//事務提交
session.commit();
} else {
System.out.println("刪除失敗");
//事務回滾
session.rollback();
}*/
//=================註解測試===================
/*
* 只需將xml測試程式碼copy一份,然後將方法名都加上一個New即可
* 在UserMainMapper.java對映器中原本可以將xml部分註釋,
* 註解部分方法用同名,但是這樣做我們就得將UserMainMapper.xml刪掉,
* 或者改名,否則註解不能查詢出數值,著跟名稱空間和class,xml載入有關係.
* 總之class中和xml中不能出現註解和xml同時使用,對於單個方法,二者
* 只能存在其一,否則mybatis不知道你到底想幹啥,只能什麼都不做了
*/
//eg.
/*User user = userMainMapper.queryUserMainByIdNew(1);
System.out.println("user="+user);
System.out.println(user.getUsername());*/
} catch (Exception e) {
if (session != null) {
session.close();
}
}
}
}
有了上面的體驗,我們在對每個元素等等進行分析,然後在參考例項,能體驗更深。
結合上面的例項對元素進行分析和補充。
三 MyBatis 配置詳細說明
1、select元素
查詢語句是MyBatis中最常用的元素,作為一個持久層框架,把資料放入資料庫是一個方面,
把資料取一條或多條更是重中之重。
eg:
<select id="queryUserMainById" parameterType="int" flushCache="true"
resultType="com.lanhuigu.mybatis.entity.User">
select
f_id id,
f_username username,
f_age age
from t_user_main
where f_id = #{id}
</select>
這個語句稱之為queryUserMainById,接受一個int或Integer的引數,
返回一個com.lanhuigu.mybatis.entity.User的物件,這個如果嫌寫起來長,
可以在mybatis-config.xml中配置別名,這個地方直接用別名替代。
別名配置:
<typeAliases>
<typeAlias alias="User" type="com.lanhuigu.mybatis.entity.User"/>
</typeAliases>
select語句中引數id符號:
#{id}獲取傳入的引數。
這樣MyBatis處理時底層用JDBC生產預編譯語句,JDBC底層MyBatis替我們做了,
這樣我們只需關注MyBatis的應用,避免寫大量的JDBC程式碼,提高工作效率,呼叫時直接執行。
select元素有很多屬性可供選擇,如下:
<select
id="queryUserMainById"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="userMainResultMap"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
select元素相關屬性的描述,如下:
屬性 | 描述 |
---|---|
id | 在名稱空間中唯一的識別符號,可以被用來引用這條語句。 |
parameterType | 將會傳入這條語句的引數類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的引數,預設值為 unset。 |
parameterMap | 這是引用外部 parameterMap 的已經被廢棄的方法。使用內聯引數對映和 parameterType 屬性。 |
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 | 這個設定僅對多結果集的情況適用,它將列出語句執行後返回的結果集並每個結果集給一個名稱,名稱是逗號分隔的。 |
2、insert、update和delete元素
資料插入(insert)、資料更新(update)、資料刪除(delete)實現大同小異。
eg:
<span style="color:#000000;"><!-- 插入使用者物件(insert) -->
<insert id="insertUser"
parameterType="com.lanhuigu.mybatis.entity.User"
useGeneratedKeys="false">
insert into t_user_main (f_id,f_username,f_age)
values(#{id},#{username},#{age})
</insert>
<!-- 更新使用者物件(update) -->
<update id="updateUser"
parameterType="com.lanhuigu.mybatis.entity.User"
flushCache="true">
update t_user_main set f_username=#{username},f_age=#{age}
where f_id=#{id}
</update>
<!-- 刪除使用者物件(delete) -->
<delete id="deleteUser"
parameterType="int"
flushCache="true">
delete from t_user_main where f_id=#{id}
</delete></span>
這三個元素的屬性描述如下:
屬性 | 描述 |
---|---|
id | 名稱空間中的唯一識別符號,可被用來代表這條語句。 |
parameterType | 將要傳入語句的引數的完全限定類名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的引數,預設值為 unset。 |
parameterMap | 這是引用外部 parameterMap 的已經被廢棄的方法。使用內聯引數對映和 parameterType 屬性。 |
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 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
3、sql元素
這個元素可以被用來定義可重用的 SQL 程式碼段,可以包含在其他語句中。
它可以被靜態地(在載入引數) 引數化. 不同的屬性值通過包含的例項變化。
把上面的例項中UserMainMapper.xml修改,來個簡單例項:
<!-- sql語句塊 -->
<sql id="queryConditionSql">
f_id = #{id}
</sql>
<!-- 查詢使用者物件,返回User物件 -->
<select id="queryUserMainById" parameterType="int" flushCache="true"
resultType="com.lanhuigu.mybatis.entity.User">
select
f_id id,
f_username username,
f_age age
from t_user_main
<where>
<include refid="queryConditionSql"/>
</where>
</select>
將查詢語句的條件由where f_id=#{id}修改成sql語句塊替代。
這樣整個xml中條件部分共同的地方都提出來,避免重複囉嗦。
當然,sql語句塊除了可以用於條件部分,還可以用於其它任地方,
eg:
<!-- sql語句塊 -->
<sql id="queryConditionSql">
f_id = #{id}
</sql>
<sql id="att">
f_username username,
f_age age
</sql>
<!-- 查詢使用者物件,返回User物件 -->
<select id="queryUserMainById" parameterType="int" flushCache="true"
resultType="com.lanhuigu.mybatis.entity.User">
select
f_id id,
<include refid="att"/>
from t_user_main
<where>
<include refid="queryConditionSql"/>
</where>
</select>
查詢時,效果一樣。
總結:
1)sql語句塊用於提出xml共同sql
2)sql語句塊可以置於xml檔案中查詢語句前後,位置無關係,都能呼叫。
4、引數(parameters)
MyBatis的引數功能是非常強大的,前面用到的sql中,如果parameterType="int"
只是進行一個簡單的整數傳入,但是,在插入語句中,我們傳入的是型別User物件:
<insert id="insertUser" parameterType="com.lanhuigu.mybatis.entity.User"
useGeneratedKeys="false">
insert into t_user_main (f_id,f_username,f_age)
values(#{id},#{username},#{age})
</insert>
如果 User 型別的引數物件傳遞到了語句中,id、username 和 password 屬性將會被查詢,然後將它們的值傳入預處理語句
的引數中。這點對於向語句中傳參是比較好的而且又簡單,不過引數對映的功能遠不止於此。
首先,像 MyBatis 的其他部分一樣,引數也可以指定一個特殊的資料型別。
#{property,javaType=int,jdbcType=NUMERIC}
像 MyBatis 的剩餘部分一樣,javaType 通常可以從引數物件中來去確定,前提是隻要物件不是一個 HashMap。
那麼 javaType 應該被確定來保證使用正確型別處理器。NOTE 如果 null 被當作值來傳遞,對於所有可能為空的列,
JDBC Type 是需要的。你可以自己通過閱讀預處理語句的 setNull() 方法的 JavaDocs 文件來研究這種情況。
為了以後定製型別處理方式,你也可以指定一個特殊的型別處理器類(或別名),比如:
#{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 型別,
它會自動地被設定為結果集。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis 也支援很多高階的資料型別,比如結構體,但是當註冊 out 引數時你必須告訴它語句型別名稱。
比如(再次提示,在實際中要像這樣不能換行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
儘管所有這些強大的選項很多時候你只簡單指定屬性名,其他的事情 MyBatis 會自己去推斷,最多你需要為
可能為空的列名指定 jdbcType。
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
一個完整的配置xml:
<insert id="addUser" parameterType="com.lanhuigu.core.dao.model.user.User"
useGeneratedKeys="true" keyColumn="f_userId" flushCache="true">
insert into t_user_main (
f_userId ,
f_userName ,
f_email ,
f_password ,
f_isPayPassword ,
f_mobile ,
f_registerTime ,
f_role ,
f_status ,
f_realAuth ,
f_redInviteCode,
f_redBeInviteCode,
f_version )
values(
#{userId,jdbcType=VARCHAR},
#{userName,jdbcType=VARCHAR},
#{email,jdbcType=VARCHAR},
#{password,jdbcType=VARCHAR} ,
#{isPayPassword,jdbcType=VARCHAR},
#{mobile,jdbcType=VARCHAR},
#{registerTime,jdbcType=VARCHAR},
#{roles,jdbcType=VARCHAR},
#{status,jdbcType=VARCHAR},
#{realAuth,jdbcType=VARCHAR},
#{redInviteCode,jdbcType=VARCHAR},
#{redBeInviteCode,jdbcType=VARCHAR},
#{version,jdbcType=VARCHAR}
)
</insert>
5、字串替換
在上面中,很多地方用到#{},預設情況下,使用#{}格式的語法會導致MyBatis建立預處理語句屬性並安全地設定值(比如?)。
這樣做更安全,更迅速,通常也是首選做法,不過有時你只是想直接在SQL語句中插入一個不改變的字串,
或者在條件放一個字串。比如:
f_id = ${id}
以這種方式接受從使用者輸出的內容並提供給語句中不變的字串是不安全的,會導致潛在的 SQL 注入攻擊,
因此要麼不允許使用者輸入這些欄位,要麼自行轉義並檢驗。