项目背景
前段时间我们介绍了如何使用thymeleaf去生成mybatis相关的模板:https://www.lixiang.red/articles/2019/07/23/1563857782748.html今天小刀和各位小伙伴们一起来深入下这个问题,我们来研究下怎么去封装一个通用的Mybatis模板,让简单的增删改查操作,直接通过我们的代码生成工具就能解决,让工具从demo级别上升到生产可用的项目
mybatis简单介绍
相信很多小伙伴都已经用过Mybatis了,很简单方便, 在springboot的集成环境里面,写个DAO接口,加上注解,然后写个xml和DAO关联起来,然后xml里面方法和DAO里面的方法关联起来,然后就可以通过调用DAO方法的形式来调用这个sql,并获得相应的返回结果,代码结构如图下所示:
这是以往的方式,在新版Mybatis中,我们有了一个新的选择,用java类和注解的方式去完成一个sql ,官方文档如下:http://www.mybatis.org/mybatis-3/statement-builders.html写法如下:
只建议简单的sql用注解的方式去处理,复杂的还是要手写sql , 可以重新建一个DAO , 或者在provider里面用字符串拼接的方式去完成具体用法,大家可以参考上面的官方文档中的地址
设计封装的方法
封装规则要求
我们总说,我们一直都在做重复的增删改查工作,现在我们就可以把这些基本的操作都封装起来,把更多的精力放在应用高可用,高响应,业务逻辑的梳理上面因此 ,我们的封装也是以增删改查四大方向为主.在封装之前,我们先约定以下规定:1.数据库对应的实体对象,我们用DO(domain object)来表示2.数据库对应的查询对应,我们用QC(query object)来表示3.各种操作方法的命名,如,getModel,listModels,saveModels等等这样把数据库实体对象和查询对象分开,因为我们的在加查询条件的时候,比如通过idList去查询,如果在DO里面新加字段就不太好,所以抽象了一个QC的概念,专门做数据库查询对象。
从前到后的对应关系
为什么我们项目可以用模板生成工具进行生成呢,大家仔细研究下手中的项目可以发现我们的项目从controller开始,到最后的DAO其实都是有名字对应起来的,我们以查询为例: listItems,通过这个名字我们可以直观的看到,是获取商品列表的.Controller中的代码:
@GetMapping("/item/query")@ResponseBodypublic BaseResponse> queryItem(ItemDO item, Integer pageIndex , Integer pageSize){ List items = itemManager.queryItem(item, pageIndex, pageSize);Long totalCount = itemManager.countItem(item); pageIndex = pageIndex == null?1:pageIndex; pageSize = pageSize == null?20:pageSize;return BaseResponse.assemblePageResponse(items,totalCount,pageIndex,pageSize); }
模板:
@GetMapping("/[(${table.javaCamelName})]/query") @ResponseBody public BaseResponse> query[(${table.javaTableName})]([(${table.javaTableName})]DO [(${table.javaCamelName})], Integer pageIndex , Integer pageSize){ List [(${table.javaCamelName})]s = [(${table.javaCamelName})]Manager.query[(${table.javaTableName})]([(${table.javaCamelName})], pageIndex, pageSize); Long totalCount = [(${table.javaCamelName})]Manager.count[(${table.javaTableName})]([(${table.javaCamelName})]); pageIndex = pageIndex == null?1:pageIndex; pageSize = pageSize == null?20:pageSize; return BaseResponse.assemblePageResponse([(${table.javaCamelName})]s,totalCount,pageIndex,pageSize); }
Manager中的代码:
public List queryItem(ItemDO item, int pageIndex , int pageSize);
模板:
public List query[(${table.javaTableName})]([(${table.javaTableName})]DO [(${table.javaCamelName})], int pageIndex , int pageSize);
ManagerImpl中的代码:
@Overridepublic List queryItem(ItemDO item, int pageIndex , int pageSize){ Page page = new Page(pageIndex,pageSize); ItemQC qc = new ItemQC(); qc.setPage(page); BeanUtils.copyProperties(item,qc); List items = itemMapper.listItems(qc);return items; }
模板:
@Override public List query[(${table.javaTableName})]([(${table.javaTableName})]DO [(${table.javaCamelName})], int pageIndex , int pageSize){ Page page = new Page(pageIndex,pageSize); [(${table.javaTableName})]QC qc = new [(${table.javaTableName})]QC(); qc.setPage(page); BeanUtils.copyProperties([(${table.javaCamelName})],qc); List [(${table.javaCamelName})]s = [(${table.javaCamelName})]Mapper.list[(${table.javaTableName})]s(qc);return [(${table.javaCamelName})]s; }
Mapper中的代码:
@SelectProvider(type = ItemProvider.class) List listItems(ItemQC itemQC);
模板:
@SelectProvider(type = [(${table.javaTableName})]Provider.class) List list[(${table.javaTableName})]s([(${table.javaTableName})]QC [(${table.javaCamelName})]QC);
Provider中的代码:
public String listItems(ItemQC itemQC){ SQL sql = new SQL() {{ SELECT(TABLE_FIELDS); FROM("item"); }}; MapperUtils.richWhereSql(sql, itemQC);return sql.toString(); }
模板:
public String list[(${table.javaTableName})]s([(${table.javaTableName})]QC [(${table.javaCamelName})]QC){ SQL sql = new SQL() {{ SELECT(TABLE_FIELDS); FROM("[(${table.javaCamelName})]"); }}; MapperUtils.richWhereSql(sql, [(${table.javaCamelName})]QC);return sql.toString(); }
这样我们从前到后看过来,发其实都是围绕着一个关键词在走: Item , 有时候把他变成大写的,有时候把他变成驼峰命名,有时候把他加个DO后缀,有时候把他加个Mapper后缀.
生成文件到对应目录中
在上篇文章中,我们只是生成了对应的字符串,但是在实际开发中,我们是需要生成文件的,最好是能在对应文件夹中.所以这就有两步,1是生成文件.2是找到对应文件夹
生成对应文件
生成文件这个应该是很简单的,直接一句代码搞定:
Files.write(Paths.get(pathMap.get("model")+tableInfo.getJavaTableName() + "DO.java"),model.getBytes());Files.write(Paths.get(pathMap.get("mapper")+tableInfo.getJavaTableName() + "Mapper.java"),dao.getBytes());Files.write(Paths.get(pathMap.get("provider")+tableInfo.getJavaTableName() + "Provider.java"),provider.getBytes());Files.write(Paths.get(pathMap.get("manager")+tableInfo.getJavaTableName() + "Manager.java"),manager.getBytes());Files.write(Paths.get(pathMap.get("managerImpl")+tableInfo.getJavaTableName() + "ManagerImpl.java"),managerImpl.getBytes()); Files.write(Paths.get(pathMap.get("controller")+tableInfo.getJavaTableName() + "Controller.java"),controller.getBytes());
输出到对应文件夹中
这一步,就要我们明确,我们的每个文件是放在什么地方在,如Mapper/Provider是查询数据库用的, 我们把他放在business-impl模块中的dao文件夹中,下面小刀把自己项目中放的位置列出来和大家分享下,大家有好的想法可以一起交流:XXXDO放在 business 模块中 models.dos文件夹中XXXController 放在admin 模块中 controller 文件夹中XXXManager 放在business模块中 business文件夹中XXXManagerImpl 放在business-impl模块中 business.impl文件夹中XXXMapper/XXXProvider 放在 business-impl模块中 dao 文件夹中这样我们就可以用代码去定位到对应的文件夹中了:
// 获得当前项目的文件夹String currentDir = System.getProperty("user.dir"); currentDir=currentDir.substring(0,currentDir.lastIndexOf("/")); pathMap.put("model",currentDir); pathMap.put("mapper",currentDir); pathMap.put("provider",currentDir); pathMap.put("manager",currentDir); pathMap.put("managerImpl",currentDir); pathMap.put("controller",currentDir);//如果设置了自动路径,就YY一下路径if(autoFilePath){ Files.list(Paths.get(currentDir)).forEach(file->{if(Files.isDirectory(file)){String fileName = file.toString();if(fileName.endsWith("admin")){ pathMap.put("controller",fileName+"/src/main/java/"+packageName.replaceAll("\\.","/")+"/admin/controller/"); }if(fileName.endsWith("business")){ pathMap.put("model",fileName+"/src/main/java/"+packageName.replaceAll("\\.","/")+"/models/dos/"); pathMap.put("manager",fileName+"/src/main/java/"+packageName.replaceAll("\\.","/")+"/business/manager/"); }if(fileName.endsWith("business-impl")){ pathMap.put("managerImpl",fileName+"/src/main/java/"+packageName.replaceAll("\\.","/")+"/business/manager/impl/"+domainName+"/"); pathMap.put("mapper",fileName+"/src/main/java/"+packageName.replaceAll("\\.","/")+"/dao/"+domainName+"/"); pathMap.put("provider",fileName+"/src/main/java/"+packageName.replaceAll("\\.","/")+"/dao/"+domainName+"/"); } } }); }
这样我们就可以做到,把增删改查从controller到dao直接一键生成到对应文件夹啦,只要把表建好,剩下的事就一步搞定啦
最后说两句
这个工具一般是做后台管理页面,增删改查的时候用, 业务逻辑不建议用工具类生成,业务逻辑一般是提供dubbo接口出来,不要直接把manager中的增删改查提供出去,提供出去的一定是要先有业务,再有接口,然后在serviceImpl中调用manager去完成业务逻辑大家有什么想法,欢迎留言或者加小刀微信: best396975802