关于Mybatis-Plus Wrapper是否应该出现在Servcie类中
作者:罗小爬EX 发布时间:2023-11-28 22:04:56
一、问题
最近在做代码重构,代码工程采用了Controller/Service/Dao分层架构,Dao层使用了Mybatis-Plus框架。
在查看Service层时发现如下代码:
@Service
public class SampleServiceImpl implements SampleService {
@Resource
private SampleMapper sampleMapper;
@Override
public SampleTo findById(Long id) {
Sample sample = this.sampleMapper.selectOne(Wrappers.<Sample>lambdaQuery()
//仅查询指定的column
.select(Sample::getId, Sample::getName, Sample::getDate)
//查询条件 id = #{id}
.eq(Sample::getId, id)
);
return (SampleTo) BaseAssembler.populate(sample, new SampleTo());
}
@Override
public PageInfo<SampleTo> findListByDto(SampleQueryDto sampleQueryDto) {
//开启分页
PageHelperUtil.startPage(sampleQueryDto);
//查询分页列表
List<Sample> sampleList = this.sampleMapper.selectList(Wrappers.<Sample>lambdaQuery()
//查询条件 id = #{id}
.eq(Objects.nonNull(sampleQueryDto.getId()), Sample::getId, sampleQueryDto.getId())
//查询条件 name like concat('%', #{name}, '%')
.like(StringUtils.hasText(sampleQueryDto.getName()), Sample::getName, sampleQueryDto.getName())
//查询条件 type = #{type}
.eq(StringUtils.hasText(sampleQueryDto.getType()), Sample::getType, sampleQueryDto.getType())
//查询条件 date >= #{startDate}
.ge(Objects.nonNull(sampleQueryDto.getStartDate()), Sample::getDate, sampleQueryDto.getStartDate())
//查询条件 date <= #{endDate}
.le(Objects.nonNull(sampleQueryDto.getEndDate()), Sample::getDate, sampleQueryDto.getEndDate()));
//转换分页结果
return PageHelperUtil.convertPageInfo(sampleList, SampleTo.class);
}
@Override
public List<SampleTo> findListByCondition(String name, String type) {
List<Sample> sampleList = this.sampleMapper.selectList(Wrappers.<Sample>lambdaQuery()
//查询条件 name like concat('%', #{name}, '%')
.like(StringUtils.hasText(name), Sample::getName, name)
//查询条件 type = #{type}
.eq(StringUtils.hasText(type), Sample::getType, type)
);
return BaseAssembler.populateList(sampleList, SampleTo.class);
}
@Override
public SampleDetailTo findDetailById(Long id) {
return this.sampleMapper.findDetail(id);
}
@Override
public Integer add(SampleAddDto sampleAddDto) {
Sample sample = new Sample();
BaseAssembler.populate(sampleAddDto, sample);
return this.sampleMapper.insert(sample);
}
}
dao层代码:
public interface SampleMapper extends BaseMapper<Sample> {
//SQL脚本通过XML进行定义
SampleDetailTo findDetail(@Param("id") Long id);
}
如上Service代码中直接使用Mybatis-Plus框架提供的Wrapper构造器,写的时候是挺爽,不用再单独为SampleMapper接口写XML脚本了,直接在Service类中都完成了,但是我不推荐这种写法。
分层架构的本意是通过分层来降低、隔离各层次的复杂度,
各层间仅通过接口进行通信,层间仅依赖抽象接口,不依赖具体实现,
只要保证接口不变可以自由切换每层的实现。
上述Service类中直接引用了Dao层实现框架Mybatis-Plus中的Wrappers类,尽管Servcie层依赖了Dao层的Mapper接口,但是Mapper接口中的参数Wrapper却是Dao层具体实现Mybatis-Plus所独有的,试想我们现在Dao层用的Mybatis-Plus实现,后续如果想将Dao层实现切换为Spring JPA,那Mybatis-Plus中Wrapper是不都要替换,那Servcie层中的相关Wrapper引用也都要进行替换,我们仅是想改变Dao实现,却不得不把Servcie层也进行修改。同时Service层本该是写业务逻辑代码的地方,但是却耦合进了大量的Wrapper构造逻辑,代码可读性差,难以捕捉到核心业务逻辑。
二、优化建议
那是不是Mybatis-Plus中的Wrapper就不能用了呢?我的答案是:能用,只是方式没用对。
Wrapper绝对是个好东西,方便我们构造Sql,也可以将我们从繁琐的XML脚本中解救出来,但是不能跨越层间界限。
优化建议如下:
移除Servcie中的Wrapper使用
Java8+之后接口提供了默认方法的支持,可通过给Dao层Mapper接口添加default方法使用Wrapper
单表相关的操作 - 通过Dao层Mapper接口的default方法直接使用Wrapper进行实现,提高编码效率
多表关联的复杂操作 - 通过Dao层Mapper接口和XML脚本的方式实现
优化后的Service层代码如下:
@Service
public class SampleServiceImpl implements SampleService {
@Resource
private SampleMapper sampleMapper;
@Override
public SampleTo findById(Long id) {
Sample sample = this.sampleMapper.findInfoById(id);
return (SampleTo) BaseAssembler.populate(sample, new SampleTo());
}
@Override
public SampleDetailTo findDetailById(Long id) {
return this.sampleMapper.findDetail(id);
}
@Override
public PageInfo<SampleTo> findListByDto(SampleQueryDto sampleQueryDto) {
//开启分页
PageHelperUtil.startPage(sampleQueryDto);
//查询分页列表
List<Sample> sampleList = this.sampleMapper.findList(sampleQueryDto);
//转换分页结果
return PageHelperUtil.convertPageInfo(sampleList, SampleTo.class);
}
@Override
public List<SampleTo> findListByCondition(String name, String type) {
List<Sample> sampleList = this.sampleMapper.findListByNameAndType(name, type);
return BaseAssembler.populateList(sampleList, SampleTo.class);
}
@Override
public Integer add(SampleAddDto sampleAddDto) {
Sample sample = new Sample();
BaseAssembler.populate(sampleAddDto, sample);
return this.sampleMapper.insert(sample);
}
}
优化后的Dao层代码:
public interface SampleMapper extends BaseMapper<Sample> {
default Sample findInfoById(Long id) {
return this.selectOne(Wrappers.<Sample>lambdaQuery()
//仅查询指定的column
.select(Sample::getId, Sample::getName, Sample::getDate)
//查询条件 id = #{id}
.eq(Sample::getId, id)
);
}
default List<Sample> findList(SampleQueryDto sampleQueryDto) {
return this.selectList(Wrappers.<Sample>lambdaQuery()
//查询条件 id = #{id}
.eq(Objects.nonNull(sampleQueryDto.getId()), Sample::getId, sampleQueryDto.getId())
//查询条件 name like concat('%', #{name}, '%')
.like(StringUtils.hasText(sampleQueryDto.getName()), Sample::getName, sampleQueryDto.getName())
//查询条件 type = #{type}
.eq(StringUtils.hasText(sampleQueryDto.getType()), Sample::getType, sampleQueryDto.getType())
//查询条件 date >= #{startDate}
.ge(Objects.nonNull(sampleQueryDto.getStartDate()), Sample::getDate, sampleQueryDto.getStartDate())
//查询条件 date <= #{endDate}
.le(Objects.nonNull(sampleQueryDto.getEndDate()), Sample::getDate, sampleQueryDto.getEndDate())
);
}
default List<Sample> findListByNameAndType(String name, String type) {
return this.selectList(Wrappers.<Sample>lambdaQuery()
//查询条件 name like concat('%', #{name}, '%')
.like(StringUtils.hasText(name), Sample::getName, name)
//查询条件 type = #{type}
.eq(StringUtils.hasText(type), Sample::getType, type)
);
}
//SQL脚本通过XML进行定义)
SampleDetailTo findDetail(@Param("id") Long id);
}
优化后的Servcie层完全移除了对Wrapper的依赖,将Servcie层和Dao层实现进行解耦,同时Dao层通过Java8+接口的默认方法同时支持Wrapper和XML的使用,整合编码和XML脚本的各自优势。
三、Repository模式
经过优化过后,Service层代码确实清爽了许多,移除了Mybatis-Plus的Wrapper构造逻辑,使得Service层可以更专注于业务逻辑的实现。但是细心的小伙伴还是会发现Servcie层仍旧依赖了Mybatis的分页插件PageHelper中的PageHelper类、PageInfo类,PageHelper插件也是技术绑定的(强绑定到Mybatis),既然我们们之前强调了Servcie层与Dao层间的界限,如此在Servcie层使用PageHelper也是越界了,例如后续如果切换Spring JPA,那PageHelper在Servcie层的相关的引用也都需要调整。
真正做到业务和技术解耦,可以参考DDD中的Repository(仓库、资源库)模式:
单独定义通用的分页查询参数DTO、分页查询结果DTO(与具体技术解耦)
定义Repository接口,仅依赖聚合、通用分页查询参数DTO、分页查询结果DTO
定义Repository接口的实现类,具体实现可依赖如Mybatis、JPA等Dao框架,在Repository的具体实现类中完成转换:
领域模型(聚合)<==> 数据实体
通用分页查询参数DTO、结果DTO <==> Dao框架的分页参数、结果(如PageHelper、IPage等)
DDD映射到代码层面,改动还是比较大的,所以在这次重构代码的过程中并没有真正采用DDD Repository模式,
而是仅从Servcie中移除Mybatis-Plus Wrapper便结束了,虽没有完全将Service层与Dao层实现(PageHelper)解耦,
但在Service层移除Wrapper构造逻辑后,使得Service层代码更清爽,可读性更好了,重构过程的代码改动量也在可接收的范围内。
来源:https://blog.csdn.net/luo15242208310/article/details/130556852


猜你喜欢
- 前言TensorFlow是Google开源的一款人工智能学习系统。为什么叫这个名字呢?Tensor的意思是张量,代表N维数组;Flow的意思
- 示例:导入相关数据(Excel文件),相关的文件数据编辑好。XML文件配置再spring的xml文件中配置要上传文件的大小<!-- 上
- Ionic是一款流行的移动端开发框架,但是刚入门的同学会发现,Ionic在iOS和Android的底部tabs显示不一样。在安卓情况下底部t
- @ConditionalOnMissingBean,它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果而注册相同类型的
- 引言JVM进程消失可能有哪些原因?这个问题也是面试中经常出现的,如下图所示ps:由于两年多没写crud了,所以忘记mybatis怎么用了,所
- 之前用View Pager做了一个图片切换的推荐栏(就类似与淘宝、头条客户端顶端的推荐信息栏),利用View Pager很快就能实现,但是一
- 1:先检查 字段有没有加上注解 @TableField(fill = FieldFill.INSERT_UPDATE)@TableField
- 本文实例为大家分享了javaweb登录验证码的具体代码,供大家参考,具体内容如下使用:Controller:生成验证码@RequestMap
- 问题描述Flutter 应用在 Android 端上启动时会有一段很明显的白屏现象,白屏的时长由设备的性能决定,设备性能越差,白屏时间越长。
- 说到导出 Excel,我们首先会想到 poi、jsxl 等,使用这些工具会显得笨重,学习难度大。今天学习使用 JeecgBoot 中的 Au
- 前言各位精通CRUD的老司机,相信大家在工作中mybatis或者mybatisplus使用的肯定是比较多的,那么大家或多或少都应该对下面的行
- Android 系统每隔 16ms 会发出 VSYNC 信号重绘界面(Activity)。之所以是 16ms,是因为 Android 设定的
- java swing GUI窗口美化一般我们写出的窗口是这个样子的,文本框和按钮都不是太美观,如果按钮是原色的就更难看了。今天发现了一个更加
- 目录1、表达式目录树2、构建表达式目录树3、使用Expression来进行不同对象的相同名字的属性映射4、表达式目录树构建SQL删选&nbs
- 目录1、前提知识2、实现思路:1、前提知识需要知道简单的IO流操作,以及简单的UDP发送数据包的原理。需要用到的类:DatagramSock
- 本文介绍了spring boot的maven配置依赖详解,分享给大家,具体如下:我们通过引用spring-boot-starter-pare
- 代码如下所示:package com.hoo.util; import java.awt.Color; import
- 1. 简介很早就听说了Google的Lifecycle组件,因为项目没有使用过,所以并没有过多的接触。不过最近看到了一篇文章,其中的一条评论
- 本文实例为大家分享了C语言实现通讯录小项目的具体代码,供大家参考,具体内容如下编写程序实现通讯录的基本功能,可以做到增,删,查,改,打印通讯
- 激活码:9MWZD5CC4E-eyJsaWNlbnNlSWQiOiI5TVdaRDVDQzRFIiwibGljZW5zZWVOY