具體内容
映射檔案指導着MyBatis如何進行資料庫的增删改查,是以具有非常重要的意義,映射檔案中包含的标簽:
1. 一級标簽
< mapper namespace=“對應接口的全類名”></ mapper>,表示我們的這個SQL映射檔案式對應哪個接口的。也意味着一個接口就存在一個sql映射檔案。
2. 二級标簽
- cache:配置二級緩存的标簽
- cache-ref:應用其他命名空間的二級緩存配置
- resultMap:自定義映射結果集
- sql:抽取可重用的sql片段
- insert:新增操作
- delete:删除操作
- select:查詢操作
- parameterMap:已廢棄的參數映射
3. 三級标簽:
- where
- if
- foreach
- set
-
trim
…
增删改查
- 在Dao接口中,定義增删改查方法。
//新增使用者
public void insertEmp(Emp emp);
//删除使用者
public void deleteEmp(Integer empId);
//修改使用者
public void updateEmp(Emp emp);
//查找使用者
public List<Emp> selectEmp();
- 編寫sql映射檔案
<!--
public void insertEmp(Emp emp);
如果傳入的參數為實體類的對象,那麼在sql中,必須使用#{實體類JavaBean風格的屬性名} 進行參數綁定
-->
<insert id = "insertEmp">
insert into emp(emp_name,emp_mail,emp_gender,dept_id) values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
<!--
public void deleteEmp(Integer empId)
-->
<delete id="deleteEmp">
delete from emp where emp_id = #{empId}
</delete>
<!--
public void updateEmp(Emp emp);
-->
<update id="updateEmp">
update emp set emp_name = #{empName},emp_mail=#{empMail} where emp_id = #{empId}
</update>
<!--
public List<Emp> selectEmp();
-->
<select id="selectEmp" resultType="com.nhkj.entity.Emp">
select * from emp
</select >
如何知道增删改是否成功?
實際上MyBatis已經為我們封裝好了Integer,int,Long,long,Boolean,boolean類型參數,如果我們接口中的增删改方法需要擷取這些的參數,我們隻需要修改增删改的方法,将傳回值設定為對應類型的傳回值即可,而且sql映射檔案不必做任何修改。Integer,int,Long,long傳回值類型傳回的是影響資料的筆數,Boolean,boolean表示影響資料筆數為0的情況下傳回false,否則傳回true。
public int insertEmp(Emp emp);
public Long deleteEmp(Integer empId);
public Integer updateEmp(Emp emp);
測試類
@Test
public void testEmpUpdate() throws Exception {
InputStream input = Resources.getResourceAsStream("mybatis-conf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao empDao = sqlSession.getMapper(EmpDao.class);
Emp emp = new Emp();
emp.setEmpId(90);
emp.setEmpName("張益達");
emp.setEmpMail("[email protected]");
Integer cont = empDao.updateEmp(emp); //調用接口中修改方法,并且回去相應的傳回值
System.out.println(cont + "=========================");// 結果 1=========================
sqlSession.commit();
sqlSession.close();
}
擷取自增主鍵的值
Mysql是支援主鍵自增的,我們在開發中,有這樣的一個需求,傳遞一個沒有主鍵的實體類對象進行新增操作,新增完成之後,需要這個實體類封裝上主鍵值。
Emp -- > insert
Java程式中對象
新增之前
empe_id emp_name emp_mail emp_gender dept_id
null 張三 [email protected] 1 1
新增之後
empe_id emp_name emp_mail emp_gender dept_id
10 張三 [email protected] 1 1
- Mysql資料庫的相應操作
<!--
public void insertEmp(Emp emp);
如果傳入的參數為實體類的對象,那麼在sql中,必須使用#{實體類JavaBean風格的屬性名} 進行參數綁定
擷取自增主鍵的值:
keyColumn : 資料表中的主鍵列名
keyProperty : 對應主鍵實體類的屬性名
useGeneratedKeys : 表示是否通過自增主鍵擷取主鍵值
- true 是
- false 否
-->
<insert id = "insertEmp" keyColumn="emp_id" keyProperty="empId" useGeneratedKeys="true">
insert into emp(emp_name,emp_mail,emp_gender,dept_id) values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
//第二種方式:
<insert id = "insertEmp">
<!-- 配置儲存時擷取插入的 id -->
<selectKey keyColumn="emp_id" keyProperty="empId" resultType="int">
select last_insert_id();
</selectKey>
insert into emp(emp_name,emp_mail,emp_gender,dept_id) values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
- Oracle中使用序列來完成自增主鍵值的擷取
Oracle中沒有主鍵自增,而是通過序列完成。
create table emp_721(
emp_id number(11) primary key,
emp_name varchar2(20) not null,
emp_mail varchar2(30) not null,
emp_gender number(1) not null,
dept_id number(10) not null
);
select * from emp_721;
insert into emp_721 (emp_id,emp_name,emp_mail,emp_gender,dept_id)
values(1,'張大炮','[email protected]',1,1);
-- 以下sql報錯,那麼如何實作主鍵自增呢
insert into emp_721 (emp_name,emp_mail,emp_gender,dept_id)
values('張益達','[email protected]',1,1);
-- oracle資料庫中要實作主鍵自增,我們必須使用序列
create sequence seq_2020721;
-- sequence查詢
select seq_2020721.nextval from dual; -- 查詢下一個值
select seq_2020721.currval from dual; -- 查詢序列目前的使用的值
-- 是以開發中,Oracle資料庫表資料新增,對NUmber的主鍵來說,都使用序列進行自增
insert into emp_721 (emp_id,emp_name,emp_mail,emp_gender,dept_id)
values(seq_2020721.nextval,'張大炮','[email protected]',1,1);
方式①:BEFORE版:先查詢後新增資料
<!--
Oracle環境下擷取非自增主鍵的值
public void insertEmp(Emp emp);
-->
<insert id="insertEmp" databaseId="oracle">
<!--
selectKey : 表示查詢一個值,作為新增資料的主鍵
keyColumn : 資料表中的主鍵列名
keyProperty : 對應主鍵實體類的屬性名
order:表示是在新增之前執行查詢還是新增之後執行查詢
BEFORE : 表示新增之前
AFTER : 表示新增之後
-->
<selectKey keyColumn="emp_id" keyProperty="empId" resultType="int" order="BEFORE">
select seq_2020721.nextval from dual
</selectKey>
insert into emp_721(emp_id,emp_name,emp_mail,emp_gender,dept_id) values(#{empId},#{empName},#{empMail},#{empGender},#{deptId})
</insert>
方式②:AFTER版:先新增資料,後查詢
<!--
Oracle環境下擷取非自增主鍵的值
public void insertEmp(Emp emp);
-->
<insert id="insertEmp" databaseId="oracle">
<selectKey keyColumn="emp_id" keyProperty="empId" resultType="int" order="AFTER">
select seq_2020721.currval from dual
</selectKey>
insert into emp_721(emp_id,emp_name,emp_mail,emp_gender,dept_id) values(seq_2020721.nextval,#{empName},#{empMail},#{empGender},#{deptId})
</insert>
參數的處理
在正常的開發中,參數可能是單個參數,也可能是多個參數,也可能是實體類的對象,這些是較常見的,還有一些特殊的,比如參數是map鍵值對,是list,除了map和list之外還有其他的多個參數。
1. 單個參數(Integer empId):
單個參數MyBatis不會做任何特殊處理,在SQL映射檔案中使用任意的參數名稱都可以取得該參數,如:#{abc},但一般都要有語義#{empId}。
2. 多個參數(String empName,Integer pageCurrent,Integer pageSIze):
首先我們測試一下多個參數,看情況。
<!--
public List<Emp> selectEmpByEmpNameLike(String empName,Integer startSize,Integer pageSize);
-->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_name like #{empName} limit #{startSize},#{pageSize}
</select>
以上程式運作錯誤提示:Available parameters are [0, 1, 2, param3, param1, param2],這是因為在傳遞多個參數的情況下,MyBatis會将參數自動的封裝成Map鍵值對,而且每一個參數都有兩個鍵和兩個值,這兩個鍵不同,但是值相同,以上程式最終封裝的鍵值對如下:
Key | Value |
---|---|
第一個參數的值 | |
param1 | 第一個參數的值 |
1 | 第二個參數的值 |
param2 | 第二個參數的值 |
2 | 第三個參數的值 |
param3 | 第三個參數的值 |
那麼在我們的SQl映射檔案中,#{key},在執行的時候就會傳入相應的鍵對應的值。如下:
<!--
public List<Emp> selectEmpByEmpNameLike(String empName,Integer startSize,Integer pageSize);
-->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_name like #{param1} limit #{1},#{param3}
</select>
當然我們也可以給參數指定鍵(key),在接口方法的入參前使用@Param注解,給參數指定封裝的Map的key,如下:
<!--
public List<Emp> selectEmpByEmpNameLike(@Param("empName")String empName,@Param("startSize")Integer startSize,@Param("pageSize")Integer pageSize);
-->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_name like #{empName} limit #{startSize},#{pageSize}
</select>
現在注解後可以在SQL映射檔案中,使用指定注解的key獲得參數的值,那麼預設的param1…或者0,1…還能用嗎?
答:Param1,param2…可以正常使用,0,1,2…不能使用了。
3. 傳遞的參數是一個實體類對象
會将參數封裝成一個Map鍵值對,鍵對應的是實體類對象的屬性名,值對應的是該實體類屬性的值。比如,傳遞對象為Emp,則封裝的Map兼職對為:
Key | Value |
---|---|
empName | empName屬性值 |
empMail | empMail的屬性值 |
empId | empId的屬性值 |
empGender | empGender的屬性值 |
deptId | deptId屬性值 |
<insert id = "insertEmp">
insert into emp(emp_name,emp_mail,emp_gender,dept_id) values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
4. Map作為參數(Map<String,Object>傳入參數為Map的情況下,直接在sql映射檔案中使用#{鍵} 就可以傳遞相應的值
5. List【Set,Array】作為參數(List< String >)
一個list參數是很好處理的,集合會被封裝成鍵值對,鍵為list和collection,值為List集合的值,是以在sql映射檔案中想要取得集合的某個位置的具體的值:#{list[索引位置]},#{collection[索引位置]},同理Set集合或者Array資料處理相似。
<!--
public Emp selectEmpByID2(List<Integer> ids); 去主鍵集合中的第一個元素作為查詢條件
-->
<select id="selectEmpByID2" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_id = #{collection[2]} 或者 #{list[2]}
</select>
<!--
public Emp selectEmpByID2(Integer[] ids); 去主鍵集合中的第一個元素作為查詢條件
-->
<select id="selectEmpByID2" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_id = #{array[2]}
</select>
6. Map,List,和其他參數(Map<String,Object>,List,String empName,Integer pageCurrent,Integer pageSIze):
會将參數封裝成Map鍵值對,而且是符合②所述多個參數的封裝規則。
<!--
public List<Emp> selectEmpByEmpNameLike(String empName,Integer startSize,Integer pageSize,List<Integer> ids);
-->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_name like #{param1} and emp_id = #{param4[2]} limit #{1},#{2}
</select>
#{}與${}的差別
- #{}表示一個占位符号
通過#{}可以實作 preparedStatement 向占位符中設定值,自動進行 java 類型和 jdbc 類型轉換,
#{}可以有效防止 sql 注入。 #{}可以接收簡單類型值或 pojo 屬性值。 如果 parameterType 傳輸單個簡單類
型值,#{}括号中可以是 value 或其它名稱。

- ${}表示拼接 sql 串
通過${}可以将 parameterType 傳入的内容拼接在 sql 中且不進行 jdbc 類型轉換, 可 以 接 收 簡 單 類 型 值 或 p o j o 屬 性 值 , 如 果 p a r a m e t e r T y p e 傳 輸 單 個 簡 單 類 型 值 , {}可以接收簡單類型值或 pojo 屬性值,如果 parameterType 傳輸單個簡單類型值, 可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,{}括号中隻能是 value。
查詢傳回的結果(resultType)
查詢記錄傳回List
如果查詢的結果集為多筆資料【每一筆資料都對應實體類的一個對象】,那麼我們可以使用List接收,那麼每一條資料都會被分裝為實體類的一個對象,而List中就是存放了查詢結果集的多個對象。
public List<Emp> selectEmp();
<select id="selectEmp" resultType="com.wanbangee.entities.Emp" databaseId="mysql">
select * from emp
</select>
注意一點:requestType設定為實體類的全類名,而不是List全類名。
查詢記錄傳回Map
在查詢結果集映射為多個實體列對象情況下,不能使用Map接收,隻有查詢結果集為單行的情況下,才能使用Map接收。單筆資料的情況下,使用Map接收,Map的鍵為查詢的資料列明,值為對應列明的查詢結果。
<!-- public Map<String,Object> selectEmp(); -->
<!-- map是MyBatis中定義好的Map别名-->
<select id="selectEmp" resultType="map">
select * from emp where emp_id = 1
</select>
Key | Value |
---|---|
empName | empName屬性值 |
empMail | empMail的屬性值 |
empId | empId的屬性值 |
empGender | empGender的屬性值 |
deptId | deptId屬性值 |
查詢結果集是單行單列
比如我們查詢資料筆數,那麼就是單行單列的值。這種情況下,可以直接将結果集定義為查詢結果集的類型。
<!--
public Integer selectEmpCount();
-->
<select id="selectEmpCount" resultType="int">
select count(*) from emp
</select>
單行單列也可以使用Map接收:
<!--
public Map<String,Object> selectEmpCount();
-->
<select id="selectEmpCount" resultType="map">
select count(*) from emp
</select>
查詢記錄傳回resultMap
在屬性名和列名不同的情況下,我們解決映射關系的方式有兩種:
-
自動駝峰,但是在某些情況下,自動駝峰解決不了
如,列名:empno, 屬性名:empId
-
查詢的sql語句中,給列名取一個别名,别名可以映射上實體類的屬性
除了以上的兩種解決方案,還有另外一種解決方案,叫做自定義結果集映射,這個時候必須使用resultMap标簽來進行自定義。
<!-- 自定義封裝結果集映射
type : 封裝後結果集的類型全類名或者别名
id : 表示自定義封裝結果的id,唯一的
id 标簽: 表示自定義主鍵封裝映射規則
result 标簽:表示自定義非主鍵封裝映射規則
- column:查詢結果集的列名
- property : 列明對應的屬性名
在select标簽,如果要使用自定義封裝結果集,必須使用resultMap屬性聲明,而且resultMap和resultType 不能同時存在
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="empno" property="empId"/>
<result column="ename" property="empName"/>
</resultMap>
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultMap="myEmp">
select empno ,ename from emp where empno = #{id}
</select>
級聯屬性的封裝
現在我們在Emp實體類中引用Dept,在查詢Emp的同時,要求将對應引用的Dept屬性也能夠封裝上,按照傳統的寫法如下:
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultType="com.wanbangee.entities.Emp" databaseId="mysql">
select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,b.dept_id `dept.deptId`, b.dept_name `dept.deptName` from emp a
left join dept b on a.dept_id = b.dept_id where emp_id = #{id}
</select>
發現程式運作正常了,實際上我們還有一種寫法,叫做使用resultMap自定義封裝結果集:
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="emp_id" property="empId"/>
<result column = "emp_name" property="empName"/>
<result column = "emp_mail" property="empMail"/>
<result column = "emp_gender" property="empGender"/>
<result column = "dept_id" property="deptId"/>
<result column = "dept_id" property="dept.deptId"/>
<result column = "dept_name" property="dept.deptName"/>
</resultMap>
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultMap="myEmp" databaseId="mysql">
select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,b.dept_name from emp a
left join dept b on a.dept_id = b.dept_id where emp_id = #{id}
</select>
association關聯映射
Association是MyBatis中提供的一個多對一的關聯映射的一個标簽,使用在resultMap标簽中,表示多對一的關系,在多的一端引用一的一端,比如雇員和部門的關系,每個雇員都存在于一個部門。現在查詢雇員的同時要關聯對應的部門,雇員對應的部門隻有一個,這個時候我們就可以使用association進行關聯映射:
<!-- 自定義封裝結果集映射
type : 封裝後結果集的類型全類名或者别名
id : 表示自定義封裝結果的id,唯一的
id 标簽: 表示自定義主鍵封裝映射規則
result 标簽:表示自定義非主鍵封裝映射規則
- column:查詢結果集的列名
- property : 列明對應的屬性名
在select标簽,如果要使用自定義封裝結果集,必須使用resultMap屬性聲明,而且resultMap和resultType 不能同時存在
association : 使用之後,可以将引用屬性進行分裝,比如Emp中封裝dept屬性
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_mail" property="empMail"/>
<result column="emp_gender" property="empGender"/>
<result column="dept_id" property="deptId"/>
<association property="dept" javaType="com.wanbangee.entities.Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultMap="myEmp" databaseId="mysql">
select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,b.dept_name from emp a
left join dept b on a.dept_id = b.dept_id where emp_id = #{id}
</select>
以上的查詢雖然結果正确,但是在資料量特别大的時候,效率很低,因為關聯查詢一定存在笛卡爾乘積現象。是以後期開發中,幾乎不會使用這種封裝形式。而是使用分布查詢。
association分步查詢
Association是支援進行分布查詢的,第一步先查詢雇員資訊,第二步查詢對應 部門資訊。
<!-- 自定義封裝結果集映射
association : 分步查詢
property : 設定需要封裝的屬性
select : 調用其他的查詢方法
column : 查詢所需要傳遞的參數
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_mail" property="empMail"/>
<result column="emp_gender" property="empGender"/>
<result column="dept_id" property="deptId"/>
<association property="dept" select="com.wanbangee.dao.DeptDaoPlus.selectDeptByDeptId" column="dept_id">
</association>
</resultMap>
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultMap="myEmp" databaseId="mysql">
select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id from emp a
where a.emp_id = #{id}
</select>
在對應DeptDao和DeptDao.xml中寫selectDeptByDeptId()方法和配置
/**
* 根據部門id查詢部門資訊
* @param deptId
* @return
*/
public Dept selectDeptByDeptId(Integer deptId);
<resultMap type="dept" id="deptMap">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</resultMap>
<!-- 根據Id查詢部門資訊 -->
<select id="selectDeptByDeptId" resultMap="deptMap">
select * from dept where dept_id = #{deptId}
</select>
association分步查詢,延遲加載
延遲加載政策可以大大的提升資料庫的查詢性能,比如我們在查詢Emp對象的時候,不應該将Dept查詢出來,而是要在Emp對象需要使用Dept的時候,再進行查詢部門,這種查詢政策叫做延遲加載政策,又稱按需加載,又稱懶加載。預設情況下,MyBatis提供的分布查詢政策就是即時加載,是以我們要通過配置開啟延遲加載政策。
①:改變MyBatis運作時的行為,表示要開啟延遲加載
<!-- 全局配置檔案 開啟延遲加載政策 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
添加了上述配置之後,我們分布查詢就是延遲加載政策了,那麼在開啟了延遲加載政策之後,某些配置需要即時加載,有怎麼配置呢?我們可以在association标簽中配置fetchType屬性:
<!-- 自定義封裝結果集映射
type : 封裝後結果集的類型全類名或者别名
id : 表示自定義封裝結果的id,唯一的
id 标簽: 表示自定義主鍵封裝映射規則
result 标簽:表示自定義非主鍵封裝映射規則
- column:查詢結果集的列名
- property : 列明對應的屬性名
在select标簽,如果要使用自定義封裝結果集,必須使用resultMap屬性聲明,而且resultMap和resultType 不能同時存在
association : 使用之後,可以将引用屬性進行分裝,比如Emp中封裝dept屬性
association : 分步查詢
property : 設定需要封裝的屬性
select : 調用其他的查詢方法
column : 查詢所需要傳遞的參數
fetchType : 設定提取政策
- lazy : 預設為延遲加載
- eager : 即時加載
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_mail" property="empMail"/>
<result column="emp_gender" property="empGender"/>
<result column="dept_id" property="deptId"/>
<association property="dept" select="com.wanbangee.dao.DeptDaoPlus.selectDeptByDeptId" column="dept_id" fetchType="eager">
</association>
</resultMap>
collection分布查詢及延遲加載
<!--
public Dept selectDeptByDeptId(Integer deptId);
-->
<resultMap type="com.wanbangee.entities.Dept" id="myDept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="emps" select="com.wanbangee.dao.EmpDaoPlus.selectEmpByDeptId" column="dept_id" javaType="java.util.List" >
</collection>
</resultMap>
<select id="selectDeptByDeptId" resultMap="myDept">
select * from dept where dept_id = #{deptId}
</select>
相應是編寫empDao中的查詢
/**
* 根據部門id查詢員工的資訊
* @param EmpId
* @return
*/
public Emp selectEmpByDeptId(Integer deptId);
//配置
!-- 根據部門Id查詢員工資訊 -->
<select id="selectEmpByDeptId" resultType="com.nhkj.entity.Emp">
select * from emp where dept_id = #{deptId}
</select>
分布查詢傳遞多列值的情況
不管是association還是collection,上面的程式隻傳遞了一列值作為分布查詢的查詢條件參數,在開發中,還存在另外一種情況,分布查詢值傳遞兩個參數,也就意味着傳遞兩列值作為分布查詢的查詢條件參數
<resultMap type="com.wanbangee.entities.Dept" id="myDept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<!--
在傳遞多列值的情況下使用{abc=dept_id,bcd=dept_name},根據我們參數規則,
多個參數在MyBatis中會封裝了Map鍵值對,此時Map封裝的效果如下:
key ======================value
abc dept_id資料列的值
bcd dept_name資料列的值
-->
<collection property="emps" select="com.wanbangee.dao.EmpDaoPlus.selectEmpByDeptId"
column="{abc=dept_id,bcd=dept_name}" javaType="java.util.List" >
</collection>
</resultMap>