自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一種避免寫(xiě)大量CRUD方法的新思路

開(kāi)發(fā) 架構(gòu)
今天,我繼續(xù)給大家?guī)?lái)一個(gè)超級(jí)無(wú)敵霹靂的編碼新招式,來(lái)自我最近的親身實(shí)踐,我把公司的PHP工程(兩個(gè)端,幾百個(gè)接口)重構(gòu)到Java工程上來(lái),僅僅用了兩天!

哈嘍,各位代碼戰(zhàn)士們,我是Jensen,一個(gè)夢(mèng)想著和大家一起在代碼的海洋里遨游,順便撿起那些散落的知識(shí)點(diǎn)的程序員小伙伴。

今天,我繼續(xù)給大家?guī)?lái)一個(gè)超級(jí)無(wú)敵霹靂的編碼新招式,來(lái)自我最近的親身實(shí)踐,我把公司的PHP工程(兩個(gè)端,幾百個(gè)接口)重構(gòu)到Java工程上來(lái),僅僅用了兩天!

先看看業(yè)務(wù)——租賃平臺(tái)領(lǐng)域圖:

乍一看這張領(lǐng)域圖就不簡(jiǎn)單(表梳理、核心業(yè)務(wù)梳理、建模等花了我兩天),順便用腳趾頭數(shù)了一下,總共是36張表,只談常規(guī)CRUD方法的話,要寫(xiě)36*4=144個(gè)API接口,這里還涉及客戶端和管理端API的隔離,那翻個(gè)倍就是288個(gè)API接口了唄。

CrudBoy是不可能的,這輩子都不可能的。

你信不信,我只寫(xiě)兩個(gè)Controller,就能把兩個(gè)端的CRUD全部搞定!

本文涉及技術(shù)點(diǎn):SpringMVC、MybatisPlus。

一、思路分析

問(wèn)題來(lái)了,一個(gè)Controller怎么做到多張表的CRUD(增刪查改)呢?

要做到所有表共用一個(gè)Controller,就需要復(fù)用公共的CRUD方法。我們需要滿足以下5個(gè)條件:

  • 不同的表需要通過(guò)模型名稱(chēng)進(jìn)行隔離
  • 通過(guò)模型名稱(chēng)能找到對(duì)應(yīng)的模型類(lèi)
  • 通過(guò)模型類(lèi)能找到對(duì)應(yīng)的倉(cāng)庫(kù),從而操作數(shù)據(jù)庫(kù)
  • 對(duì)于查詢方法,請(qǐng)求參數(shù)能轉(zhuǎn)化為查詢條件,模型作為查詢返回類(lèi)
  • 對(duì)于操作方法,請(qǐng)求參數(shù)能轉(zhuǎn)化為模型

只需要解決上述問(wèn)題,一個(gè)Controller即可解決所有表的CRUD需求。

老規(guī)矩——設(shè)計(jì)先行:

好吧,我承認(rèn)這張圖是剛臨時(shí)畫(huà)的,代碼早就已經(jīng)實(shí)現(xiàn)了,正如你的產(chǎn)品經(jīng)理告訴你:

開(kāi)發(fā)小哥哥,客戶說(shuō)后天要上線這個(gè)新功能,能不能拜托你今天把這個(gè)小需求開(kāi)發(fā)完,晚上測(cè)試完就能發(fā)布上線了唄。

你不得不用腦子先畫(huà)個(gè)藍(lán)圖,邊寫(xiě)代碼邊小步迭代,做完后再補(bǔ)設(shè)計(jì)。

二、先造輪子

首先來(lái)個(gè)聚合控制器接口AggregateController:

/**
 * 聚合控制器,實(shí)現(xiàn)該控制器的Controller,自帶CRUD方法
 *
 * @author Jensen
 * @公眾號(hào) 架構(gòu)師修行錄
 */
public interface AggregateController {
    // 公共POST分頁(yè)
    @PostMapping("/{modelName}/page")
    default Page<Model> postPage(@PathVariable("modelName") String modelName, @RequestBody Map<String, Object> query) {
        return convertQuery(getModelClass(modelName), query).page();
    }
    // 公共GET分頁(yè)
    @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();
    }
    // 公共詳情,通過(guò)其他條件查第一條
    @GetMapping("/{modelName}/detail")
    default Model detail(@PathVariable("modelName") String modelName, Map<String, Object> query) {
        return convertQuery(getModelClass(modelName), query).first();
    }
    // 公共詳情,通過(guò)ID查
    @GetMapping("/{modelName}/detail/{id}")
    default Model detail(@PathVariable("modelName") String modelName, @PathVariable("id") String id) {
        return BaseRepository.of(getModelClass(modelName)).get(id);
    }
    // 公共創(chuàng)建
    @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;
    }
    // 公共批量創(chuàng)建
    @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);
    }
    // 通過(guò)模型名找到模型類(lèi)
    static Class<Model> getModelClass(String modelName) {
        Class<Model> modelClass = MappingKit.get("MODEL_NAME", modelName);
        BizAssert.notNull(modelClass, "Model: {} not found", modelName);
        return modelClass;
    }
    // 通過(guò)模型類(lèi)找到查詢類(lèi),并把Map參數(shù)轉(zhuǎn)換為查詢參數(shù)
    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);
    }
    // 通過(guò)Map參數(shù)轉(zhuǎn)換為模型
    static Model convertModel(Class<Model> modelClass, Map<String, Object> modelMap) {
        return BeanKit.ofMap(modelMap, modelClass);
    }
}

路徑參數(shù){modelName}就是模型名,比如建了個(gè)表user_info,對(duì)應(yīng)的模型是UserInfo,對(duì)應(yīng)的模型名叫userInfo。

下一步,我們需要通過(guò)這個(gè)動(dòng)態(tài)的模型名路由到對(duì)應(yīng)的模型上,怎么做呢?

這時(shí)候,我們需要在應(yīng)用啟動(dòng)時(shí),在初始化倉(cāng)庫(kù)實(shí)現(xiàn)類(lèi)中獲取到模型后,注入到一個(gè)容器。

這里我們先定義一個(gè)基礎(chǔ)倉(cāng)庫(kù)接口:

/**
 * 基礎(chǔ)倉(cāng)庫(kù)接口
 * 針對(duì)CRUD進(jìn)行封裝,業(yè)務(wù)倉(cāng)庫(kù)需要實(shí)現(xiàn)當(dāng)前接口
 *
 * @author Jensen
 * @公眾號(hào) 架構(gòu)師修行錄
 */
public interface BaseRepository<M extends Model, Q extends Query> {
    // 定義一個(gè)存放模型類(lèi)/查詢類(lèi)-倉(cāng)庫(kù)實(shí)現(xiàn)類(lèi)映射的容器
    Map<Class<?>, Class<?>> REPOSITORY_MAPPINGS = new ConcurrentHashMap<>();
    /**
     * 注入倉(cāng)庫(kù)類(lèi)
     *
     * @param mappingClass    Model類(lèi)/Query類(lèi)
     * @param repositoryClass 倉(cāng)庫(kù)類(lèi)
     */
    static <R extends BaseRepository> void inject(Class<?> mappingClass, Class<R> repositoryClass) {
        REPOSITORY_MAPPINGS.put(mappingClass, repositoryClass);
    }
    // TODO 封裝的CRUD方法暫且略過(guò)
}

上面這種使用ConcurrentHashMap作為容器的技術(shù),在各個(gè)框架里隨處可見(jiàn),還是挺實(shí)用的,大家可以學(xué)一學(xué)。

接下來(lái),我們?cè)賹?duì)MybatisPlus的BaseMapper類(lèi)進(jìn)行淺封裝,作為基礎(chǔ)倉(cāng)庫(kù)實(shí)現(xiàn)類(lèi),針對(duì)CRUD進(jìn)行二次封裝:

/**
 * 基礎(chǔ)倉(cāng)庫(kù)實(shí)現(xiàn)類(lèi)
 * 針對(duì)CRUD進(jìn)行封裝,業(yè)務(wù)倉(cāng)庫(kù)實(shí)現(xiàn)需要繼承當(dāng)前類(lèi)
 *
 * @author Jensen
 * @公眾號(hào) 架構(gòu)師修行錄
 */
public abstract class BaseRepositoryImpl<MP extends BaseMapper<P>, M extends Model, P, Q extends Query> implements BaseRepository<M, Q>, Serializable {


    // 在倉(cāng)庫(kù)實(shí)現(xiàn)類(lèi)構(gòu)造器中初始化各種映射信息
    public BaseRepositoryImpl() {
        // 通過(guò)反射工具,拿到具體的模型類(lèi)
        final Class<M> modelClass = (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), 1);
        // 通過(guò)反射工具,拿到具體的持久化實(shí)體類(lèi)
        final Class<P> poClass = (Class<P>) ReflectionKit.getSuperClassGenericType(this.getClass(), 2);
        // 通過(guò)反射工具,拿到具體的查詢類(lèi)
        final Class<Q> queryClass = (Class<Q>) ReflectionKit.getSuperClassGenericType(this.getClass(), 3);
        // 注入模型類(lèi)-倉(cāng)庫(kù)實(shí)現(xiàn)類(lèi)
        BaseRepository.inject(modelClass, this.getClass());
        // 注入查詢類(lèi)-倉(cāng)庫(kù)實(shí)現(xiàn)類(lèi)
        BaseRepository.inject(queryClass, this.getClass());
        // 映射模型類(lèi)-實(shí)體類(lèi)
        MappingKit.map("MODEL_PO", modelClass, poClass);
        MappingKit.map("MODEL_PO", poClass, modelClass);
       // 映射模型類(lèi)-查詢類(lèi)
        MappingKit.map("MODEL_QUERY", modelClass, queryClass);
        MappingKit.map("MODEL_QUERY", queryClass, modelClass);
        // 映射模型名-模型類(lèi),模型名首字母設(shè)為小寫(xiě)(駝峰式命名)
        String modelClassName = modelClass.getSimpleName().toLowerCase().substring(0, 1) + modelClass.getSimpleName().substring(1);
        MappingKit.map("MODEL_NAME", modelClassName, modelClass);
    }
    
    // TODO 封裝的CRUD方法暫且略過(guò)
}

上面的MappingKit是封裝好的用于Bean映射的容器工具類(lèi):

/**
 * 用于任意對(duì)象映射,按biz隔離(為了復(fù)用)
 */
@UtilityClass
public 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);
    }
}

注入的邏輯比較簡(jiǎn)單,就是建一個(gè)Map<String, Class>,key放模型名,Class放Model類(lèi),這樣就可以通過(guò)模型名找到對(duì)應(yīng)的Model類(lèi)了。

在倉(cāng)庫(kù)實(shí)現(xiàn)類(lèi)初始化時(shí),我們把其他必要信息先進(jìn)行映射,如通過(guò)Model類(lèi)找RepositoryImpl倉(cāng)庫(kù)實(shí)現(xiàn)、通過(guò)Model類(lèi)找Query類(lèi)等等。

至此,我們把映射的工作完成了,大家可以回過(guò)頭看看AggregateController,就是實(shí)際通過(guò)模型名modelName從容器中取出模型類(lèi)、模型類(lèi)取出倉(cāng)庫(kù)的過(guò)程了。

還看不懂沒(méi)關(guān)系,結(jié)合上面的AC架構(gòu)圖,重新理解幾遍~

三、造完輪子,開(kāi)車(chē)!

打開(kāi)方式很簡(jiǎn)單,比如我按端隔離定義了下面兩個(gè)控制器,僅僅用幾行代碼就代替了288個(gè)API接口的編寫(xiě):

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

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


}

四、寫(xiě)在最后

希望今天分享的AC架構(gòu)能提高大家CRUD的效率,也讓系統(tǒng)重構(gòu)不再可怕。

對(duì)了,我把它的完整版集成到了我的D3Boot(DDD快速啟動(dòng))開(kāi)源基礎(chǔ)框架內(nèi),也有適用于部分場(chǎng)景使用的CRUDController,大家需要的話可以移步Gitee抄作業(yè)。

責(zé)任編輯:姜華 來(lái)源: 架構(gòu)師修行錄
相關(guān)推薦

2024-04-26 08:58:54

if-else代碼JavaSpring

2024-05-09 08:20:29

AC架構(gòu)數(shù)據(jù)庫(kù)冗余存儲(chǔ)

2022-06-23 07:05:46

跳板機(jī)服務(wù)器PAM

2023-09-17 23:16:46

緩存數(shù)據(jù)庫(kù)

2016-10-26 09:12:58

2018-04-18 07:34:58

2025-01-27 13:00:00

2020-11-27 14:45:57

開(kāi)發(fā)服務(wù)器代碼

2017-08-24 15:02:01

前端增量式更新

2019-11-22 09:21:17

技術(shù)研發(fā)數(shù)據(jù)

2016-10-13 10:57:55

phptcp專(zhuān)欄

2021-05-18 06:22:39

CSS 制作波浪技巧

2009-12-03 10:32:21

2017-01-23 11:18:16

戴爾

2018-12-14 14:30:12

安全檢測(cè)布式系測(cè)試

2011-07-04 17:53:48

快速測(cè)試

2010-03-26 13:34:47

CentOS安裝

2021-06-11 00:11:23

GPS數(shù)據(jù)協(xié)議

2023-06-18 23:19:17

ChatGPTPPT方式

2018-02-08 08:11:41

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)