当前位置:首页 > 科技  > 软件

一种避免写大量CRUD方法的新思路

来源: 责编: 时间:2024-04-30 08:42:50 326观看
导读哈喽,各位代码战士们,我是Jensen,一个梦想着和大家一起在代码的海洋里遨游,顺便捡起那些散落的知识点的程序员小伙伴。今天,我继续给大家带来一个超级无敌霹雳的编码新招式,来自我最近的亲身实践,我把公司的PHP工程(两个端,几

jWP28资讯网——每日最新资讯28at.com

哈喽,各位代码战士们,我是Jensen,一个梦想着和大家一起在代码的海洋里遨游,顺便捡起那些散落的知识点的程序员小伙伴。jWP28资讯网——每日最新资讯28at.com

今天,我继续给大家带来一个超级无敌霹雳的编码新招式,来自我最近的亲身实践,我把公司的PHP工程(两个端,几百个接口)重构到Java工程上来,仅仅用了两天!jWP28资讯网——每日最新资讯28at.com

先看看业务——租赁平台领域图:jWP28资讯网——每日最新资讯28at.com

jWP28资讯网——每日最新资讯28at.com

乍一看这张领域图就不简单(表梳理、核心业务梳理、建模等花了我两天),顺便用脚趾头数了一下,总共是36张表,只谈常规CRUD方法的话,要写36*4=144个API接口,这里还涉及客户端和管理端API的隔离,那翻个倍就是288个API接口了呗。jWP28资讯网——每日最新资讯28at.com

CrudBoy是不可能的,这辈子都不可能的。jWP28资讯网——每日最新资讯28at.com

你信不信,我只写两个Controller,就能把两个端的CRUD全部搞定!jWP28资讯网——每日最新资讯28at.com

本文涉及技术点:SpringMVC、MybatisPlusjWP28资讯网——每日最新资讯28at.com

一、思路分析

问题来了,一个Controller怎么做到多张表的CRUD(增删查改)呢?jWP28资讯网——每日最新资讯28at.com

要做到所有表共用一个Controller,就需要复用公共的CRUD方法。我们需要满足以下5个条件:jWP28资讯网——每日最新资讯28at.com

  • 不同的表需要通过模型名称进行隔离
  • 通过模型名称能找到对应的模型类
  • 通过模型类能找到对应的仓库,从而操作数据库
  • 对于查询方法,请求参数能转化为查询条件,模型作为查询返回类
  • 对于操作方法,请求参数能转化为模型

只需要解决上述问题,一个Controller即可解决所有表的CRUD需求。jWP28资讯网——每日最新资讯28at.com

老规矩——设计先行:jWP28资讯网——每日最新资讯28at.com

jWP28资讯网——每日最新资讯28at.com

好吧,我承认这张图是刚临时画的,代码早就已经实现了,正如你的产品经理告诉你:jWP28资讯网——每日最新资讯28at.com

开发小哥哥,客户说后天要上线这个新功能,能不能拜托你今天把这个小需求开发完,晚上测试完就能发布上线了呗。jWP28资讯网——每日最新资讯28at.com

你不得不用脑子先画个蓝图,边写代码边小步迭代,做完后再补设计。jWP28资讯网——每日最新资讯28at.com

二、先造轮子

首先来个聚合控制器接口AggregateController:jWP28资讯网——每日最新资讯28at.com

/** * 聚合控制器,实现该控制器的Controller,自带CRUD方法 * * @author Jensen * @公众号 架构师修行录 */public interface AggregateController {    // 公共POST分页    @PostMapping("/{modelName}/page")    default Page<Model> postPage(@PathVariable("modelName") String modelName, @RequestBody Map<String, Object> query) {        return convertQuery(getModelClass(modelName), query).page();    }    // 公共GET分页    @GetMapping("/{modelName}/page")    default Page<Model> getPage(@PathVariable("modelName") String modelName, Map<String, Object> query) {        return convertQuery(getModelClass(modelName), query).page();    }    // 公共POST列表    @PostMapping("/{modelName}/list")    default List<Model> postList(@PathVariable("modelName") String modelName, @RequestBody Map<String, Object> query) {        return convertQuery(getModelClass(modelName), query).list();    }    // 公共GET列表    @GetMapping("/{modelName}/list")    default List<Model> getList(@PathVariable("modelName") String modelName, Map<String, Object> query) {        return convertQuery(getModelClass(modelName), query).list();    }    // 公共详情,通过其他条件查第一条    @GetMapping("/{modelName}/detail")    default Model detail(@PathVariable("modelName") String modelName, Map<String, Object> query) {        return convertQuery(getModelClass(modelName), query).first();    }    // 公共详情,通过ID查    @GetMapping("/{modelName}/detail/{id}")    default Model detail(@PathVariable("modelName") String modelName, @PathVariable("id") String id) {        return BaseRepository.of(getModelClass(modelName)).get(id);    }    // 公共创建    @PostMapping({"/{modelName}/save", "/{modelName}/create"})    default Model save(@PathVariable("modelName") String modelName, @RequestBody Map<String, Object> query) {        Model model = convertModel(getModelClass(modelName), query);        model.save();        return model;    }    // 公共批量创建    @PostMapping("/{modelName}/saveBatch")    default void saveBatch(@PathVariable("modelName") String modelName, @RequestBody List<Map<String, Object>> params) {        Class<Model> modelClass = getModelClass(modelName);        BaseRepository.of(modelClass).save(convertModels(modelClass, params));    }    // 公共修改    @PostMapping({"/{modelName}/update", "/{modelName}/modify"})    default void update(@PathVariable("modelName") String modelName, @RequestBody Map<String, Object> query) {        convertModel(getModelClass(modelName), query).update();    }    // 公共删除    @PostMapping({"/{modelName}/delete/{id}", "/{modelName}/remove/{id}"})    default void delete(@PathVariable("modelName") String modelName, @PathVariable("id") String id) {        BaseRepository.of(getModelClass(modelName)).delete(id);    }    // 通过模型名找到模型类    static Class<Model> getModelClass(String modelName) {        Class<Model> modelClass = MappingKit.get("MODEL_NAME", modelName);        BizAssert.notNull(modelClass, "Model: {} not found", modelName);        return modelClass;    }    // 通过模型类找到查询类,并把Map参数转换为查询参数    static Query convertQuery(Class<Model> modelClass, Map<String, Object> queryMap) {        Class<Query> queryClass = MappingKit.get("MODEL_QUERY", modelClass);        BizAssert.notNull(queryClass, "Query not found");        return BeanKit.ofMap(queryMap, queryClass);    }    // 通过Map参数转换为模型    static Model convertModel(Class<Model> modelClass, Map<String, Object> modelMap) {        return BeanKit.ofMap(modelMap, modelClass);    }}

路径参数{modelName}就是模型名,比如建了个表user_info,对应的模型是UserInfo,对应的模型名叫userInfo。jWP28资讯网——每日最新资讯28at.com

下一步,我们需要通过这个动态的模型名路由到对应的模型上,怎么做呢?jWP28资讯网——每日最新资讯28at.com

这时候,我们需要在应用启动时,在初始化仓库实现类中获取到模型后,注入到一个容器。jWP28资讯网——每日最新资讯28at.com

这里我们先定义一个基础仓库接口:jWP28资讯网——每日最新资讯28at.com

/** * 基础仓库接口 * 针对CRUD进行封装,业务仓库需要实现当前接口 * * @author Jensen * @公众号 架构师修行录 */public interface BaseRepository<M extends Model, Q extends Query> {    // 定义一个存放模型类/查询类-仓库实现类映射的容器    Map<Class<?>, Class<?>> REPOSITORY_MAPPINGS = new ConcurrentHashMap<>();    /**     * 注入仓库类     *     * @param mappingClass    Model类/Query类     * @param repositoryClass 仓库类     */    static <R extends BaseRepository> void inject(Class<?> mappingClass, Class<R> repositoryClass) {        REPOSITORY_MAPPINGS.put(mappingClass, repositoryClass);    }    // TODO 封装的CRUD方法暂且略过}

上面这种使用ConcurrentHashMap作为容器的技术,在各个框架里随处可见,还是挺实用的,大家可以学一学。jWP28资讯网——每日最新资讯28at.com

接下来,我们再对MybatisPlus的BaseMapper类进行浅封装,作为基础仓库实现类,针对CRUD进行二次封装:jWP28资讯网——每日最新资讯28at.com

/** * 基础仓库实现类 * 针对CRUD进行封装,业务仓库实现需要继承当前类 * * @author Jensen * @公众号 架构师修行录 */public abstract class BaseRepositoryImpl<MP extends BaseMapper<P>, M extends Model, P, Q extends Query> implements BaseRepository<M, Q>, Serializable {    // 在仓库实现类构造器中初始化各种映射信息    public BaseRepositoryImpl() {        // 通过反射工具,拿到具体的模型类        final Class<M> modelClass = (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), 1);        // 通过反射工具,拿到具体的持久化实体类        final Class<P> poClass = (Class<P>) ReflectionKit.getSuperClassGenericType(this.getClass(), 2);        // 通过反射工具,拿到具体的查询类        final Class<Q> queryClass = (Class<Q>) ReflectionKit.getSuperClassGenericType(this.getClass(), 3);        // 注入模型类-仓库实现类        BaseRepository.inject(modelClass, this.getClass());        // 注入查询类-仓库实现类        BaseRepository.inject(queryClass, this.getClass());        // 映射模型类-实体类        MappingKit.map("MODEL_PO", modelClass, poClass);        MappingKit.map("MODEL_PO", poClass, modelClass);       // 映射模型类-查询类        MappingKit.map("MODEL_QUERY", modelClass, queryClass);        MappingKit.map("MODEL_QUERY", queryClass, modelClass);        // 映射模型名-模型类,模型名首字母设为小写(驼峰式命名)        String modelClassName = modelClass.getSimpleName().toLowerCase().substring(0, 1) + modelClass.getSimpleName().substring(1);        MappingKit.map("MODEL_NAME", modelClassName, modelClass);    }        // TODO 封装的CRUD方法暂且略过}

上面的MappingKit是封装好的用于Bean映射的容器工具类:jWP28资讯网——每日最新资讯28at.com

/** * 用于任意对象映射,按biz隔离(为了复用) */@UtilityClasspublic final class MappingKit {    // Bean容器    private final Map<String, Map<Object, Object>> BEAN_MAPPINGS = new ConcurrentHashMap<>();    public <K, V> void map(String biz, K key, V value) {        Map<Object, Object> mappings = BEAN_MAPPINGS.get(biz);        if (mappings == null) {            mappings = new ConcurrentHashMap<>();            BEAN_MAPPINGS.put(biz, mappings);        }        mappings.put(key, value);    }    public <K, V> V get(String field, K source) {        Map<Object, Object> mappings = BEAN_MAPPINGS.get(field);        if (mappings == null) return null;        return (V) mappings.get(source);    }}

注入的逻辑比较简单,就是建一个Map<String, Class>,key放模型名,Class放Model类,这样就可以通过模型名找到对应的Model类了。jWP28资讯网——每日最新资讯28at.com

在仓库实现类初始化时,我们把其他必要信息先进行映射,如通过Model类找RepositoryImpl仓库实现、通过Model类找Query类等等。jWP28资讯网——每日最新资讯28at.com

至此,我们把映射的工作完成了,大家可以回过头看看AggregateController,就是实际通过模型名modelName从容器中取出模型类、模型类取出仓库的过程了。jWP28资讯网——每日最新资讯28at.com

还看不懂没关系,结合上面的AC架构图,重新理解几遍~jWP28资讯网——每日最新资讯28at.com

三、造完轮子,开车!

打开方式很简单,比如我按端隔离定义了下面两个控制器,仅仅用几行代码就代替了288个API接口的编写:jWP28资讯网——每日最新资讯28at.com

/** * 客户端控制器 */@RestController@RequestMapping("/client")public class ClientController implements AggregateController {}
/** * 管理端控制器 */@RestController@RequestMapping("/admin")public class AdminController implements AggregateController {}

四、写在最后

希望今天分享的AC架构能提高大家CRUD的效率,也让系统重构不再可怕。jWP28资讯网——每日最新资讯28at.com

对了,我把它的完整版集成到了我的D3Boot(DDD快速启动)开源基础框架内,也有适用于部分场景使用的CRUDController,大家需要的话可以移步Gitee抄作业。jWP28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-86684-0.html一种避免写大量CRUD方法的新思路

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Spring一个强大便捷的代理工厂类,你用过吗?

下一篇: 深入剖析:如何使用Pulsar和Arthas高效排查消息队列延迟问题

标签:
  • 热门焦点
  • K60至尊版狂暴引擎2.0加持:超177万跑分斩获性能第一

    Redmi的后性能时代战略发布会今天下午如期举办,在本次发布会上,Redmi公布了多项关于和联发科的深度合作,以及新机K60 Ultra在软件和硬件方面的特性,例如:“K60 至尊版,双芯旗舰
  • 十个可以手动编写的 JavaScript 数组 API

    JavaScript 中有很多API,使用得当,会很方便,省力不少。 你知道它的原理吗? 今天这篇文章,我们将对它们进行一次小总结。现在开始吧。1.forEach()forEach()用于遍历数组接收一参
  • K8S | Service服务发现

    一、背景在微服务架构中,这里以开发环境「Dev」为基础来描述,在K8S集群中通常会开放:路由网关、注册中心、配置中心等相关服务,可以被集群外部访问;图片对于测试「Tes」环境或者
  • 多线程开发带来的问题与解决方法

    使用多线程主要会带来以下几个问题:(一)线程安全问题  线程安全问题指的是在某一线程从开始访问到结束访问某一数据期间,该数据被其他的线程所修改,那么对于当前线程而言,该线程
  • ESG的面子与里子

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之三伏大幕拉起,各地高温预警不绝,但处于厄尔尼诺大&ldquo;烤&rdquo;之下的除了众生,还有各大企业发布的ESG报告。ESG是&ldquo;环境保
  • 阿里大调整

    来源:产品刘有媒体报道称,近期淘宝天猫集团启动了近年来最大的人力制度改革,涉及员工绩效、层级体系等多个核心事项,目前已形成一个初步的&ldquo;征求意见版&rdquo;:1、取消P序列
  • 7月4日见!iQOO 11S官宣:“鸡血版”骁龙8 Gen2+200W快充加持

    上半年已接近尾声,截至目前各大品牌旗下的顶级旗舰都已悉数亮相,而下半年即将推出的顶级旗舰已经成为了数码圈爆料的主流,其中就包括全新的iQOO 11S系
  • 华为举行春季智慧办公新品发布会 首次推出电子墨水屏平板

    北京时间2月27日晚,华为在巴塞罗那举行春季智慧办公新品发布会,在海外市场推出之前已经在中国市场上市的笔记本、平板、激光打印机等办公产品,并首次推出搭载
  • 世界人工智能大会国际日开幕式活动在世博展览馆开启

    30日上午,世界人工智能大会国际日开幕式活动在世博展览馆开启,聚集国际城市代表、重量级院士专家、国际创新企业代表,共同打造人工智能交流平台。上海市副市
Top