1. 程式人生 > 程式設計 >mybatis-plus QueryWrapper自定義查詢條件的實現

mybatis-plus QueryWrapper自定義查詢條件的實現

mybatis-plus框架功能很強大,把很多功能都集成了,比如自動生成程式碼結構,mybatis crud封裝,分頁,動態資料來源等等,附上官網連結https://mp.baomidou.com/,github上有程式碼例子,國內小夥伴推薦碼雲https://gitee.com/baomidou/mybatis-plus。
但是,其中還是有些小坑,文件也沒有涉及的很全面,碰到問題,百度或者發issue,能力強的還是直接看原始碼好,一切答案都在原始碼中。

版本推薦用3.1.0,3.1.1及以上版本有bug,訪問mapper介面的時候,會把資料庫date型別轉換為localDateTime,報錯java.sql.SQLFeatureNotSupportedException

解決方案可以參考 https://www.jb51.net/article/193995.htm

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.1.0</version>
</dependency>

mybatis-plus裡有個類QueryWrapper,封裝sql物件,包括where條件,order by排序,select哪些欄位等等。該類的具體用法,網上教程很多。

這裡有個需求,通過前端提交查詢條件,後臺動態拼接成where的sql語句,用於查詢。常規做法是前端提交一堆查詢引數,controller層用一個物件接收,然後在mybatis的xml裡對該物件裡的各種屬性做判斷

<select id="test">
 select * from test
 <where>
 <if test=" name != null and name != '' ">
  and name=#{name}
 </if>
 ...
 </where>
</select>

這有個問題是具體欄位連線型別就有很多,like,=,>,<等等。當然要實現功能有很多種方式,mybatis-plus的QueryWrapper很強大,可以通過物件的方式進行查詢操作,但是不同的頁面都自己管自己,效率低下,會存在大量重複程式碼。所以我就想自己封裝一套,從前端的查詢條件傳固定格式的引數,到後臺進行轉換,自動拼接成對應的where sql語句,再傳到mybatis xml裡進行動態查詢。這樣所有頁面就可以統一,便於操作。下面進入正題:

前端

前端用的技術是html+jquery,jquery操作dom做各種操作。html就僅僅是樣式展現,不涉及任何的邏輯程式碼,沒有使用vue之類的mvvm框架,也沒有使用thymeleaf之類的模板引擎,其實這些都會在html嵌入汙染程式碼,導致美工修改頁面樣式的時候一臉矇蔽。html就是純的html+css,通過jquery來完成剩餘的工作。

index.html

<form id="myform">
 <input name="name"/>
 <input name="age"/>
 <input name="startdate"/>
 <input name="enddate"/>
</form>

jquery發起post請求,拼接的引數如下:

var searchParam = [
 {column: "COLUMN_NAME",type: "like",value: "tim"},{column: "COLUMN_AGE",type: "eq",value: "22"},{column: "COLUMN_DATE",type: "ge",value: "2019-08-16 00:00:00"},type: "le",value: "2019-08-16 23:59:59"}
];

其中column值 為資料表的欄位名;type值為sql欄位拼接的方式,規則可自己定製;value就是欄位值了;目標拼接成的sql語句如下:

COLUMN_NAME like '%tim%' and COLUMN_AGE=22 and COLUMN_DATE>='2019-08-16 00:00:00' and COLUMN_DATE<='2019-08-16 23:59:59'

jquery發起post請求:

$.ajax({
 url: "list",type: "post",data: {pageNum:1,pageSize:20,condition:JSON.stringify(searchParam),...(根據需要自己加請求引數)}
 success: function(result){...}
});

說明:請求引數condition為什麼要傳json字串後續有介紹。

後端

Controller

controller接收前端發過來的引數,碰到一個問題,在有多個請求引數的情況下,如何接收 集合物件 請求引數?使用了很多方法都不行,後續有空再研究下,目前使用的方法簡單粗暴,就上傳json字串,後端轉換成物件。

Controller:

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@RequestMapping(value = "/list",method = RequestMethod.POST)
public Object getTestList(@RequestParam(name = "pageNum",required = false,defaultValue = "1") int pageNum,@RequestParam(name = "pageSize",defaultValue = "15") int pageSize,@RequestParam(name = "condition",required = false) String conditionJson) {
 QueryWrapper queryWrapper = SearchUtil.parseWhereSql(conditionJson);
   queryWrapper.orderByDesc("CREATE_DATE");
   return service.getPageTestList(queryWrapper,pageNum,pageSize);
}

SearchUtil:

public static QueryWrapper parseWhereSql(String conditionJson){
  QueryWrapper queryWrapper = new QueryWrapper();
  if(StrUtil.isNotEmpty(conditionJson)){
    List<ConditionVo> conditionList = JSON.parseArray(conditionJson,ConditionVo.class);
    if(CollUtil.isNotEmpty(conditionList)){
      for(ConditionVo conditionVo : conditionList){
        switch (conditionVo.getType()){
          case "eq": queryWrapper.eq(conditionVo.getColumn(),conditionVo.getValue());break;
          case "ne": queryWrapper.ne(conditionVo.getColumn(),conditionVo.getValue());break;
          case "like": queryWrapper.like(conditionVo.getColumn(),conditionVo.getValue());break;
          case "leftlike": queryWrapper.likeLeft(conditionVo.getColumn(),conditionVo.getValue());break;
          case "rightlike": queryWrapper.likeRight(conditionVo.getColumn(),conditionVo.getValue());break;
          case "notlike": queryWrapper.notLike(conditionVo.getColumn(),conditionVo.getValue());break;
          case "gt": queryWrapper.gt(conditionVo.getColumn(),conditionVo.getValue());break;
          case "lt": queryWrapper.lt(conditionVo.getColumn(),conditionVo.getValue());break;
          case "ge": queryWrapper.ge(conditionVo.getColumn(),conditionVo.getValue());break;
          case "le": queryWrapper.le(conditionVo.getColumn(),conditionVo.getValue());break;
        }
      }
    }
  }
  return queryWrapper;
}

該類是重點,根據type不同的值進行組合,queryWrapper包含了很多拼接方法,可以看文件。這裡只寫了一些常用的拼接方法。

ConditionVo:

@Data
public class ConditionVo implements Serializable {
  private static final long serialVersionUID = -5099378457111419832L;
  /**
   * 資料庫欄位名
   */
  private String column;
  /**
   * 欄位值
   */
  private String value;
  /**
   * 連線型別,如llike,equals,gt,ge,lt,le
   */
  private String type;
}

拿到queryWrapper物件就是重點了,之後就是sql操作了。

Service

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@Override
public IPage<ListVo> getPageEntityList(QueryWrapper queryWrapper,int pageNum,int pageSize) {
   Page<ListVo> page = new Page<>(pageNum,pageSize);
   IPage<ListVo> list = page(page,queryWrapper);
   return list;
 }
@Override
public IPage<ListVo> getPageTestList(QueryWrapper queryWrapper,int pageSize) {
  Page<ListVo> page = new Page<>(pageNum,pageSize);
  IPage<ListVo> list = mapper.getPageTestList(page,queryWrapper);
  return list;
}

上面的第一個方法getPageEntityList使用的是mybatis-plus自帶的page(page,queryWrapper)方法,具體使用方法可以檢視官方文件。使用該方法就不需要自己寫sql語句了。本篇文章重點是下面那個方法getPageTestList。
mybatis-plus自帶了強大的翻頁功能,只需往mapper方法裡傳入一個Page類,該類實現了IPage介面。
這裡有個坑,通過看原始碼發現,mapper方法的引數有順序要求:page物件一定要放在第一個引數,否則翻頁查詢會報錯。原始碼在com.baomidou.mybatisplus.core.override.MybatisMapperMethod中public Object execute(SqlSession sqlSession,Object[] args)方法的case SELECT判斷裡,因反編譯後的行數可能不同,所以貼上具體是哪個方法,我這邊反編譯後在68行。
ListVo是自己的業務物件。

Mapper

import com.baomidou.mybatisplus.core.toolkit.Constants;
IPage<ListVo> getPageTestList(Page<ListVo> page,@Param(Constants.WRAPPER) Wrapper query);

大家可以進Constans這個介面看下原始碼都有哪些值,後續xml要用到。

XML

<select id="getPageTestList" resultType="xx.xx.xx.ListVo">
  select * from test
  <if test="ew.emptyOfWhere == false">
    ${ew.customSqlSegment}
  </if>
</select>

這裡先強調一點,${ew.customSqlSegment}是用美元符號$,而不是#。

這裡的ew是啥?其實就是mapper方法裡的@Param(Constants.WRAPPER) Wrapper query物件,Constants.WRAPPER的值就是ew。

首先判斷ew.emptyOfWhere是否存在where條件,有的話再拼接上去。ew裡還有個屬性nonEmptyOfWhere,看單詞應該跟emptyOfWhere的值相反,但是在xml中使用卻提示不存在,不知道為什麼,又是一個地雷?

ew.customSqlSegment又是啥,該值是WHERE + sql語句,還有個ew.sqlSegment是不包括WHERE字串。大家可以在service層輸出queryWrapper裡面的相關方法:

log.info(queryWrapper.isEmptyOfWhere()+"");
log.info(queryWrapper.getCustomSqlSegment());
log.info(queryWrapper.getSqlSegment());
log.info(queryWrapper.getParamNameValuePairs().toString());

輸出如下:

getCustomSqlSegment()
WHERE COLUMN_NAME LIKE #{ew.paramNameValuePairs.MPGENVAL1}
getSqlSegment()
COLUMN_NAME LIKE #{ew.paramNameValuePairs.MPGENVAL1}
getParamNameValuePairs()
{MPGENVAL1=%tim%}

看到這,mybatis框架用的熟練的小夥伴們應該就懂了吧,這樣就可以避免sql注入的問題。
這裡又有 一個小坑,就是order by排序。傳入了page引數,mybatis-plus底層就會幫你翻頁查詢,會查詢總數量。通過輸出的sql日誌可以發現,其實框架是在你的sql基礎上外面再套一層select count(1) from。這裡會有個問題,本人用的資料庫是sqlserver,如果在count查詢語句裡用了order by就會出錯,解決方法是呼叫queryWrapper物件中的排序方法,如:queryWrapper.orderByDesc("CREATE_DATE"),xml中就不要用order by。所以我在controller層用了這個方法,這樣mybatis-plus底層會合理地進行查詢。

總結

通過上面這種封裝方式,就不需要在xml裡面做一大堆的where條件if判斷來拼接sql。
mybatis-plus框架功能很強大,且還在維護中,有空可以仔細閱讀下文件、官方例子,能力強的可以直接看原始碼,一切答案都在原始碼中。

到此這篇關於mybatis-plus QueryWrapper自定義查詢條件的實現的文章就介紹到這了,更多相關mybatis-plus QueryWrapper查詢條件內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!