Mybatis動態映射,這次終於搞明白了 if+where實現多條件查詢 建議 if+trim+foreach實現多條件查詢 set choose Bind 總結

動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。

使用動態 SQL 並非一件易事,但藉助可用於任何 SQL 映射語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。

如果你之前用過 JSTL 或任何基於類 XML 語言的文本處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間瞭解大量的元素。藉助功能強大的基於 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。

choose(when,otherwise):相當於java中的switch語句,通常與when和otherwise搭配。
set:解決動態更新語句。
trim:靈活的去除多餘的關鍵字。
foreach:迭代一個集合,通常用於in條件。

實際工作中很多時候,這幾個標籤都是組合着使用。

今天的演示使用的是 Spring-Boot+Mybatis 進行演示,對於Spring-Boot整合Mybatis推薦:

if+where實現多條件查詢

創建一種昂數據庫表:

CREATE TABLE `m_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `gender` int(11) DEFAULT NULL COMMENT '0:女生 1:男生',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;

初始化幾條數據:

先來看UserMapper.xml文件:

<mapper namespace="com.tian.mybatis.mapper.UserMapper">
    <resultMap id="User" type="com.tian.mybatis.entity.User">
        <id column="id" property="id"/>
        <result column="name" property="userName"/>
    </resultMap>
    <select id="selectUserById"  resultMap="User">
        select * from m_user
        <where>
            <if test="id != null">
                id = #{id}
            </if>
            <if test="name != null and name != ''">
                and `name` = #{name}
            </if>
        </where>
    </select>
</mapper>

UserMapper.java內容:

import com.tian.mybatis.entity.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper {
    User selectUserById(@Param("name") String userName, @Param("id") Integer id);
}

UserService.java內容:

public interface UserService {
    User selectUserById(String userName, Integer id);
}

UserServiceImpl.java內容:

import com.tian.mybatis.entity.User;
import com.tian.mybatis.mapper.UserMapper;
import com.tian.mybatis.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;
    @Override
    public User selectUserById(String userName, Integer id) {
        return userMapper.selectUserById(userName, id);
    }
}

UserController.java內容:

import com.tian.mybatis.entity.User;
import com.tian.mybatis.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class UserController {
    @Resource
    private UserService userService;
    @GetMapping("/test")
    public User selectUserById() {
        return userService.selectUserById("tian", 1);
    }
}

Application.java內容:

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.tian.mybatis.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

把項目啓動起來,然後進行訪問/test。

http://localhost :9002/test

返回:

上面的這個案例也是我們工作中的代碼案例,我們工作但部分都使用這種方式。

下面的所有演示都是基於上面這些代碼進行調整而成的。

回到正題。

上面的案例中使用了where+if。案例中貌似有個問題:

如果id=null,豈不是多了個and嗎?

我們修改controller中的代碼

@GetMapping("/test")
    public User selectUserById() {
        return userService.selectUserById("tian", null);
    }

爲了能讓sql輸出,我們在配置文件添加了一個配置項:

logging:
  level:
    com:
      tian:
        mybatis:
          mapper: debug

再次執行,輸出和前面是一樣的。控制檯輸出的sql中並沒有and。這就是所謂的動態映射的強大功能之一。

如果我們不使用動態映射標籤,在處理or或者and的時候很有可能出問題。

if元素的test用於判斷表達式是否符合,符合則繼續拼接SQL語句。

建議

建議使用這種動態標籤,不要使用原生態,因爲有時候總有意想不到的判斷導致多了一個and或者or,於是就出現bug,嚴重的可能導致線上某個功能不可能用。

if+trim+foreach實現多條件查詢

對前面的代碼進行調整

<select id="selectUsersByIds" resultMap="User">
        select * from m_user
        <trim prefix="where" prefixOverrides="and | or">
            <if test="idList != null">
                id in
                <foreach collection="idList" item="id" open="(" close=")" separator=",">
                    #{id}
                </foreach>
            </if>
            <if test="gender != null and gender != 0">
                AND gender = #{gender}
            </if>
        </trim>
</select>

UserMapper.java增加

List<User> selectUsersByIds(@Param("idList") List<Integer> idList, @Param("gender") Integer gender);

controller新增方法:

@GetMapping("/users")
    public List<User> selectUsersByIds() {
        List<Integer> idList = new ArrayList<>();
        idList.add(1);
        idList.add(2);
        idList.add(3);
        idList.add(4);
        idList.add(5);
        return userService.selectUsersByIds(idList, null);
    }

項目跑起來,訪問

http://localhost :9002/users

輸出:

sql輸出:

對上面相關屬性進行說明

trim的屬性

  • prefix:前綴: 作用是通過自動識別是否有返回值後,在trim包含的內容上加上前綴,如上述示例的where。
  • suffix:後綴: 作用是在trim包含的內容上加上後綴。
  • prefixOverrides::對於trim包含內容的首部進行指定內容,(如上述示例的 and | or) 的忽略(去餘);
  • suffixOverrides::對於trim包含內容的首位部進行指定內容的忽略。

foreach的屬性

  • item:表示集合中每一個元素進行迭代時的別名。
  • index::指定一個名稱,表示在迭代的過程中,每次迭代到的位置。
  • open:表示該語句以什麼開始(既然是in條件語句,必然是 ' ( ' 開始)
  • separator::表示每次進行迭代的時候以什麼符號作爲分隔符(既然是in條件語句,必然是 ' , ' 分隔)
  • close::表示該語句以什麼結束(既然是in條件語句,必然是 ' ) ' 結束)
  • collection:最關鍵,並且最容易出錯的屬性。需注意,該屬性必須指定,不同情況下,該屬性值是不同的,主要有三種情況:

@Param是Mybatis中的註解,寫的時候別引用錯了,@Param("name"),這裏的name就是我們在Mapper.xml中使用的名稱。

在項目中我見過很多人這麼幹,就是當where語句後面不太確定能有條件出現時,使用

slect ...from...where 1=1

看看你的代碼是否也有?

set

set元素可以用於動態包含需要更新的列,忽略其它不更新的列。

UserMapper.xml新增

<update id="updateAuthorIfNecessary">
        update m_user
        <set>
            <if test="userName != null and userName != ''">
               `name` = #{userName},
            </if>
            <if test="gender != null and gender != 0">
                gender = #{gender},
            </if>
            <if test="age != null and age != 0">
                age = #{age},
            </if>
        </set>
        where id=#{id}
</update>

UserMapper.java新增

int updateAuthorIfNecessary(User user);

controller新增

@PostMapping("/updateUser")
    public String update() {
        User user = new User();
        user.setAge(18);
        user.setUserName("田哥");
        user.setId(1);
        return userService.updateAuthorIfNecessary(user) == 1 ? "success" : "fail";
    }

重啓項目,訪問

http://localhost :9002/updateUser

輸出:success

數據庫表中數據已經修改成功:

SQL輸出

這個例子中,set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。

換一種方式

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

我們把上面的xml中diam進行調整:

<update id="updateAuthorIfNecessary">
        update m_user
        <trim prefix="SET" suffixOverrides=",">
            <if test="userName != null and userName != ''">
                `name` = #{userName},
            </if>
            <if test="gender != null and gender != 0">
                gender = #{gender},
            </if>
            <if test="age != null and age != 0">
                age = #{age},
            </if>
        </trim>
        where id=#{id}
    </update>

controller修改:

@PostMapping("/updateUser")
    public String update() {
        User user = new User();
        user.setAge(19);
        user.setUserName("tian");
        user.setId(1);
        return userService.updateAuthorIfNecessary(user) == 1 ? "success" : "fail";
    }

最後看看SQL輸出:

自動給我們加上了SET關鍵字。並且數據庫修改成功。

choose

相當於Java中的switch語句,通常與when和otherwise搭配。

有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。

下面我們繼續使用上面的案例代碼進行演示。

UserMapper.xml新增方法:

<select id="selectUsersByName" resultMap="User">
        select * from m_user where age = 19
        <choose>
            <when test="userName != null and userName != ''">
                and `name` = #{userName}
            </when>
            <otherwise>
                AND gender = 1
            </otherwise>
        </choose>
</select>

controller新增方法:

@GetMapping("/user/name")
    public  List<User>  selectUsersByName() {
        return userService.selectUsersByName("tian");
}

返回:

SQL輸出:

正確的輸出。如果我們userName沒有是null呢?

輸出和上面正常,在看看SQL輸出:

因爲我們的userName的條件不滿足的情況下,直接執行了gender。

上面 <otherwise> 就類似於相當於我們java語法中switch中的default,當前麪條件不滿足的時候,執行default模塊一樣。

Bind

這種方式使用的不是很多,但是也是有用的。 bind 元素允許你在 OGNL 表達式以外創建一個變量,並將其綁定到當前的上下文。比如:

<select id="selectUserByName" resultType="User">
  <bind name="pattern" value="'%' + userName + '%'" />
  select * from m_user
  WHERE `name` LIKE #{pattern}
</select>

還有就是script,這個就沒有必要在演示了,在工作中基本上用不上。它就是把SQL卸載Java代碼中。比如

@Update({"<script>",
      "update m_user",
      "  <set>",
      "    <if test='username != null'>`name`=#{username},</if>",
      "    <if test='gender != null and gender != 0'>gender=#{gender},</if>",
      "  </set>",
      "where id=#{id}",
      "</script>"})
    void updateUserValues(User user);

總結

文章中部分知識爲了演示,可能有些代碼不是很規範,尤其是sql部分,我們在開發中,針對使用Mybatis開發,我個人總結了幾個點:

  • 表中是否已經有索引,有索引的時候我們的SQL中是否有用上。
  • 返回字段儘量不要寫星號*,建議寫成需要的字段。
  • 關鍵字建議都寫成大寫,更好的區別非關鍵字。
  • 遇到表中字段和數據庫關鍵一樣的時候,記得單引號。
  • 使用@Param註解注意一定要使用Mybatis中的註解。
  • 使用不管是一個參數還是多個參數時,使用註解@Param指定名稱,方便日後需要再次添加字段。
  • 強烈建議使用動態標籤,避免出現多出and或者or關鍵字的SQL錯誤,同時也不用再寫where 1=1

來源:https://www.tuicool.com/articles/EfMNVrf

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