关于Mybatis-Plus Wrapper是否应该出现在Servcie类中

 更新时间:2023年05月09日 12:37:21   作者:罗小爬EX  
最近在做代码重构,代码工程采用了Controller/Service/Dao分层架构,Dao层使用了Mybatis-Plus框架,本文带领大家学习Mybatis-Plus Wrapper应该出现在Servcie类中吗,需要的朋友可以参考下

一、问题

最近在做代码重构,代码工程采用了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层代码更清爽,可读性更好了,重构过程的代码改动量也在可接收的范围内。

到此这篇关于Mybatis-Plus Wrapper应该出现在Servcie类中吗?的文章就介绍到这了,更多相关Mybatis-Plus Wrapper内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何利用JAVA正则表达式轻松替换JSON中的大字段

    如何利用JAVA正则表达式轻松替换JSON中的大字段

    这篇文章主要给大家介绍了关于如何利用JAVA正则表达式轻松替换JSON中大字段的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Java基础教程之封装与接口

    Java基础教程之封装与接口

    这篇文章主要介绍了Java基础教程之封装与接口,本文用浅显易懂的语言讲解了Java中的封装与接口,很形象的说明了这两个面向对象术语,需要的朋友可以参考下
    2014-08-08
  • Java下3中XML解析 DOM方式、SAX方式和StAX方式

    Java下3中XML解析 DOM方式、SAX方式和StAX方式

    目前我知道的JAVA解析XML的方式有:DOM, SAX, StAX;如果选用这几种,感觉还是有点麻烦;如果使用:JAXB(Java Architecture for XML Binding),个人觉得太方便了
    2013-04-04
  • SpringCloud 服务注册中的nacos实现过程

    SpringCloud 服务注册中的nacos实现过程

    这篇文章主要介绍了SpringCloud 服务注册之nacos实现过程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • 解决SpringBoot运行Test时报错:SpringBoot Unable to find

    解决SpringBoot运行Test时报错:SpringBoot Unable to find

    这篇文章主要介绍了SpringBoot运行Test时报错:SpringBoot Unable to find a @SpringBootConfiguration,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • spring boot项目没有mainClass如何实现打包运行

    spring boot项目没有mainClass如何实现打包运行

    这篇文章主要介绍了spring boot项目没有mainClass如何实现打包运行,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • Spring框架基于xml实现自动装配流程详解

    Spring框架基于xml实现自动装配流程详解

    自动装配就是指 Spring 容器在不使用 <constructor-arg> 和<property> 标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中
    2022-11-11
  • 如何基于回调实现Java的异步调用

    如何基于回调实现Java的异步调用

    这篇文章主要介绍了如何基于回调实现Java的异步调用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • 解决Java中的强制类型转换和二进制表示问题

    解决Java中的强制类型转换和二进制表示问题

    这篇文章主要介绍了解决Java中的强制类型转换和二进制表示问题,需要的朋友可以参考下
    2019-05-05
  • Knife4j 3.0.3 整合SpringBoot 2.6.4的详细过程

    Knife4j 3.0.3 整合SpringBoot 2.6.4的详细过程

    本文要讲的是 Knife4j 3.0.3 整合SpringBoot 2.6.4,在SpringBoot 2.4以上的版本和之前的版本还是不一样的,这个也容易导致一些问题,本文就这两个版本的整合做一个实战介绍
    2022-09-09

最新评论