目錄
- 一、動态SQL
- 1、if
- 2、choose、when、otherwise
- 3、trim(where,set)
- 3.1、where
- 3.2、set
- 3.3、trim
- 4、foreach
- 5、sql、include、bind

MyBatis源碼及資料: https://github.com/coderZYGui/MyBatis-Study
MyBatis系列
- MyBatis — ORM思想、MyBatis概述、日志架構、OGNL
- MyBaits — MyBatis的CRUD操作、别名配置、屬性配置、查詢結果映射、Mapper元件、參數處理、注解開發
- MyBatis — 動态SQL、if、where、set、foreach、sql片段
- MyBatis — 對象關系映射、延遲加載、關聯對象的配置選擇
- MyBatis — 緩存機制、EhCache第三方緩存
- MyBatis — MyBatis Generator插件使用(配置詳解)
一、 動态SQL
跳轉到目錄
- MyBatis的強大特性之一便是它的
。動态SQL
- 動态sql是mybatis中的一個核心,什麼是動态sql?
。動态sql即對sql語句進行靈活操作,通過表達式進行判斷,對sql進行靈活拼接、組裝
- 如果你有使用JDBC或其他類似架構的經驗,你就能體會到根據不同條件拼接SQL語句有多麼痛苦。
- 拼接的時候要確定不能忘了必要的空格, 還要注意省掉列名清單最後的逗号。利用
這一特性可以徹底擺脫這種痛苦。動态SQL
- 通常使用動态SQL不可能是獨立的一部分, MyBatis當然使用一種強大的動态SQL語言來改進這種情形,這種語言可以被用在任意的SQL映射語句中。
- 和标簽庫
很像JSTL
1、if 标簽
跳轉到目錄
<if test="boolean表達式"></if>
—> test: 判斷表達式 (采用OGNL表達式)
<!-- 調用了trim()方法 -->
<if test="email!=null and email.trim()!=""">
and email=#{email}
</if>
- if 元素用于判斷,一般用作
是否應該包含某一個查詢條件 (傳遞過來的對象是否為 null 或 '')
測試類
/**
* 查詢工資大于等于1000的員工
*/
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
BigDecimal minSalary = new BigDecimal("1001");
List<Employee> employees = mapper.query1(minSalary);
for (Employee employee : employees) {
System.out.println(employee);
}
sqlSession.close();
}
/**
* 查詢工資在1000-2000之間的員工
*/
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
BigDecimal minSalary = new BigDecimal("1000");
BigDecimal maxSalary = new BigDecimal("2000");
List<Employee> employees = mapper.query2(minSalary, maxSalary);
for (Employee employee : employees) {
System.out.println(employee);
}
sqlSession.close();
}
Employee接口
@Param注解 : 為了解決參數個數問題, 因為Mapper接口, 底層隻允許傳遞一個參數
- 解決方法
- 1、将多個參數, 封裝為一個VO, 進行傳遞
- 2、使用Map來存儲多個參數, 通過map的key, 傳遞給sql
- 3、使用@Param注解, 底層仍然使用的是Map方式, @Param注解中的内容, 就充當了key
/**
* 查詢工資大于1000的員工
*/
List<Employee> query1(@Param("minSalary") BigDecimal minSalary);
/**
* 查詢工資在1000-2000之間
* @param minSalary
* @param maxSalary
* @return
*/
List<Employee> query2(
@Param("minSalary") BigDecimal minSalary,
@Param("maxSalary") BigDecimal maxSalary
);
EmployeeMapper.xml
<!--查詢工資大于等于1000的員工-->
<select id="query1" resultType="Employee">
SELECT * FROM employee
<if test="minSalary != null and minSalary != ''">
WHERE salary >= #{minSalary}
</if>
</select>
<!--查詢工資在1000-2000之間的員工-->
<select id="query2" resultType="Employee">
SELECT * FROM employee WHERE 1 = 1
<if test="minSalary != null and minSalary != ''">
AND salary >= #{minSalary}
</if>
<if test="maxSalary != null and maxSalary != ''">
AND salary <= #{maxSalary};
</if>
</select>
注意: 如果minSalary和maxSalary條件是可選擇的,也就是說
當minSalary傳入null時,SQL就會出現問題
; 就不确定使用WHERE還是AND來連接配接查詢條件.
解決方案: 使用
WHERE 1 = 1
方式,其他的查詢條件都使用AND或OR連接配接,但是
WHERE 1 = 1
會影響查詢性能.
2、choose、when、otherwise 标簽
跳轉到目錄
- 相當于switch判斷
測試方法
/**
* 查詢指定部門的員工資訊
*/
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
BigDecimal minSalary = new BigDecimal("100");
BigDecimal maxSalary = new BigDecimal("1000");
List<Employee> employees = mapper.query3(minSalary, maxSalary, 20L);
for (Employee employee : employees) {
System.out.println(employee);
}
sqlSession.close();
}
Employee接口中的方法
List<Employee> query3(
@Param("minSalary") BigDecimal minSalary,
@Param("maxSalary") BigDecimal maxSalary,
@Param("deptId") Long deptId
);
EmployeeMapper.xml
<!--查詢指定部門的員工資訊-->
<select id="query3" resultType="Employee">
SELECT * FROM employee WHERE 1 = 1
<if test="minSalary!=null and minSalary != ''">
AND salary >= #{minSalary}
</if>
<if test="maxSalary!=null and maxSalary != ''">
AND salary <= #{maxSalary}
</if>
<!--假如下拉清單擷取的部門id,"所在部門"這個要排除,設為-1-->
<!--<if test="deptId > 0">
AND deptId = #{deptId}
</if>-->
<choose> <!--相當于switch判斷-->
<when test="deptId > 0">AND deptId = #{deptId}</when>
<otherwise>AND deptId IS NOT NULL</otherwise>
</choose>
</select>
和上面例子無關
<!-- public List<Employee> getEmpsByConditionChoose(Employee employee); -->
<select id="getEmpsByConditionChoose" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<where>
<!-- 如果帶了id就用id查,如果帶了lastName就用lastName查;隻會進入其中一個 -->
<choose>
<when test="id!=null">
id=#{id}
</when>
<when test="lastName!=null">
last_name like #{lastName}
</when>
<when test="email!=null">
email = #{email}
</when>
<!-- 上面選擇都沒進, 才執行otherwise -->
<otherwise>
gender = 0
</otherwise>
</choose>
</where>
</select>
3、trim(where,set) 标簽
跳轉到目錄
3.1、where
跳轉到目錄
-
: 使用where元素
首先會判斷查詢條件是否有<where>标簽
關鍵字,如果沒有,則在第一個查詢條件之前,插入一個WHERE關鍵字, 如果發現查詢條件AND 或者 OR開頭,也會把第一個查詢條件前的AND/OR 去掉WHERE
- 這種方式避免了
的形式WHERE 1 = 1
查詢指定部門的員工資訊
<select id="query3" resultType="Employee">
SELECT * FROM employee
<where>
<if test="minSalary!=null">
AND salary >= #{minSalary}
</if>
<if test="maxSalary!=null">
AND salary <= #{maxSalary}
</if>
<choose> <!--相當于switch判斷-->
<when test="deptId > 0">AND deptId = #{deptId}</when>
<otherwise>AND deptId IS NOT NULL</otherwise>
</choose>
</where>
</select>
3.2、set
跳轉到目錄
-
和set元素
相似,也能根據set中的sql動态的去掉where元素
,并在前面添加最後的逗号
,如果沒有内容,也會選擇忽略set語句.set關鍵字
應用場景
- 因為如果修改使用者資訊的時候, 當password沒有設定值, 是以在#{password}時從getPassword()中就擷取的為NULL,是以就要采用
來動态判斷password是否為空,如果為空,則不拼接,但是此時會出現問題,上面拼接的語句最後會存在一個if
. 此時就會出現sql文法錯誤,
MyBatis——動态SQL、if、where、set、foreach、sql片段一、 動态SQL - 這個時候就采用
來操作了,可以去掉後面的set元素
,
測試方法
/**
* 更新指定id的員工資訊
*/
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setId(6L);
employee.setSn("6238");
// employee.setName("guizy");
employee.setSalary(new BigDecimal("8888"));
int update = mapper.update(employee);
if (update > 0){
System.out.println("成功修改"+update+"條使用者資訊!");
}
sqlSession.commit();
sqlSession.close();
}
EmployeeMapper接口
EmployeeMapper.xml
<update id="update">
UPDATE employee
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="sn != null and sn != ''">
sn = #{sn},
</if>
<if test="salary != null and salary != ''">
salary = #{salary},
</if>
</set>
WHERE id = #{id};
</update>
3.3、trim
跳轉到目錄
- trim是更強大的
的标簽:格式化SQL
<trim prefix="" prefixOverrides="" suffix="" suffixOverrides="">
<!--trim包含的動态SQL-->
</trim>
前提如果trim元素包含内容傳回一個字元串,
則在傳回之後的字元串
-
:在trim标簽中的内容的 前面添加某些内容prefix
-
:在trim标簽中的内容的 前面去掉某些内容prefixOverrides
-
:在trim标簽中的内容的 後面添加某些内容suffix
-
:在trim标簽中的内容的 後面去掉某些内容suffixOverrides
<!--public List<Employee> getEmpsByConditionTrim(Employee employee); -->
<select id="getEmpsByConditionTrim" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<!-- 後面多出的and或者or where标簽不能解決
prefix="":字首:trim标簽體中是整個字元串拼串 後的結果。
prefix給拼串後的整個字元串加一個字首
prefixOverrides="":
字首覆寫: 去掉整個字元串前面多餘的字元
suffix="":字尾
suffix給拼串後的整個字元串加一個字尾
suffixOverrides=""
字尾覆寫:去掉整個字元串後面多餘的字元
-->
<!-- 自定義字元串的截取規則
prefix="where"表示: 在trim标簽内拼接好的字元串前面加上 where
suffixOverrides="and"表示: 在trim标簽内拼接好的字元串後面 去掉 and
-->
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null && lastName!=""">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=""">
email=#{email} and
</if>
<!-- ognl會進行字元串與數字的轉換判斷 "0"==0 -->
<if test="gender==0 or gender==1">
gender=#{gender}
</if>
</trim>
</select>
- 使用where等價于
<!-- 相當于使用 WHERE 來替換 AND 或者 OR--> <trim prefix="WHERE" prefixOverrides="AND |OR "> </trim>
注意: 此時AND後面有一個空格<select id="query3" resultType="Employee"> SELECT * FROM employee <!-- 如果trim标簽裡面的字元串, 以prefixOverrides中的值打頭, 就用prefix來替代--> <!--和使用where标簽效果一樣 因為以AND打頭, 和prefixOverrides中的一樣, 是以用prefix的内容WHERE替換AND --> <!-- trim标簽内字元串前面加上 where, 然後再将trim标簽内前面的AND或OR去除 --> <trim prefix="WHERE" prefixOverrides="AND|OR"> <if test="minSalary!=null"> AND salary >= #{minSalary} </if> <if test="maxSalary!=null"> AND salary <= #{maxSalary} </if> <choose> <!--相當于switch判斷--> <when test="deptId > 0">AND deptId = #{deptId}</when> <otherwise>AND deptId IS NOT NULL</otherwise> </choose> </trim> </select>
- 使用set等價于
<!-- 使用suffix的空格, 替換末尾的 ,--> <trim prefix="SET" suffixOverrides=","> </trim>
<!-- 因為最後以 , 結尾, 和suffixOverrides中相同, 是以suffix替代去掉逗号, 相當于标簽set操作 --> <!-- 在trim标簽内前面加上SET, 在後面去除掉, --> <trim prefix="SET" suffix="" suffixOverrides=","> <if test="name!=null"> name = #{name}, </if> <if test="sn!=null"> sn = #{sn}, </if> <if test="salary!=null"> salary = #{salary}, </if> </trim>
相關trim的介紹: https://www.cnblogs.com/wx60079/p/13212333.html
4、foreach
跳轉到目錄
- SQL中有時候使用
關鍵字,如IN
,此時可以使用WHERE id IN(10,20,30)
直接拼接SQL ,但是會導緻SQL注入問題,要避免SQL注入,隻能使用${ids}
方式,此時就可以配合使用#{}
元素了。foreach元素用于疊代一個foreach
, 通常是建構在IN運算符條件中。集合/數組
MyBatis——動态SQL、if、where、set、foreach、sql片段一、 動态SQL -
元素:foreach
-
屬性:表示對哪一個collection
做疊代集合或數組
- 如果參數是
類型,此時Map的key為數組
;array
- 如果參數是
類型,此時Map的key為List
;list
- 如果參數是
-
屬性:在疊代集合之前,拼接什麼符号open
-
屬性:在疊代集合之後,拼接什麼符号close
-
屬性:在疊代元素時,每一個元素之間使用什麼符号分割開來separactor
-
屬性:被疊代的每一個元素的變量item
-
屬性:疊代的索引index
-
需求: 删除ID為10,20,30的資料
需求: 批量插入文法
注意: 當傳遞一個
List對象
或
Array對象
參數給MyBatis時,(這裡可以看前面講MyBatis參數處理的部分),
MyBatis會自動把它包裝到一個Map中,當是List對象時會以list作為key, 數組對象會以array作為key.
一般使用Param注解設定key名.
EmployeeMapperTest測試類
/**
* 批量删除指定id的員工資訊
*/
@Test
public void test5(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.batchDelete(new Long[]{10L,20L,30L});
sqlSession.commit();
sqlSession.close();
}
/**
* 批量插入員工資訊
*/
@Test
public void test6(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> list = new ArrayList<>();
list.add(new Employee(null, "周", "10001", new BigDecimal("5555.00"), 50L));
list.add(new Employee(null, "吳", "10002", new BigDecimal("6666.00"), 60L));
list.add(new Employee(null, "鄭", "10003", new BigDecimal("7777.00"), 70L));
int count = mapper.batchInsert(list);
if (count > 0){
System.out.println("成功插入了:" + count + "條使用者資訊!");
}
sqlSession.commit();
sqlSession.close();
}
EmployeeMapper接口
/**
* 使用foreach元素批量删除
* @param ids
* param注解原理還是Map,Map的key
*/
void batchDelete(@Param("ids") Long[] ids);
/**
* 批量插入使用者資訊
* @param list
* @return
* 當參數是數組或集合時,一般要加上@Param注解,寫死
*/
int batchInsert(@Param("emps") List<Employee> emps);
EmployeeMapper.xml
<!--使用foreach元素_完成批量删除-->
<delete id="batchDelete">
DELETE FROM employee WHERE id IN
<!--
foreach元素:
collection屬性:表示對哪一個集合或數組做疊代
如果參數是數組類型,此時Map的key為array;
如果參數是List類型,此時Map的key為list;
open屬性:在疊代集合之前,拼接什麼符号
close屬性:在疊代集合之後,拼接什麼符号
separactor屬性:在疊代元素時,每一個元素之間使用什麼符号分割開來
item屬性:被疊代的每一個元素的變量
index屬性:疊代的索引
-->
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
<!--使用foreach元素_完成批量插入-->
<insert id="batchInsert">
INSERT INTO employee(id, name, sn, salary, deptId) VALUES
<foreach collection="emps" separator="," item="e">
(#{e.id}, #{e.name}, #{e.sn}, #{e.salary}, #{e.deptId})
</foreach>
</insert>
兩個内置參數:
<!-- 兩個内置參數:
不隻是方法傳遞過來的參數可以被用來判斷,取值。。。
mybatis預設還有兩個内置參數:
_parameter:代表整個參數
單個參數:_parameter就是這個參數
多個參數:參數會被封裝為一個map;_parameter就是代表這個map
_databaseId:如果配置了databaseIdProvider标簽。
_databaseId就是代表目前資料庫的别名oracle
-->
<!--public List<Employee> getEmpsTestInnerParameter(Employee employee); -->
<select id="getEmpsTestInnerParameter" resultType="Employee">
<!--
_parameter就代表着傳遞過來的參數, 目前指 Employee對象
_databaseId的使用要在mybatis-config中配置多資料源資訊(databaseIdProvider标簽)
-->
<if test="_databaseId=='mysql'">
select * from tbl_employee
<if test="_parameter!=null">
where last_name like #{lastName}
</if>
</if>
<if test="_databaseId=='oracle'">
select * from employees
<if test="_parameter!=null">
where last_name like #{_parameter.lastName}
</if>
</if>
</select>
5、sql、include、bind 标簽
跳轉到目錄
- 使用sql可以把相同的
起一個名字,并使用sql片段
在sql任意位置使用.include
- bind:
使用OGNL表達式建立一個變量,并将其綁定在上下文中.
需求: 按照員工的關鍵字、工資範圍、所屬部門來查詢
需求: 按照查詢條件查詢員工的人數
-
使用sql标簽:
來封裝表的全部字段, 然後通過<sql>片段
來引入;<include>
注意: 要封裝一個查詢條件類,用來設定條件使用
EmployeeQueryObject 封裝查詢條件的
package com.sunny.query;
import lombok.Data;
import java.math.BigDecimal;
/**
* 封裝員工的進階查詢資訊--->封裝查詢條件
*/
@Data
public class EmployeeQueryObject {
private String keyword; // 根據keyword來查詢,員工名字或編号
private BigDecimal minSalary; // 最低工資
private BigDecimal maxSalary; // 最高工資
private Long deptId = -1L; // 部門ID,預設為-1;表示所有部門
/**
* 重寫keyword的get方法,如果
* @return
*/
public String getKeyword(){
// 防止傳的條件是空或空字元
return empty2null(keyword);
}
// 如果字元串為空串,也應該設定為null
private String empty2null(String str){
return hasLength(str) ? str : null;
}
// 判斷這個字元串是否有資料
private boolean hasLength(String str){
// str不為空 并且 str trim()後不和""相等
/**
* 判斷非空,假如str為zy
* zy!=null ---> true
* "".equals(zy.trim()) --> false
* !"".equals(zy.trim()) ---> !false ---> true
* 是以
* true && true ---> true 不為空
*/
return str!=null && !"".equals(str.trim());
}
}
EmployeeMapper接口
public interface EmployeeMapper {
/**
* 根據查詢條件來查詢員工
* @param qo 封裝查詢條件的類對象
* @return
*/
List<Employee> queryForList(EmployeeQueryObject qo);
/**
* 根據查詢條件來查詢員勞工數
* @param qo 封裝查詢條件的類對象
* @return
*/
int queryForEmpCount(EmployeeQueryObject qo);
}
EmployeeMapper.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">
<!--命名空間,類似包的概念: namespace:綁定一個對應的Dao/Mapper接口-->
<mapper namespace="com.sunny.dao.EmployeeMapper">
<!--多個查詢共同使用的sql-->
<sql id="Base_where">
<where>
<if test="keyword!=null">
<bind name="keywordLike" value="'%' + keyword +'%'"/>
<!-- 下面 #{KeywordLike} 用 %keyword%來替代, 這個keyword是實體的屬性 -->
AND (name LIKE #{keywordLike} OR sn LIKE #{keywordLike})
<!--AND (name LIKE concat('%', #{keyword}, '%') OR sn LIKE concat('%', #{keyword}, '%'))-->
</if>
<if test="minSalary!=null">
AND salary >= #{minSalary}
</if>
<if test="maxSalary!=null">
AND salary <=#{maxSalary}
</if>
<if test="deptId!=null">
AND deptId = #{deptId}
</if>
</where>
</sql>
<!--根據查詢條件來查詢符合條件的查詢-->
<select id="queryForList" resultType="Employee">
SELECT * FROM employee
<include refid="Base_where"></include>
</select>
<!--查詢符合條件的員工數量-->
<select id="queryForEmpCount" resultType="int">
SELECT count(*) FROM employee
<include refid="Base_where"></include>
</select>
</mapper>
EmployeeMapperTest測試類
public class EmployeeMapperTest {
/**
* 按照員工的關鍵字、工資範圍、所屬部門來查詢
*/
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
EmployeeQueryObject qo = new EmployeeQueryObject();
qo.setKeyword("2");
qo.setMinSalary(new BigDecimal("1000"));
qo.setMaxSalary(new BigDecimal("9000"));
qo.setDeptId(30L);
List<Employee> employees = mapper.queryForList(qo);
for (Employee employee : employees) {
System.out.println(employee);
}
sqlSession.close();
}
/**
* 按照查詢條件了查詢員工的人數
*/
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
EmployeeQueryObject qo = new EmployeeQueryObject();
qo.setKeyword("2");
qo.setMinSalary(new BigDecimal("1000"));
qo.setMaxSalary(new BigDecimal("9000"));
qo.setDeptId(30L);
int i = mapper.queryForEmpCount(qo);
if (i > 0) {
System.out.println("符合條件的一共有:"+i+"人!");
}
sqlSession.close();
}
}