MyBatisPlus基础进阶之⾃定义Sql语句使⽤分页Page以及Wrapper条件构造器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.amname,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="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 = State();
if(!checkWorkOrderState(state))
throw new IllegalArgumentException("请传递正确的类型");
LambdaQueryWrapper<WorkOrder> condition = Wrappers.<WorkOrder>lambdaQuery().apply("wo.state = {0}", state);
//存在派⼯单⽇期
String teamid = Teamid();
if(!StringUtils.isBlank(teamid))
condition.eq(WorkOrder::getTeamid, teamid);
//如果指定了时间
String currTime = CurrTime();
if(!StringUtils.isBlank(currTime))
condition.apply("DATE_FORMAT(pull_date,'%Y-%m-%d') = {0}", currTime);
//所属⽣产线
String ssscxname = Ssscxname();
if(!StringUtils.isBlank(ssscxname))
condition.like(WorkOrder::getSsscxname, ssscxname);
//分页信息判断
Integer pageNum = PageNum();
Integer pageSize = PageSize();
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 = Records();
if(records.size()<=0)
return page;
//⼯单信息
List<WorkOrderVo> collect = records.stream().map(workOrder ->{
WorkOrderVo workOrderVo =new WorkOrderVo();
ProductionLineVo productionLineVo = productionLineService.Ssscxname());
workOrderVo.setProductionLineVo(productionLineVo);
return workOrderVo;
}).List());
page.setRecords(collect);
return page;
}
有关更多的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的⽅法还是请到官⽹学习,本⽂当作例⼦参考,请结合官⽹学习:
另外在分享⼀点⼩经验:
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: ntity
另外有关MyBatisPlus的多数据源下期分享。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论