MyBatisPlus基礎進階之自定義Sql語句使用分頁Page以及Wrapper條件構造器

MyBatisPlus進階實戰

官網:MyBatisPlus官網
我們這裏不過多介紹,感興趣的小夥伴可以上官網查看。看完文章,您將收穫以下知識點。

  1. MyBatisPlus的分頁插件。
  2. MyBatisPlus的條件構造器的lambda寫法(Wrapper)。
  3. 如何自定義SQL語句,且使用MyBatisPlus的條件構造器。
  4. 外連接的自定義SQL語句,使用MyBatisPlus的條件構造器以及使用Page分頁插件,使用ResultMap映射到其他實體類上。
  5. MybatisPlus的ResultMap映射NULL無法映射到字段問題。
  6. MyBatisPlus的修改語句以及新增語句NULL值無法更新問題。
  7. 在Wrapper構造器中使用原生SQL語句作爲條件。
  8. 數據庫新增字段查詢語句無法映射。

MyBatisPlus的分頁插件。
  日常開發中,分頁就像是家常便飯一般。但是如果自己手寫分頁信息則需要寫兩套SQL,一套是關於數據查詢,一套則是用於統計數據條數,然後後續根據LIMIT進行分頁。有幸MyBatisPlus爲我們提供了很方便的分頁插件Page。雖然你他也是寫了兩套SQL但是好在不需要我們關心分頁邏輯。
代碼示例:

  • 首先我們需要將分頁插件註冊到Spring容器中,這樣後續我們纔可以使用Page進行分頁
    /**
     * mybatisplus分頁插件
     *
     * @return 分頁插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 設置請求的頁面大於最大頁後操作, true調回到首頁,false 繼續請求  默認false
        // paginationInterceptor.setOverflow(false);
        // 設置最大單頁限制數量,默認 500 條,-1 不受限制
        paginationInterceptor.setLimit(500);
        // 開啓 count 的 join 優化,只針對部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
  • 現在我們就可以盡情使用我們的分頁插件了,我們主要使用的是IPage接口,這個接口是一個泛型的,這也爲我們後面的自定義SQL返回值打下了基礎。

  場景一:現在有一個實體類,WorkOrder->派工單類、ProductionLine->生產線類、Equipment ->設備類。其中派工單會綁定生產線,而生產線又會綁定計數設備以及顯示設備(都屬於設備,不同類型而已)。現在我需要查詢派工單,且展示生產線信息,以及設備狀態以確保是否可以開始生產,且需要進行分頁。

  這個場景不難,也很簡單,就是根據左外鏈接即可。但是我們演示的是使用MyBatisPlus來簡化快速實現該功能。

  首先我們在Service中定義一個接口:findWorkOrderByCondition 參數是WorkOrderBo,WorkOrderBo也肯定是會包含WorkOrder的字段的,且會多很多前端查詢條件的字段,想具體瞭解Bo,Vo等可以百度一下POJO命名規則以及作用。
代碼示例:


	//首先需要定義一個接口,他的返回值是IPage類型的,其作用就是根據指定的條件進行查詢派工單信息
	//因爲這是給前端的,所以我們的返回值類型是WorkOrderVo,但是我們數據庫實體類卻是WorkOrder
	//其中WorkOrderVo中的字段是包含了WorkOrder的字段的。
    IPage<WorkOrderVo> findWorkOrderByCondition(WorkOrderBo workOrderBo);

  findWorkOrderByCondition這個接口就是用來滿足我們上面功能的Service接口。我們先不關心具體實現,我們的重心是在Dao層。

  • Dao(Mapper)層也定義一個接口:
    /**
     * 根據條件查詢派工單信息
     * @param page 分頁信息
     * @param wrapper 條件映射
     * @return 派工單信息
     */
    IPage<WorkOrderVo> findWorkOrderBy(@Param("page") IPage<WorkOrderVo> page,
                                                @Param(Constants.WRAPPER) Wrapper<WorkOrder> wrapper);

  這個接口就是Dao層用於實現該功能的接口,我們需要關注三個東西

  1. 返回值IPage<·WorkOrderVo>:表示我需要返回一個是WorkOrderVo的分頁的數據
  2. @Param(“page”)IPage<·WorkOrderVo> page:表示需要一個Page分頁對象
  3. @Param(Constants.WRAPPER) Wrapper<·WorkOrder> wrapper):MyBatisPlus中的條件構造器
    然後我們在看一下我們的XML配置文件,這裏需要注入泛型類型是WorkOrder而不是WorkOrderVo對象,因爲Mrapper對象只支持數據庫實體類對象,因爲他會使用實體對象的字段作爲查詢數據庫的條件。
 <!--獲取派工單信息-->
    <select id="findWorkOrderBy" resultMap="findWorkOrderByCondition">
        select wo.id as wid,wo.vbillcode,DATE_FORMAT(wo.dbilldate,'%Y-%m-%d %H:%i:%s') as dbilldatestring,
        wo.vmobillcode,wlname,wo.materialspec,wo.teamname,wo.ssscxname,wo.nbdispatchassnum,wo.state
        from work_order as wo
        LEFT JOIN production_line as pl on pl.`name` = wo.ssscxname
        ${ew.customSqlSegment}
    </select>

    <!--獲取派工單信息映射-->
    <resultMap id="findWorkOrderByCondition" type="org.example.core.pojo.vo.WorkOrderVo">
        <result column="wid" property="id"/>
        <result column="vbillcode" property="vbillcode"/>
        <result column="dbilldatestring" property="dbilldatestring"/>
        <result column="vmobillcode" property="vmobillcode"/>
        <result column="wlname" property="wlname"/>
        <result column="materialspec" property="materialspec"/>
        <result column="teamname" property="teamname"/>
        <result column="ssscxname" property="ssscxname"/>
        <result column="nbdispatchassnum" property="nbdispatchassnum"/>
        <result column="state" property="state"/>
    </resultMap>

  在上面是Sql語句我們可以很清晰的看到,我們只有Select語句,但是沒有Where語句,所有的Where條件都被我們的 ${ew.customSqlSegment} 替換掉了,到時候我們只需要按照MyBatisplus的條件構造器的方式對 findWorkOrderBy 方法入參則會自動映射Wher條件

  現在Dao層以及實現XML都已經就緒了,Dao層的接口 findWorkOrderBy需要兩個參數

  1. 分頁對象,進行分頁
  2. 條件構造器,爲我們自定義的sql提供查詢Where條件

  還記得我們的Service的接口 IPage<·WorkOrderVo> findWorkOrderByCondition(WorkOrderBo workOrderBo); 現在我們可以看看他的實現,他是怎麼去調用Dao層接口,且怎麼爲他入參的。

Impl實現:

  /**
     * 根據條件查詢派工單信息
     *
     * @param workOrderBo 請求參數
     * @return 派工單信息
     */
    @Override
    public IPage<WorkOrderVo> findWorkOrderByCondition(WorkOrderBo workOrderBo) {
        String state = workOrderBo.getState();
        if (!checkWorkOrderState(state))
            throw new IllegalArgumentException("請傳遞正確的類型");

        LambdaQueryWrapper<WorkOrder> condition = Wrappers.<WorkOrder>lambdaQuery().apply("wo.state = {0}", state);
        //存在派工單日期
        String teamid = workOrderBo.getTeamid();
        if (!StringUtils.isBlank(teamid))
            condition.eq(WorkOrder::getTeamid, teamid);
        //如果指定了時間
        String currTime = workOrderBo.getCurrTime();
        if (!StringUtils.isBlank(currTime))
            condition.apply("DATE_FORMAT(pull_date,'%Y-%m-%d') = {0}", currTime);
        //所屬生產線
        String ssscxname = workOrderBo.getSsscxname();
        if (!StringUtils.isBlank(ssscxname))
            condition.like(WorkOrder::getSsscxname, ssscxname);
        condition.orderByDesc(WorkOrder::getPullDate);
        //分頁信息判斷
        Integer pageNum = workOrderBo.getPageNum();
        Integer pageSize = workOrderBo.getPageSize();
        if (Objects.isNull(pageNum) || Objects.isNull(pageSize) || pageNum <= 0 || pageSize <= 0)
            throw new IllegalArgumentException("請傳遞正確的頁碼信息");
        Page<WorkOrderVo> workOrderPage = new Page<>(pageNum, pageSize);
        //如果沒有數據 則返回
        IPage<WorkOrderVo> page = workOrderMapper.findWorkOrderBy(workOrderPage, condition);
        List<WorkOrderVo> records = page.getRecords();
        if (records.size() <= 0)
            return page;
        //工單信息
        List<WorkOrderVo> collect = records.stream().map(workOrder -> {
            WorkOrderVo workOrderVo = new WorkOrderVo();
            BeanUtils.copyProperties(workOrder, workOrderVo);
            ProductionLineVo productionLineVo = productionLineService.findBy(workOrder.getSsscxname());
            workOrderVo.setProductionLineVo(productionLineVo);
            return workOrderVo;
        }).collect(Collectors.toList());
        page.setRecords(collect);
        return page;
    }

有關更多的Wrapper構造器的知識以及語法請查看官網:Wrapper條件構造器
  因爲Wrapper生成的構造器是一個符合鏈式編程的對象,這和lombok種的@builder一個道理,也就是需要設置下一個條件的時候不需要set調用,而是直接 “點+方法名” 即可
含義解釋:
1: LambdaQueryWrapper condition = Wrappers.<·WorkOrder>lambdaQuery().apply(“wo.state = {0}”, state);
Wrappers.<·WorkOrder>lambdaQuery():聲明一個Lambda形式的Wrapper構造器,有了Wrapper構造器之後就可以使用構造器語法生產Where條件語句,lambdaQuery():是泛型的,所以需要指定一個實體類型,這個實體類型一定要是我們的對應數據庫的實體對象,否則會報錯。
.apply(“wo.state = {0}”, state):使用apply語法,其作用在Wrapper中使用自定義的原生SQL語句,我這裏的含義是啥,因爲我們是使用外連接查詢,但是三個表中很多都有state字段,如果直接使用eq(WorkOrder::getState,state)則會報錯,爲啥呢,因爲會生成一個where條件爲:state = state,但是這裏的state是那個表的呢?所以我們使用到apply語法,明確指定生成一個條件 wo.state = state,wo是WorkOrder的別名。這樣Sql語句識別就明確知道我們用的WorkOrder表的state。
2:condition.eq(WorkOrder::getTeamid, teamid);:eq方法,就是等於,這裏的例子會生成一個where條件 ,因爲在中間部位(前面有一個**.apply(“wo.state = {0}”, state)**)所以會生成一個 and teamid = teamid 的Sql語句,至於動態的where條件是否會添加and關鍵字不需要我們關心,如果不顯示的指定是 or() 或者 and() 總是會默認添加 and 如果後面的條件是or 那麼請調用 or()方法。
3:condition.apply(“DATE_FORMAT(pull_date,’%Y-%m-%d’) = {0}”, currTime):是的,又是自定義where條件裏面的sql語句,很遺憾Mybatisplus並不支持對時間條件查詢進行格式化操作,所以有關時間格式化的查詢語句還是得自定義Sql,如上面的例子就是將時間戳格式化爲 YYYY-MM-dd的格式進行比對。
4:condition.like(WorkOrder::getSsscxname, ssscxname):like,模糊查詢,該方法的功能就是模糊全匹配,當然也支持左匹配(likeLeft())以及右匹配(likeRight)或者notLike,該方法就會生成一個 sssxname like ‘%ssscxname%’ 的Sql語句。
5:Page<·WorkOrderVo> workOrderPage = new Page<>(pageNum, pageSize):創建一個需要分頁的對象,是一個泛型,他的類型可以不是數據庫實體類。而是你需要返回的類型。注意:起始值是1開始
6:IPage<·WorkOrderVo> page = workOrderMapper.findWorkOrderBy(workOrderPage, condition):這就是調用我們的Dao層的接口(Mapper),我們也滿足了接口的參數,一個Page分頁對象,一個Wrapper(我們這裏的condition)條件構造器對象。這個是否MyBatisplus就會去按照我們的自定義Sql語句以及解析Wrapper裏面的動態Where條件,最後返回帶分頁的數據列表 IPage<·WorkOrderVo> page

  看完上面的可能很繞,但是隻要花點時間理解,相信可以很快玩轉MyBatisPlus,想了解更多的的Wrapper的方法還是請到官網學習,本文當作例子參考,請結合官網學習:Wrapper條件構造器

另外在分享一點小經驗:

  1: 如果你新增的字段,或者使用條件構造器進行查詢,打印的sql語句複製到數據庫可以運行,但是就是在運行中報錯,告訴XXXX 類型無法轉換爲 XXX類型 那麼你就應該檢查你的構造函數是否寫好了(無參,以及全參都應該存在)這裏推薦使用lombok的註解:@NoArgsConstructor(無參構造函數)@AllArgsConstructor(全參構造函數)

報錯如下:

org.springframework.dao.DataIntegrityViolationException: Error attempting to get column ‘successed’ from result set. Cause: java.sql.SQLDataException: Unsupported conversion from LONG to java.sql.Timestamp
; Unsupported conversion from LONG to java.sql.Timestamp; nested exception is java.sql.SQLDataException: Unsupported conversion from LONG to java.sql.Timestamp

  這是充滿欺詐的報錯,你如果看第二處加粗的地方,會認爲是類型錯誤,實際不然,我們看看第一段報錯:Error attempting to get column ‘successed’ from result set. 他說嘗試在結果集獲取列XXX發生錯誤。只要看到這個請先檢查構造函數是否正確添加。

  2:當我們在查詢的時候,Mybatisplus是默認不會映射字段爲NULL的字段,也就是說如果字段沒有值那麼就不會返回該值的信息。怎麼解決呢?解決辦法是可以在配置文件加上以下配置:

mybatis-plus:
  configuration:
    # 在查詢語句的是否,對Map或者是entity進行映射賦值的時候null也進行映射。默認false,不進行應誰
    call-setters-on-nulls: true

  3:MyBatisPlus在作Insert或者Update的時候,如果值是NULL則不會進行對該字段進行賦值,比如:在Update的時候我需要將xxx字段改爲NULL,則需要寫 set xxx = null,但是Mybatis不會進行作這個set操作,因爲在set之前會建測值,如果是不合法的則不會進行set(NULL就是不合法的)。那麼怎麼解決呢,有兩種辦法:

//第一種,在可能爲NULL的字段上加上以下註解

	@TableField(updateStrategy = FieldStrategy.IGNORED,insertStrategy = FieldStrategy.IGNORED)
    private String productionlinename;


//第二種 寫一個全局配置,因爲一個一個寫太麻煩了。但是粒度細,配置了全局配置則全局生效
mybatis-plus:
  # 設置更新或者修改的時候的策略,不進行校驗,否則如果是null則不會進行更新或者插入,當然在@TableField註解進行指定單個字段
  global-config:
    db-config:
      insert-strategy: ignored
      update-strategy: ignored

  到了這裏就差不多了,這裏附上我的MyBatisPlus的配置文件

mybatis-plus:
  # 設置更新或者修改的時候的策略,不進行校驗,否則如果是null則不會進行更新或者插入,當然在@TableField註解進行指定單個字段
  global-config:
    db-config:
      insert-strategy: ignored
      update-strategy: ignored
  configuration:
    # 在查詢語句的是否,對Map或者是entity進行映射賦值的時候null也進行映射。默認false,不進行應誰
    call-setters-on-nulls: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # mapper文件地址
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: org.example.core.entity

  另外有關MyBatisPlus的多數據源下期分享。

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