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的多数据源下期分享。

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值