SSM框架開發web項目系列(四) MyBatis之快速掌握動態SQL

  前言

        經過前面的MyBatis部分學習,已經可使用MyBatis獨立構建一個數據庫程序,基本的增刪查改/關聯查詢等等均可以實現了。簡單的單表操做和關聯查詢在實際開的業務流程中必定會有,可是可能只會佔一部分,不少業務需求每每夾雜着一些須要咱們在後臺去判斷的參數,舉個例子,咱們基本都上過購物網站,想要查看心儀的商品列表,能夠經過商品分類篩選,也能夠經過商品價格來篩選,還能夠同時根據分類和價格來篩選,這裏咱們能夠簡單的理解爲經過商品分類和商品價格的篩選分別爲select語句中where後面的2個子句,相似category=XXX和price > xxx and price <xxx,具體怎麼篩選要看用戶怎麼操做。若是按照以前的路子,咱們要分別定義三個select方法和sql語句,這個就涉及到了一個靜態動態的問題,用戶的操做、輸入等等都是不肯定的,即動態的,可是咱們以前在sql映射文件中寫的語句都是針對單個操做單個想法去寫死的,即靜態的。這樣一來,隨着需求和判斷的的不斷疊加,這個代碼量會很可怕。另一個問題,若是你們有使用Java代碼拼接過複雜SQL語句經歷,應該不會感到很方便,本人使用hibernate的時候也拼接過HQL,共同點就是那些分隔符、空格之類的寫起來很麻煩,也容易出錯。MyBatis提供了動態SQL這一特性,能同時改良上述兩種開發場景。java

  總覽

        MyBatis提供的動態SQL元素實際就是經過在咱們的sql語句中嵌入標籤實現動態,具體標籤以下圖所示。mysql

       

        熟悉jsp、jstl、el表達式那套的,應該對裏面大部分標籤名都不陌生,也比較容易理解,具體用法下面分別進行解析。爲告終合具體的使用場景,將上面元素細分爲四組來演示,分別爲【if、where、trim】、【if、set、trim】、【choose、when、otherwise】、【foreach】sql

  背景

private Integer id;         //主鍵
private String name;        //姓名
private String gender;      //性別
private Integer age;        //年齡
private String ifIT;        //是否從事IT行業

  if、where、trim篇

  1.查詢語句中的if

  以上爲咱們定義的一我的的屬性,數據庫中也有一我的的數據表。如今假設須要查詢人中的全部男性,同時若是輸入參數中年齡不爲空,就根據性別和年齡查詢。在沒有使用動態SQL以前,按照咱們的慣有思路,咱們須要在Mapper接口中定義兩個查詢方法,同時分別對應在SQL映射文件中定義兩個<select>語句,以下:數據庫

<select id="selectPerson1" parameterType="psn" resultMap="personResultMap">
    select * from person where GENDER  = '男'
</select>

<select id="selectPerson2" parameterType="psn" resultMap="personResultMap">
    select * from person where GENDER  = '男' and AGE = #{age}
</select>

  這樣一來,隨着相似的須要愈來愈多,咱們的方法和SQL語句量會增長到不少,而且會發現,其實語句中存在不少重複部分。那麼有沒有辦法能同時應對相似的相關需求,同時減小代碼量呢?動態SQL就提供了相關的功能實現這些需求,例如上述場景,咱們便可只需定義一個方法,對應的SQL語句寫成以下:apache

<select id="selectPerson" parameterType="psn" resultMap="personResultMap">
    select * from person where GENDER  = '男'
        <if test="age != null">
            and AGE = #{age}
        </if>
</select>

  在這項<select>咱們將肯定的(靜態的的部分)select * from person where GENDER = '男'和後面的<if>部分結合起來,經過動態SQL提供的<if>標籤給語句預加一層判斷,test屬性值爲布爾類型,true或者false,當爲true(即真)時,纔會把<if>標籤下的內容添加到語句中響應爲值,這裏的test中即判斷輸入參數中年齡是否爲空,不爲空則添加【and AGE = #{age}到【select * from person where GENDER = '男'】後面,爲空則不加,這樣就達到了同時知足兩種須要,但只定義了一個方法和一條SQL。session

  2.查詢語句中if的where改進

  進一步擴展若是想把where後面的部分都動態化,這裏以性別爲例,查詢時若是參數中有不爲空的性別值,則根據性別查詢,反之則查詢全部,有了前面if的學習,咱們不難寫出以下動態SQL:mybatis

<select id="selectPerson1" parameterType="psn" resultMap="personResultMap">
    select * from person where
        <if test="gender != null">
            gender = #{gender}
        </if>
</select>

 

  這時候問題來了,當性別不爲空時,語句是 select * from person where gender = #{gender} ,這樣還能正常查詢出咱們想要的結果,可是若是性別爲空,會發現語句變成了 select * from person where ,這顯然是生成一個錯誤的SQL了,爲了解決相似的問題,動態SQL<where>能幫咱們解決這個問題,咱們能夠將上述語句優化成以下:app

<select id="selectPerson1" parameterType="psn" resultMap="personResultMap">
    select * from person
    <where>
        <if test="gender != null">
            gender = #{gender}
        </if>
    </where>
</select>

  這樣mybatis在這裏會根據<where>標籤中是否有內容來肯定要不要加上where,在這裏使用<where>後,若是年齡爲空,則前面引起錯誤的where也不會出現了。jsp

  3.針對where的trim同等轉換

  進一步擴展,若是咱們查詢有多個參數須要判斷,根據性別和年齡參數,有了前面<if>和<where>的瞭解,咱們就能夠寫出以下SQL:maven

<select id="selectPerson2" parameterType="psn" resultMap="personResultMap">
    select * from person
    <where>
        <if test="gender != null">
            gender = #{gender}
        </if>
        <if test="age != null">
            and age = #{age}
        </if>
    <where>
</select>

  乍一看基本沒什麼毛病了,在這裏【性別空、年齡空】、【性別不空、年齡不空】、【性別不空、年齡空】都沒問題,可是若是是【性別空、年齡不爲空】,按理來講語句變成這樣 select * from person where and age = #{age} ,而後若是你動手嘗試一下,就會發現,並不會,這也體現了<where>一個強大之處,它不只會根據元素中內容是否爲空決定要不要添加where,還會自動過濾掉內容頭部的and或者or,另外空格之類的問題也會智能處理。

  MyBatis還提供了一種更靈活的<trim>標籤,在這裏能夠替代<where>,如上面的定義能夠修改爲以下,一樣能夠實現效果:

<select id="selectPerson2" parameterType="psn" resultMap="personResultMap">
    select * from person
    <trim prefix="where" prefixOverrides="and |or " >
        <if test="gender != null">
            and gender = #{gender}
        </if>
        <if test="age != null">
            and age = #{age}
        </if>
    </trim>
</select>

  <trim>標籤共有四個屬性,分別爲【prefix】、【prefixOverrides】、【suffix】、【suffixOverrides】,prefix表明會給<trim>標籤中內容加上的前綴,固然前提是內容不爲空,prefixOverrides表明前綴過濾,過濾的是內容的前綴,suffix和suffixOverrides則分別對應後綴。例如上面的語句中若是性別和年齡都不爲空,<trim>會在添加where前綴的同時,把第一個<if>中的and去掉,這樣來實現<where>一樣的功能。

  if、set、trim篇

  1.更新語句中的if

  前面都是查詢,咱們換個更新試試,這裏更新咱們只想更新部分字段,並且要根據參數是否爲空來肯定是否更新,這裏以姓名和性別爲例。

<update id="updatePerson">
    update person set
    <if test="name != null">
        NAME = #{name},
    </if>
    <if test="gender != null">
        GENDER = #{gender}
    </if>
</update>

  2.更新語句中if的set改進

  這裏若是【姓別爲空】或者【性別和性別都爲空】,相似以前的where問題一樣也來了,因此MyBatis一樣也提供了<set>標籤來解決這一問題,因此上面的定義能夠優化成以下所示

<update id="updatePerson1">
    update person
    <set>
     
<if test="name != null"> NAME = #{name}, </if> <if test="gender != null"> GENDER = #{gender}, </if> </set>
</update>

   在這裏,<set>會根據標籤中內容的有無來肯定要不要加上set,同時能自動過來內容後綴逗號,可是有一點要注意,不一樣於<where>,當<where>中內容爲空時,咱們能夠查出全部人的信息,可是這裏更新語句中,<set>內容爲空時,語句變成 update person ,仍是會出錯,同時更新操做也不會生效了。

  3.針對set的trim同等轉換

  以前介紹到的<trim>靈活之處,在這裏經過修改屬性值,也能用來替代<set>,上面的定義使用<trim>改寫以下所示:

<update id="updatePerson2">
    update person
    <trim prefix="set" suffixOverrides=",">
        <if test="name != null">
            NAME = #{name},
        </if>
        <if test="gender != null">
            GENDER = #{gender}
        </if>
    </trim>
</update>

  這裏設置前綴爲set,後綴過濾爲逗號「,」,這樣一來,在if判斷以後會引起報錯的逗號將不會存在,一樣能夠實現相關功能。

  choose、when、otherwise篇

  前面咱們瞭解到的,<select>查詢語句中where後面,<if>經過判斷各個參數是否爲空來肯定是否添加其子句內容到where後面,是一個累加的關係,可是若是咱們的需求,不是累加,而是多選一,例如姓名(name)、性別(gender)、是否從事It行業(ifIT),具體來說就是,若是優先判斷姓名是否有值,有的話就根據姓名查,沒有的話其次再判斷性別是否有值,有的話就根據性別查,也沒有的話就根據是否從事IT行業來查。這樣一來,按照前面瞭解到的<if><where>彷佛有些頭大,不只有多重判斷,還涉及到一個優先級前後問題,一會兒彷佛很難快速想到一個簡單方便的路子。MyBatis一樣提供了一套<choose>、<when>、<otherwise>來幫咱們解決相似上面的問題。

  若是以爲不太好記憶,能夠聯想Java中條件判斷的switch,switch對應這裏的<choose>,case對應<when>,一旦某個case條件知足了會break跳出,同時若是都不知足,最後還有個default能夠對應這裏的<otherwise>,因此最終<when>和<otherwise>中有且只有一個會知足,也就只有一項內容會被添加進去。按照上面的需求,咱們能夠寫出以下動態SQL:

<select id="selectPersonExt" parameterType="psn" resultMap="personResultMap">
    select * from person
    <where>
        <choose>
            <when test="name!=null">
                NAME = #{name}
            </when>
            <when test="gender!=null">
                GENDER = #{gender}
            </when>
            <otherwise>
                IF_IT = #{ifIT}
            </otherwise>
        </choose>
    </where>
</select>

  便可方便的實現該場景。

  foreach篇

  for循環你們應該都不陌生,這裏的foreach一樣主要用來迭代集合,那麼SQL中哪些地方會用到集合呢,用過in的應該比較熟悉,例以下面select語句:

select * from person where age in(10,20,30,40)

  上面語句能夠查詢出年齡爲10或20或30或40的人,這些年齡數據是一個集合,可是經過參數傳入的集合是動態的,咱們不可能預知數值和像這樣寫死,MyBatis提供的<foreach>便可實現該功能。該集合做爲參數傳入,以上場景方法定義和SQL語句能夠寫成以下所示:

List<Person> selectForeachAge(List<Integer> ageList);
<select id="selectForeachAge" resultMap="personResultMap">
    select * from person where age in
    <foreach collection="list" item="age" index="i" open="(" close=")" separator=",">
        #{age}
    </foreach>
</select>

   這裏咱們能夠看到,<foreach>共有6個屬性。

  item:表示集合迭代時元素的別名,這裏對應#{age}中的age

  index:集合迭代時索引,用於表示集合此時迭代到的位置

  open、close、separator:這三個分別表明起始、結束、分隔符,在這裏,咱們用過in語句查詢時應該都知道,使用到集合的查詢語句結構關鍵部分能夠描述以下所示: SELECT column_name(s) FROM table_name WHERE column_name IN (value1,value2,...) 。能夠看到在IN關鍵字的後面,集合內容兩邊分別是左括號、右括號,中間集合的各個元素之間用逗號隔開,正好與這裏的三個屬性分別對應。

  collection:這個屬性是必要的,在這裏咱們傳入的是單個參數即一個List,因此屬性值就是list。

  完整示例

  上面就是動態SQL的各個元素的基本內容,熟悉以後會讓咱們編寫SQL時更加方便和靈活,下面給出完整代碼示例供參考。

  maven工程結構以下圖

  

  MyBatis配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 這裏能夠定義類的別名,在mapper.xml文件中應用會方便不少 -->
    <typeAliases>
        <typeAlias alias="psn" type="com.mmm.pojo.Person" />
    </typeAliases>
    <!-- 環境配置 -->
    <environments default="envir">
        <environment id="envir">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.0.100:3306/ssm?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/mmm/mapper/personMapper.xml"/>
    </mappers>
</configuration>

  實體類(Person)

package com.mmm.pojo;

public class Person {
    private Integer id;            //主鍵
    private String name;        //姓名
    private String gender;        //性別
    private Integer age;        //年齡
    private String ifIT;        //是否從事IT行業
    
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getIfIT() {
        return ifIT;
    }
    public void setIfIT(String ifIT) {
        this.ifIT = ifIT;
    }
    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + ", gender=" + gender
                + ", age=" + age + ", ifIT=" + ifIT + "]";
    }
    
}

  Mapper接口(PersonMapper)

package com.mmm.mapper;

import java.util.List;

import com.mmm.pojo.Person;

public interface PersonMapper {
    
    //查找全部Person對象,返回集合類型,用於在測試類中查看動態SQL結果
    List<Person> selectAll();
    
    //用於測試查詢語句中if、where、trim
    List<Person> selectPerson(Person p);
    List<Person> selectPerson1(Person p);
    List<Person> selectPerson2(Person p);
    
    //用於測試更新語句中if、set、trim
    void updatePerson(Person p);
    void updatePerson1(Person p);
    void updatePerson2(Person p);
    
    //用於測試choose、where、otherwise
    List<Person> selectPersonExt(Person p);
    
    //用於測試foreach
    List<Person> selectForeachAge(List<Integer> ageList);
    
}

  SQL映射文件(personMapper.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.mmm.mapper.PersonMapper">
    <resultMap type="psn" id="personResultMap">
        <id column="ID" property="id" />
        <result column="NAME" property="name" />
        <result column="GENDER" property="gender" />
        <result column="AGE" property="age" />
        <result column="IF_IT" property="ifIT" />
    </resultMap>
    
    <select id="selectAll" resultMap="personResultMap">
        select * from person
    </select>
    
    
    <!-- 針對查詢語句中if -->
    <select id="selectPerson" parameterType="psn" resultMap="personResultMap">
        select * from person where GENDER  = '男'
            <if test="age != null">
                and AGE = #{age}
            </if>
    </select>
    
    <!-- 針對where -->
    <select id="selectPerson1" parameterType="psn" resultMap="personResultMap">
        select * from person
        <where>
            <if test="gender != null">
                gender = #{gender}
            </if>
        </where>
    </select>
    
    <!-- 針對where的 trim轉換 -->
    <select id="selectPerson2" parameterType="psn" resultMap="personResultMap">
        select * from person
        <trim prefix="where" prefixOverrides="and |or " >
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="age != null">
                and age = #{age}
            </if>
        </trim>
        <trim prefix="" prefixOverrides="" suffix="" suffixOverrides=""></trim>
    </select>
    
    
    <!-- 針對更新語句中if -->
    <update id="updatePerson">
        update person set
        <if test="name != null">
            NAME = #{name},
        </if>
        <if test="gender != null">
            GENDER = #{gender}
        </if>
        where ID = #{id}
    </update>
    
    <!-- 針對set -->
    <update id="updatePerson1">
        update person
        <set>
            <if test="name != null">
                NAME = #{name},
            </if>
            <if test="gender != null">
                GENDER = #{gender}
            </if>
        </set>
    </update>
    
    <!-- 針對set的trim轉換 -->
    <update id="updatePerson2">
        update person
        <trim prefix="set" suffixOverrides=",">
            <if test="name != null">
                NAME = #{name},
            </if>
            <if test="gender != null">
                GENDER = #{gender}
            </if>
        </trim>
    </update>
    
    <!-- choose when otherwise -->
    <select id="selectPersonExt" parameterType="psn" resultMap="personResultMap">
        select * from person
        <where>
            <choose>
                <when test="name!=null">
                    NAME = #{name}
                </when>
                <when test="gender!=null">
                    GENDER = #{gender}
                </when>
                <otherwise>
                    IF_IT = #{ifIT}
                </otherwise>
            </choose>
        </where>
    </select>
    
    <!-- foreach -->
    <select id="selectForeachAge" resultMap="personResultMap">
        select * from person where age in
        <foreach collection="list" item="age" index="i" open="(" close=")" separator=",">
            #{age}
        </foreach>
    </select>
    
</mapper>

  最後測試

package com.mmm.test;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

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.mmm.mapper.PersonMapper;
import com.mmm.pojo.Person;

public class TestDB {
    
    static PersonMapper mapper;
    
    static {
        //直接實例SqlSessionFactoryBuilder對象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //MyBatis配置文件路徑
        String path = "mybatis-config.xml";
        //經過路徑獲取輸入流
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //經過reader構建sessionFactory
        SqlSessionFactory sessionFactory = builder.build(reader);
        //獲取SqlSession對象
        SqlSession sqlSession = sessionFactory.openSession();
        //獲取Mapper實例
        mapper = sqlSession.getMapper(PersonMapper.class);
    }
    
    @Test
    public void testSelect() throws Exception {
        Person p = new Person();
        //p.setAge(11);
        //p.setGender("男");
        List<Person> list = mapper.selectPerson2(p);
        for(Person psn:list) {
            System.out.println(psn);
        }
    }
    
    @Test
    public void testUpdate() throws Exception {
        Person p = new Person();
        p.setId(10001);
        //p.setName("小改2");
        //p.setGender("男");
        mapper.updatePerson2(p);
        List<Person> list = mapper.selectAll();
        for(Person psn:list) {
            System.out.println(psn);
        }

    }
    
    @Test
    public void testSelectExt() throws Exception {
        Person p1 = new Person();
        //p1.setName("小紅");
        //p.setGender("男");
        p1.setIfIT("是");
        List<Person> list = mapper.selectPersonExt(p1);
        for(Person psn:list) {
            System.out.println(psn);
        }

    }
    
    @Test
    public void testSelectForeachAge() throws Exception {
        List<Integer> ageList = new ArrayList<Integer>();
        ageList.add(21);
        ageList.add(25);
        ageList.add(36);
        List<Person> list = mapper.selectForeachAge(ageList);
        for(Person psn:list) {
            System.out.println(psn);
        }

    }
}

  小結 

  以上即爲MyBatis動態SQL的內容,在測試類中能夠各類嘗試改變各類輸入值,來查看效果,文中雖然每一個元素都涉及到了,可是有些地方還存在不足,並未過多深刻擴展,例如最後的foreach,咱們的參數不必定是單個,並且也不必定是集合,這些狀況咱們都該怎麼處理,按本身的須要再去深刻學習和了解,每每很快會有深入印象。一個問題與方法的前後問題,當遇到問題後,順着問題去找方法以後,每每很好記住。反之,沒有問題和應用場景,單純的學習方法和理論效果應該會遜色一些。最後首要仍是把這些基礎的東西搞懂,再去慢慢延伸。

相關文章
相關標籤/搜索