mybatis 09: 動態sql — part1

作用

  • 可以定義程式碼片段
  • 可以進行邏輯判斷
  • 可以進行循環處理(批量處理),使條件判斷更為簡單

使用方式

  • 通過mybatis中與動態sql有關的標籤來實現

< sql >標籤 + < include >標籤

作用

  • < sql >標籤:可以用來定義程式碼片段
  • < include >標籤:對定義的程式碼片段進行引用

用法

UsersMapper.xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "//mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.UsersMapper">

    <!--
        自定義程式碼片段
    -->
    
    <sql id="allColumns">
            id, username, birthday, sex, address
    </sql>
    
    <!--
        //獲取全部用戶資訊
        List<Users> getAll();
    -->
    
    <select id="getAll" resultType="user">
        select
            <include refid="allColumns"/>
        from
            users
    </select>

</mapper>

< if > 標籤 + < where >標籤

作用

  • < if >標籤:進行條件判斷
  • < where >標籤:進行多條件拼接,在查詢,刪除,更新中使用

用法

UsersMapper.java介面程式碼

package com.example.mapper;

import com.example.pojo.User;
import java.util.List;

/**
 * 數據訪問層的介面,定義對資料庫完成的CRUD的操作
 */
public interface UsersMapper {

    //根據給定的查詢條件進行多條件查詢
    List<User> selectOnCondition(User user);

}

UsersMapper.xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "//mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.UsersMapper">

    <!--
        //根據給定的查詢條件進行多條件查詢
        List<User> selectOnCondition(User user);

        private Integer id;
        private String userName;
        private Date birthday;
        private String sex;
        private String address;
    -->
    <select id="selectOnCondition" parameterType="user" resultType="user">
        select
            <include refid="allColumns"/>
        from
            users
        <where>
            <if test="userName != null and userName != ''">
                and username like concat('%', #{userName},'%')
            </if>

            <if test="birthday != null">
                and birthday=#{birthday}
            </if>

            <if test="sex != null and sex != ''">
                and sex=#{sex}
            </if>

            <if test="address != null and address != ''">
                and address=#{address}
            </if>
        </where>
    </select>

</mapper>
  • < where >標籤相當於在原sql語句後補加了一個:where關鍵字,從底層輸出的結果可以證實
==>  Preparing: select id, username, birthday, sex, address from users WHERE username like concat('%', ?,'%')
  • 注意:至少有一個< if >標籤通過,where關鍵字才會被追加,不然不會追加
    @Test
    public void testSelectOnCondition(){
        User u = new User();
        List<User> users = usersMapper.selectOnCondition(u);
        users.forEach(System.out::println);
    }
Checking to see if class com.example.mapper.TestUsersMapper matches criteria [is assignable to Object]
Checking to see if class com.example.mapper.UsersMapper matches criteria [is assignable to Object]
Opening JDBC Connection
Created connection 1340848245.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4febb875]
    
==>  Preparing: select id, username, birthday, sex, address from users        //這時sql語句後面沒有追加where
    
==> Parameters: 
<==    Columns: id, username, birthday, sex, address
<==        Row: 1, 荷包蛋, 2002-08-23, 女, 黑河
<==        Row: 2, 小王, 2001-07-12, 1, 蕪湖市
<==        Row: 3, 小張, 1999-02-22, 1, 長沙
<==        Row: 5, 段, 2001-03-10, 1, 太原
<==        Row: 6, 范成群, 2002-01-19, 1, 鮁魚圈
<==        Row: 7, 學委, 2001-05-13, 2, 平頂山市
<==        Row: 28, 邏輯, 2010-05-18, 男, 上海
<==        Row: 29, 小昕, 2001-03-14, 女, 忻州
<==        Row: 30, 小青, 1996-11-22, 女, 瀋陽市
<==      Total: 9
Users{id=1, userName='荷包蛋', birthday=Fri Aug 23 00:00:00 CST 2002, sex='女', address='黑河'}
Users{id=2, userName='小王', birthday=Thu Jul 12 00:00:00 CST 2001, sex='1', address='蕪湖市'}
Users{id=3, userName='小張', birthday=Mon Feb 22 00:00:00 CST 1999, sex='1', address='長沙'}
Users{id=5, userName='段', birthday=Sat Mar 10 00:00:00 CST 2001, sex='1', address='太原'}
Users{id=6, userName='范成群', birthday=Sat Jan 19 00:00:00 CST 2002, sex='1', address='鮁魚圈'}
Users{id=7, userName='學委', birthday=Sun May 13 00:00:00 CST 2001, sex='2', address='平頂山市'}
Users{id=28, userName='邏輯', birthday=Tue May 18 00:00:00 CST 2010, sex='男', address='上海'}
Users{id=29, userName='小昕', birthday=Wed Mar 14 00:00:00 CST 2001, sex='女', address='忻州'}
Users{id=30, userName='小青', birthday=Fri Nov 22 00:00:00 CST 1996, sex='女', address='瀋陽市'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4febb875]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4febb875]
Returned connection 1340848245 to pool.

Process finished with exit code 0
  • 在< if >標籤中的test屬性中,用於條件判斷的是入參的屬性
    • 如果該屬性不為空(對於String類型的屬性,既要不等於null,又要是非空串),標籤中的條件追加到原sql語句後
    • 注意:如果只是追加了一個條件,mybatis框架會自動去除該條件所帶的and

測試程式碼

package com.example.mapper;

import com.example.pojo.User;
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.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

import java.util.List;


public class TestUsersMapper {

    //SqlSession對象
    SqlSession sqlSession;

    //mybatis動態代理對象
    UsersMapper usersMapper;

    //獲取SqlSession
    @Before
    public void getSqlSession() throws IOException {
        //讀取核心配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //創建SqlSessionFactory對象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //獲取SqlSession
        sqlSession = factory.openSession();
        //獲取mybatis動態代理對象
        usersMapper = sqlSession.getMapper(UsersMapper.class);
    }

    //歸還SqlSession
    @After
    public void closeSession(){
        sqlSession.close();
    }

    @Test
    public void testSelectOnCondition(){
        User u = new User("小", null, null, null);
        List<User> users = usersMapper.selectOnCondition(u);
        users.forEach(System.out::println);
    }
}

輸出結果

Checking to see if class com.example.mapper.TestUsersMapper matches criteria [is assignable to Object]
Checking to see if class com.example.mapper.UsersMapper matches criteria [is assignable to Object]
Opening JDBC Connection
Created connection 2082509879.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7c209437]
==>  Preparing: select id, username, birthday, sex, address from users WHERE username like concat('%', ?,'%')
==> Parameters: 小(String)
<==    Columns: id, username, birthday, sex, address
<==        Row: 2, 小王, 2001-07-12, 1, 蕪湖市
<==        Row: 3, 小張, 1999-02-22, 1, 長沙
<==        Row: 29, 小昕, 2001-03-14, 女, 忻州
<==        Row: 30, 小青, 1996-11-22, 女, 瀋陽市
<==      Total: 4
Users{id=2, userName='小王', birthday=Thu Jul 12 00:00:00 CST 2001, sex='1', address='蕪湖市'}
Users{id=3, userName='小張', birthday=Mon Feb 22 00:00:00 CST 1999, sex='1', address='長沙'}
Users{id=29, userName='小昕', birthday=Wed Mar 14 00:00:00 CST 2001, sex='女', address='忻州'}
Users{id=30, userName='小青', birthday=Fri Nov 22 00:00:00 CST 1996, sex='女', address='瀋陽市'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7c209437]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7c209437]
Returned connection 2082509879 to pool.

Process finished with exit code 0

< set >標籤

作用

  • 對選中的記錄的欄位進行有選擇的更新
    • 注意:但是至少更新一個欄位

原先update標籤的缺點

    <!--
        //根據id更新用戶資訊
        int update(User user);
    -->
    <update id="update" parameterType="user">
        update
            users
        set
            username=#{userName}, birthday=#{birthday}, sex=#{sex}, address=#{address}
        where
            id=#{id}
    </update>
    @Test
    public void testUpdate() throws ParseException {
        //時間字元串
        String dateStr = "2002-8-23";
        int num = usersMapper.update(new User(1, "荷包蛋", date.parse(dateStr), "女", "黑河"));
        sqlSession.commit();
        if (num == 1) {
            System.out.println("更新成功!");
        }else{
            System.out.println("更新失敗!");
        }
    }
  • 缺點分析
    • 上述標籤之所以可以按照預期正常工作,是因為我們更新了該條記錄處id以外的所有欄位
    • 如果只更新一個欄位,例如更新birthday欄位,那麼除了id和birthday欄位,該條記錄的其他欄位值全部被賦值為null
    • 除非把其他不需要更改的欄位賦值為與原先一樣的值,該操作十分沒有必要

使用< set >標籤優化

UsersMapper.java

package com.example.mapper;

import com.example.pojo.User;

/**
 * 數據訪問層的介面,定義對資料庫完成的CRUD的操作
 */
public interface UsersMapper {


    //根據給定條件有選擇性的更新記錄的欄位
    int updateBetter(User user);
}

UsersMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "//mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.UsersMapper">
    
    <!--
        //根據給定條件有選擇性的更新記錄的欄位
        int updateBetter(User user);

        private Integer id;
        private String userName;
        private Date birthday;
        private String sex;
        private String address;
    -->
    
    <update id="updateBetter" parameterType="user">
        update
            users
        <set>
            <if test="userName != null and userName != ''">
                username=#{userName},
            </if>

            <if test="birthday != null">
                birthday=#{birthday},
            </if>

            <if test="sex != null and sex != ''">
                sex=#{sex},
            </if>

            <if test="address != null and address != ''">
                address=#{address},
            </if>
        </set>
        where
            id=#{id}
    </update>

</mapper>
  • < set > 標籤相當於在原先sql語句後面補加了關鍵字,set,此時sql語句相當於:”update XXX set”
==>  Preparing: update users SET address=? where id=?
  • 注意:至少有一個< if >標籤通過測試,set才會被追加
    • 這裡也可以解釋為什麼至少要更新一個欄位:如果測試條件都沒有通過,也就是不更新欄位
    • 此時sql語句被解析為:”update XXX where id = ?”,缺少set條件,sql語句出錯

image

  • < set >標籤里的< if >標籤測試條件通過後,將需要修改的欄位條件拼接到原sql語句後面
    • 注意:新拼接的欄位條件後面要跟上一個逗號,防止有多個條件成立,要添加多個欄位,當只有一個條件成立時:mybatis框架會自動去除該逗號

測試程式碼

package com.example.mapper;

import com.example.pojo.User;
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.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

import java.util.List;

public class TestUsersMapper {

    //SqlSession對象
    SqlSession sqlSession;

    //mybatis動態代理對象
    UsersMapper usersMapper;

    //獲取SqlSession
    @Before
    public void getSqlSession() throws IOException {
        //讀取核心配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //創建SqlSessionFactory對象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //獲取SqlSession
        sqlSession = factory.openSession();
        //獲取mybatis動態代理對象
        usersMapper = sqlSession.getMapper(UsersMapper.class);
    }

    //歸還SqlSession
    @After
    public void closeSession(){
        sqlSession.close();
    }


    @Test
    public void testUpdateBetter(){
        User u = new User();
        u.setId(1);
        u.setAddress("哈爾濱");
        int num = usersMapper.updateBetter(u);
        if(num == 1){
            System.out.println("更新成功!");
        }else{
            System.out.println("更新失敗!");
        }
        sqlSession.commit();
    }
}

輸出結果

Checking to see if class com.example.mapper.TestUsersMapper matches criteria [is assignable to Object]
Checking to see if class com.example.mapper.UsersMapper matches criteria [is assignable to Object]
Opening JDBC Connection
Created connection 544966217.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207b8649]
==>  Preparing: update users SET address=? where id=?
==> Parameters: 哈爾濱(String), 1(Integer)
<==    Updates: 1
更新成功!
Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207b8649]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207b8649]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207b8649]
Returned connection 544966217 to pool.

Process finished with exit code 0
Tags: