MyBatis筆記 | 詳解動態SQL

目錄

環境的準備

使用if標籤實現表達式判斷

使用trim標籤實現字符串截取

使用choose標籤來實現分支選擇

使用if與set標籤來實現動態的update語句

foreach標籤

使用foreach標籤來遍歷集合

mysql下使用foreach實現批量插入的兩種方式

兩個內置參數

使用bind標籤來進行動態綁定

使用sql標籤來抽取重用的sql片段


我們想,對於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

擴展

我們應該注意轉義字符,例如""應該寫爲&quot;&quot;

我們在接口中新增一個方法:

    //攜帶了哪個字段,查詢條件就帶上這個字段
    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()!=&quot;&quot;">
                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中多餘的andor去掉。
正確用法:

    <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()!=&quot;&quot;">
                and email=#{email}
            </if>
            <!-- ognl會進行字符串與數字的轉換判斷 -->
            <if test="gender==0 or gender==1">
                and gender=#{gender}
            </if>
        </where>
    </select>

但是其只會去掉第一個多餘的andor,如果在<if>標籤中每次都把andor寫在後面,則該標籤也無法正常去除多餘的andor,所以應該在<if>標籤中每次都把andor寫在前面。例如下面這種情形就無法正常去除:

<!-- 查詢員工,要求:攜帶了哪個字段,查詢條件就帶上這個字段 -->
    <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()!=&quot;&quot;">
                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()!=&quot;&quot;">
                 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 ? 

結果:

 

 
13424350-34f80218c03512de.png
 

 

如果改爲Employee employee = new Employee(null,null,null,null);,則發送的sql爲:

select * from tb1_employee WHERE gender = 0

結果如下:

 

 
13424350-65b7ad44ec7ef44d.png
 

 


使用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( ? , ? ) 

結果:

 

 
13424350-3e739cdd11d42a38.png
 

 

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裏面寫的標籤在該標籤裏面也可以用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章