目錄
我們想,對於SQL映射文件中的sql語句,能夠根據傳入的值的不同來動態的拼接sql語句。此時就可以使用到動態SQL。
環境的準備
新建EmployeeMapperDynamicSQL
接口:
package com.cerr.mybatis.dao;
import com.cerr.mybatis.Employee;
import java.util.List;
public interface EmployeeMapperDynamicSQL {
}
新建EmployeeMapperDynamicSQL.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="com.cerr.mybatis.dao.EmployeeMapperDynamicSQL">
</mapper>
使用if標籤實現表達式判斷
我們可以使用<if>
標籤來對錶達式進行判定,然後作出對應的SQL拼接。<if>
標籤有一個test
屬性用來判斷表達式用的,這裏的表達式採用的是OGNL表達式,對於OGNL表達式的介紹可以參考該文章的部分內容:Struts2學習筆記 | 值棧和OGNL。
擴展
我們應該注意轉義字符,例如""
應該寫爲""
。
我們在接口中新增一個方法:
//攜帶了哪個字段,查詢條件就帶上這個字段
public List< Employee > getEmpsByConditionIf(Employee employee);
測試方法:
package com.cerr.mybatis;
import com.cerr.mybatis.dao.*;
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 java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyBatisTest {
//獲取SQLSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testDynamicSql() throws IOException {
SqlSessionFactory factory = getSqlSessionFactory();
SqlSession session = factory.openSession();
try{
EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
Employee employee = new Employee(1,"%e%",null,null);
List<Employee> employees = mapper.getEmpsByConditionIf(employee);
for(Employee e : employees){
System.out.println(e);
}
}finally {
session.close();
}
}
}
在SQL映射文件中的配置:
<!-- 查詢員工,要求:攜帶了哪個字段,查詢條件就帶上這個字段 -->
<select id="getEmpsByConditionIf" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee
where
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null and lastName!=''">
and last_name like #{lastName}
</if>
<if test="email!=null and email.trim()!=""">
and email=#{email}
</if>
<!-- ognl會進行字符串與數字的轉換判斷 -->
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
</select>
但是這個配置文件的話,有個問題,每次傳入的id
必須不爲空,如果id
爲空的話,假設後面任意一個不爲空(例如lastName
),則sql語句爲select * from tb1_employee where and last_name like ?
,很明顯多了個and
,該sql語句是錯誤的,我們可以使用下面的where
標籤來改善這個問題。
使用<where>
標籤,MyBatis就會將<where>
標籤裏面拼接的sql中多餘的and
和or
去掉。
正確用法:
<select id="getEmpsByConditionIf" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee
<where>
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null and lastName!=''">
and last_name like #{lastName}
</if>
<if test="email!=null and email.trim()!=""">
and email=#{email}
</if>
<!-- ognl會進行字符串與數字的轉換判斷 -->
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
</where>
</select>
但是其只會去掉第一個多餘的and
和or
,如果在<if>
標籤中每次都把and
或or
寫在後面,則該標籤也無法正常去除多餘的and
和or
,所以應該在<if>
標籤中每次都把and
或or
寫在前面。例如下面這種情形就無法正常去除:
<!-- 查詢員工,要求:攜帶了哪個字段,查詢條件就帶上這個字段 -->
<select id="getEmpsByConditionIf" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee
<where>
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null and lastName!=''">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=""">
email=#{email} and
</if>
<!-- ognl會進行字符串與數字的轉換判斷 -->
<if test="gender==0 or gender==1">
gender=#{gender}
</if>
</where>
</select>
使用trim標籤實現字符串截取
<trim>
標籤體中是整個字符串拼串後的結果,有四個屬性:
prefix
:前綴,給拼串後的整個字符串加一個前綴prefixOverrides
:前綴覆蓋,去掉整個字符串前面多餘的字符suffix
:後綴,給拼串後的整個字符串加一個後綴suffixOverrides
:後綴覆蓋,去掉整個字符串後面多餘的字符
配置可修改如下:
<select id="getEmpsByConditionTrim" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null and lastName!=''">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=""">
email=#{email} and
</if>
<!-- ognl會進行字符串與數字的轉換判斷 -->
<if test="gender==0 or gender==1">
gender=#{gender}
</if>
</trim>
</select>
測試方法:
package com.cerr.mybatis;
import com.cerr.mybatis.dao.*;
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 java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyBatisTest {
//獲取SQLSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testDynamicSql() throws IOException {
SqlSessionFactory factory = getSqlSessionFactory();
SqlSession session = factory.openSession();
try{
EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
Employee employee = new Employee(null,"%e%",null,null);
List<Employee> employees = mapper.getEmpsByConditionTrim(employee);
for(Employee e : employees){
System.out.println(e);
}
}finally {
session.close();
}
}
}
使用choose標籤來實現分支選擇
相當於java中的switch
語句,但是是有加break
的,可配合<when>
和<otherwise>
標籤使用。
我們現在想實現以下功能,如果傳入id
就用id
查,如果傳入lastName
就用lastName
查,只會選一個。
接口新增方法:
public List<Employee> getEmpsByConditionChoose(Employee employee);
SQL映射文件:
<select id="getEmpsByConditionChoose" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee
<where>
<!-- 如果傳入`id`就用`id`查,如果傳入`lastName`就用`lastName`查,只會選一個 ,如果這兩個都沒有傳,則查女生-->
<choose>
<when test="id!=null">
id = #{id}
</when>
<when test="lastName!=null">
last_name like #{lastName}
</when>
<otherwise>
gender = 0
</otherwise>
</choose>
</where>
</select>
測試方法:
@Test
public void testDynamicSql() throws IOException {
SqlSessionFactory factory = getSqlSessionFactory();
SqlSession session = factory.openSession();
try{
EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
Employee employee = new Employee(null,"%e%",null,null);
List<Employee> employees = mapper.getEmpsByConditionChoose(employee);
for(Employee e : employees){
System.out.println(e);
}
}finally {
session.close();
}
}
測試方法中傳入的是lastName
,則發送的sql爲:
Preparing: select * from tb1_employee WHERE last_name like ?
結果:
如果改爲Employee employee = new Employee(null,null,null,null);
,則發送的sql爲:
select * from tb1_employee WHERE gender = 0
結果如下:
使用if與set標籤來實現動態的update語句
<set>
標籤可以替換掉我們之前update
語句中的set
,並且可以支持將後面多餘的,
去掉。
我們有一個需求,對於tb1_employee
表,我們要更新其一條記錄,但是我們的目標是,傳入哪些參數,我們就更新哪些字段。我們如果單純用<if>
標籤的話,就是下面的配置:
<update id="updateEmp" >
update tb1_employee set
<if test="lastName!=null">
last_name = #{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
where id=#{id}
</update>
但是這樣會出問題,假設我只傳了一個last_name
字段,則SQL語句是這樣的:
update tb1_employee set last_name = ? , where id = ?
可以看到在last_name = ?
後面多了一個,
,這個時候我們就可以使用<set>
標籤來進行改進了,改進版如下:
<update id="updateEmp" >
update tb1_employee
<set>
<if test="lastName!=null">
last_name = #{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
</set>
where id=#{id}
</update>
此時如果只傳入last_name
,也不會有多餘的逗號,發送的SQL爲:
update tb1_employee set last_name = ? where id=?
同樣也可以使用<trim>
標籤來進行修改,加上前綴的set
,並且取出後綴中多餘的,
:
<update id="updateEmp" >
update tb1_employee
<trim prefix="set" suffixOverrides=",">
<if test="lastName!=null">
last_name = #{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
</trim>
where id=#{id}
</update>
剩下的代碼:
接口方法:
public void updateEmp(Employee employee);
測試方法:
package com.cerr.mybatis;
import com.cerr.mybatis.dao.*;
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 java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyBatisTest {
//獲取SQLSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testDynamicSql() throws IOException {
SqlSessionFactory factory = getSqlSessionFactory();
SqlSession session = factory.openSession();
try{
EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
Employee employee = new Employee(1,"Admin",null,null);
mapper.updateEmp(employee);
session.commit();
}finally {
session.close();
}
}
}
foreach標籤
<foreach>
標籤可以使用遍歷集合,有如下幾個屬性:
collection
:指定要遍歷的集合,其中List
類型的參數會特殊處理封裝在Map
中,Map
的key就叫list
。item
:將當前遍歷出的元素賦值給指定的變量separator
:每個元素之間的分隔符open
:遍歷出所有結果拼接一個開始的字符close
:遍歷出所有結果拼接一個結束的字符index
:索引。當遍歷list
的時候index就是索引,item
就是當前值;當遍歷map的時候index
表示的就是map的key,item
就是map的值。
使用#{變量名}
就能取出變量的值也就是當前遍歷出的元素。
使用foreach標籤來遍歷集合
我們下面想實現一個循環查詢,即我在方法中傳入一個id數組,然後使用<foreach>
來讓我們的查詢條件可以動態的改變,即我傳入幾個id就查那幾個id對應的記錄,例如如下的sql語句:
select * from tb1_employee where id in(1,4);
select * from tb1_employee where id in(1,2,3,4,5,6);
根據傳入的id的集合來查詢對應的記錄。
首先接口的方法如下:
public List<Employee> getEmpsByConditionForeach(@Param("ids") List<Integer> ids);
在該接口中,我們使用了@Param
註解來傳入命名參數,以便我們SQL映射文件的collection
屬性來使用該集合。有關參數處理可以看這篇文章點擊查看:MyBatis筆記 | 詳解參數處理(多種類型的參數處理、源碼分析、讀取參數的兩種格式的區別)。
SQL映射文件如下:
<select id="getEmpsByConditionForeach" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee where id in
<foreach collection="ids" item="item_id" separator="," open="(" close=")">
#{item_id}
</foreach>
</select>
測試方法:
package com.cerr.mybatis;
import com.cerr.mybatis.dao.*;
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 java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyBatisTest {
//獲取SQLSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testDynamicSql1() throws IOException {
SqlSessionFactory factory = getSqlSessionFactory();
SqlSession session = factory.openSession();
try{
EmployeeMapperDynamicSQL mapper = session.getMapper(EmployeeMapperDynamicSQL.class);
List<Employee> employees = mapper.getEmpsByConditionForeach(Arrays.asList(1,4));
for(Employee e : employees){
System.out.println(e);
}
}finally {
session.close();
}
}
因爲我們此時方法傳遞的List
集合只有兩個元素,因此發送的sql如下:
Preparing: select * from tb1_employee where id in( ? , ? )
結果:
mysql下使用foreach實現批量插入的兩種方式
第一種是直接使用insert into table_name(...) values(),(),()
這種語法格式,即是使用foreach
遍歷values
後面的括號。
接口方法如下:
public void addEmps(@Param("emps") List<Employee> emps);
配置如下:
<insert id="addEmps">
insert into tb1_employee(last_name,email,gender,d_id)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
</foreach>
</insert>
第二種是每插入一條語句就發一條insert
語句,即使用foreach
循環遍歷insert
語句,中間用;
分割,接口同上。
使用這種方法的話需要在數據庫連接中設置一個allowMultiQueries
屬性,表示允許多個查詢之間使用;
分割,即url爲:
jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true
配置如下:
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into tb1_employee(last_name,email,gender,d_id)
values
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
</foreach>
</insert>
兩個內置參數
不止是方法傳遞過來的參數可以被用來判斷,取值。MyBatis默認還有兩個內置參數:
_parameter
:代表整個參數。如果傳過來的參數是單個參數,那麼_parameter
就是這個傳過來的參數;如果傳過來的參數是多個參數,則參數會被封裝成一個map,_parameter
就是這個map。_databaseId
:如果配置了DatabaseIdProvider標籤,那麼_databaseId
就是代表當前數據庫的別名。
例如我們現在想編寫一個sql映射:當數據庫爲mysql、Oracle時使用不同的sql,且當傳入的Employee
參數爲空時,就不帶條件查詢,那麼就可以使用到上面這兩個參數了。
接口如下:
public List<Employee> getEmpsTestInnerParameter(Employee employee);
配置如下:
<select id="getEmpsTestInnerParameter" resultType="com.cerr.mybatis.Employee">
<if test="_databaseId=='mysql'">
select * from tb1_employee
<if test="_parameter!=null">
where last_name = #{_parameter.lastName}
</if>
</if>
<if test="_databaseId=='oracle'">
select * from employee
</if>
</select>
當我們傳入的Employee
不爲空時,sql語句爲:
select * from tb1_employee where last_name = ?
當傳入的Employee
爲空時,sql語句爲:
select * from tb1_employee
使用bind標籤來進行動態綁定
可以將OGNL表達式的值綁定到一個變量中,方便後面引用這個變量的值。
<select id="getEmpsTestInnerParameter" resultType="com.cerr.mybatis.Employee">
<bind name="_lastName" value="'%'+lastName+'%'"/>
select * from tb1_employee
<if test="_parameter!=null">
where last_name like #{_lastName}
</if>
</select>
上述SQL映射中的<bind>
標籤將傳入的lastName
值的前後加了%
並賦值給_lastName
,在sql語句中我們就可以使用#{_lastName}
參數來做模糊查詢的條件。
使用sql標籤來抽取重用的sql片段
在sql語句中,總是存在一些可以重用的sql片段。我們可以使用<sql>
標籤來抽取這些可重用的sql片段,然後使用<include>
標籤來引用它,<include>
裏面還可以自定義一些property
,在<sql>
標籤內部就可以通過${變量名}
來使用這些自定義屬性(不能使用#{}
來取這些自定義屬性的值)
<sql id="insertColumn">
last_name,email,gender,d_id
</sql>
上述代碼,我們使用sql
抽取了tb1_employee
表的部分字段,接下來我們要插入表的時候就可以直接引用:
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into tb1_employee(
<include refid="insertColumn"/>
)
values
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
</foreach>
</insert>
相當於:
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into tb1_employee(
last_name,email,gender,d_id
)
values
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
</foreach>
</insert>
許多可以在sql裏面寫的標籤在該標籤裏面也可以用。