AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

一種避免寫大量CRUD方法的新思路

來源: 責編: 時間:2024-04-30 08:42:50 183觀看
導讀哈嘍,各位代碼戰士們,我是Jensen,一個夢想著和大家一起在代碼的海洋里遨游,順便撿起那些散落的知識點的程序員小伙伴。今天,我繼續給大家帶來一個超級無敵霹靂的編碼新招式,來自我最近的親身實踐,我把公司的PHP工程(兩個端,幾

qgn28資訊網——每日最新資訊28at.com

哈嘍,各位代碼戰士們,我是Jensen,一個夢想著和大家一起在代碼的海洋里遨游,順便撿起那些散落的知識點的程序員小伙伴。qgn28資訊網——每日最新資訊28at.com

今天,我繼續給大家帶來一個超級無敵霹靂的編碼新招式,來自我最近的親身實踐,我把公司的PHP工程(兩個端,幾百個接口)重構到Java工程上來,僅僅用了兩天!qgn28資訊網——每日最新資訊28at.com

先看看業務——租賃平臺領域圖:qgn28資訊網——每日最新資訊28at.com

qgn28資訊網——每日最新資訊28at.com

乍一看這張領域圖就不簡單(表梳理、核心業務梳理、建模等花了我兩天),順便用腳趾頭數了一下,總共是36張表,只談常規CRUD方法的話,要寫36*4=144個API接口,這里還涉及客戶端和管理端API的隔離,那翻個倍就是288個API接口了唄。qgn28資訊網——每日最新資訊28at.com

CrudBoy是不可能的,這輩子都不可能的。qgn28資訊網——每日最新資訊28at.com

你信不信,我只寫兩個Controller,就能把兩個端的CRUD全部搞定!qgn28資訊網——每日最新資訊28at.com

本文涉及技術點:SpringMVC、MybatisPlusqgn28資訊網——每日最新資訊28at.com

一、思路分析

問題來了,一個Controller怎么做到多張表的CRUD(增刪查改)呢?qgn28資訊網——每日最新資訊28at.com

要做到所有表共用一個Controller,就需要復用公共的CRUD方法。我們需要滿足以下5個條件:qgn28資訊網——每日最新資訊28at.com

  • 不同的表需要通過模型名稱進行隔離
  • 通過模型名稱能找到對應的模型類
  • 通過模型類能找到對應的倉庫,從而操作數據庫
  • 對于查詢方法,請求參數能轉化為查詢條件,模型作為查詢返回類
  • 對于操作方法,請求參數能轉化為模型

只需要解決上述問題,一個Controller即可解決所有表的CRUD需求。qgn28資訊網——每日最新資訊28at.com

老規矩——設計先行:qgn28資訊網——每日最新資訊28at.com

qgn28資訊網——每日最新資訊28at.com

好吧,我承認這張圖是剛臨時畫的,代碼早就已經實現了,正如你的產品經理告訴你:qgn28資訊網——每日最新資訊28at.com

開發小哥哥,客戶說后天要上線這個新功能,能不能拜托你今天把這個小需求開發完,晚上測試完就能發布上線了唄。qgn28資訊網——每日最新資訊28at.com

你不得不用腦子先畫個藍圖,邊寫代碼邊小步迭代,做完后再補設計。qgn28資訊網——每日最新資訊28at.com

二、先造輪子

首先來個聚合控制器接口AggregateController:qgn28資訊網——每日最新資訊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。qgn28資訊網——每日最新資訊28at.com

下一步,我們需要通過這個動態的模型名路由到對應的模型上,怎么做呢?qgn28資訊網——每日最新資訊28at.com

這時候,我們需要在應用啟動時,在初始化倉庫實現類中獲取到模型后,注入到一個容器。qgn28資訊網——每日最新資訊28at.com

這里我們先定義一個基礎倉庫接口:qgn28資訊網——每日最新資訊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作為容器的技術,在各個框架里隨處可見,還是挺實用的,大家可以學一學。qgn28資訊網——每日最新資訊28at.com

接下來,我們再對MybatisPlus的BaseMapper類進行淺封裝,作為基礎倉庫實現類,針對CRUD進行二次封裝:qgn28資訊網——每日最新資訊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映射的容器工具類:qgn28資訊網——每日最新資訊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類了。qgn28資訊網——每日最新資訊28at.com

在倉庫實現類初始化時,我們把其他必要信息先進行映射,如通過Model類找RepositoryImpl倉庫實現、通過Model類找Query類等等。qgn28資訊網——每日最新資訊28at.com

至此,我們把映射的工作完成了,大家可以回過頭看看AggregateController,就是實際通過模型名modelName從容器中取出模型類、模型類取出倉庫的過程了。qgn28資訊網——每日最新資訊28at.com

還看不懂沒關系,結合上面的AC架構圖,重新理解幾遍~qgn28資訊網——每日最新資訊28at.com

三、造完輪子,開車!

打開方式很簡單,比如我按端隔離定義了下面兩個控制器,僅僅用幾行代碼就代替了288個API接口的編寫:qgn28資訊網——每日最新資訊28at.com

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

四、寫在最后

希望今天分享的AC架構能提高大家CRUD的效率,也讓系統重構不再可怕。qgn28資訊網——每日最新資訊28at.com

對了,我把它的完整版集成到了我的D3Boot(DDD快速啟動)開源基礎框架內,也有適用于部分場景使用的CRUDController,大家需要的話可以移步Gitee抄作業。qgn28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-86684-0.html一種避免寫大量CRUD方法的新思路

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Spring一個強大便捷的代理工廠類,你用過嗎?

下一篇: 深入剖析:如何使用Pulsar和Arthas高效排查消息隊列延遲問題

標簽:
  • 熱門焦點
Top