目录
一、问题
最近在做代码重构,代码工程采用了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.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 findlistbydto(samplequerydto samplequerydto) { //开启分页 pagehelperutil.startpage(samplequerydto); //查询分页列表 list samplelist = this.samplemapper.selectlist(wrappers. 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 findlistbycondition(string name, string type) { list samplelist = this.samplemapper.selectlist(wrappers. 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{ //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 pageinfofindlistbydto(samplequerydto samplequerydto) { //开启分页 pagehelperutil.startpage(samplequerydto); //查询分页列表 list samplelist = this.samplemapper.findlist(samplequerydto); //转换分页结果 return pagehelperutil.convertpageinfo(samplelist, sampleto.class); } @override public list findlistbycondition(string name, string type) { list 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{ default sample findinfobyid(long id) { return this.selectone(wrappers. lambdaquery() //仅查询指定的column .select(sample::getid, sample::getname, sample::getdate) //查询条件 id = #{id} .eq(sample::getid, id) ); } default list findlist(samplequerydto samplequerydto) { return this.selectlist(wrappers. 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 findlistbynameandtype(string name, string type) { return this.selectlist(wrappers. 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类、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层代码更清爽,可读性更好了,重构过程的代码改动量也在可接收的范围内。