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

一種優(yōu)雅實(shí)現(xiàn)多表查詢的新思路

數(shù)據(jù)庫(kù)
AC架構(gòu)不是銀彈,不適用于所有場(chǎng)景。對(duì)于需要精細(xì)化管理接口的業(yè)務(wù),還是要拆開(kāi)一個(gè)個(gè)接口去寫;而對(duì)于小而美的微服務(wù)、單表CRUD比較多的管理后臺(tái),采用AC架構(gòu)可以節(jié)省大量重復(fù)性編碼。

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

上一篇文章有一些小伙伴在吐槽我的AC架構(gòu):

這里我統(tǒng)一補(bǔ)充:AC架構(gòu)不是銀彈,不適用于所有場(chǎng)景。對(duì)于需要精細(xì)化管理接口的業(yè)務(wù),還是要拆開(kāi)一個(gè)個(gè)接口去寫;而對(duì)于小而美的微服務(wù)、單表CRUD比較多的管理后臺(tái),采用AC架構(gòu)可以節(jié)省大量重復(fù)性編碼。

也有很多小伙伴私信我:使用AC架構(gòu)怎么解決聯(lián)表查詢問(wèn)題?本文將為大家揭曉。

本文涉及技術(shù)點(diǎn):存儲(chǔ)方式、內(nèi)存聚合。

一、冗余存還是范式存

在數(shù)據(jù)庫(kù)設(shè)計(jì)中,冗余存儲(chǔ)(Redundant Storage)和范式存儲(chǔ)(Normal Form Storage)是兩種不同的數(shù)據(jù)組織方式,它們各自有不同的優(yōu)缺點(diǎn)。

冗余存儲(chǔ)的優(yōu)缺點(diǎn):

優(yōu)點(diǎn):

  • 查詢性能:冗余存儲(chǔ)可以通過(guò)減少連接操作來(lái)提高查詢性能,因?yàn)樗钄?shù)據(jù)已經(jīng)被存儲(chǔ)在了同一個(gè)地方。
  • 減少I/O:在查詢時(shí),可以減少磁盤I/O操作,因?yàn)樗袛?shù)據(jù)都在同一個(gè)表中。
  • 簡(jiǎn)化應(yīng)用邏輯:應(yīng)用層不需要處理復(fù)雜的多表關(guān)聯(lián),簡(jiǎn)化了應(yīng)用邏輯。

缺點(diǎn):

  • 數(shù)據(jù)不一致:冗余數(shù)據(jù)可能導(dǎo)致數(shù)據(jù)不一致性問(wèn)題,特別是在數(shù)據(jù)更新時(shí),需要確保所有冗余的副本都被正確更新。
  • 存儲(chǔ)空間:冗余存儲(chǔ)會(huì)占用更多的存儲(chǔ)空間,因?yàn)橄嗤臄?shù)據(jù)在多個(gè)地方被存儲(chǔ)。
  • 維護(hù)困難:隨著數(shù)據(jù)量的增加,冗余數(shù)據(jù)的維護(hù)變得更加困難,任何結(jié)構(gòu)變更都可能涉及到多個(gè)表的修改。

范式存儲(chǔ)的優(yōu)缺點(diǎn):

優(yōu)點(diǎn):

  • 數(shù)據(jù)一致性:范式化減少了數(shù)據(jù)冗余,有助于保持?jǐn)?shù)據(jù)一致性。
  • 存儲(chǔ)效率:通過(guò)消除重復(fù)數(shù)據(jù),可以節(jié)省存儲(chǔ)空間。
  • 數(shù)據(jù)完整性:范式化有助于實(shí)施數(shù)據(jù)完整性約束,如實(shí)體完整性、參照完整性等。

缺點(diǎn):

  • 查詢性能:范式化可能導(dǎo)致查詢性能下降,因?yàn)樾枰獔?zhí)行多表連接來(lái)獲取完整數(shù)據(jù)。
  • 增加I/O:多表連接可能會(huì)增加磁盤I/O操作,特別是在涉及大型表的情況下。
  • 復(fù)雜查詢:應(yīng)用層可能需要編寫更復(fù)雜的SQL查詢來(lái)處理多表關(guān)聯(lián)。

我們?cè)購(gòu)?fù)習(xí)一下大學(xué)老師教過(guò)的數(shù)據(jù)庫(kù)范式(Normal Forms):

范式化是數(shù)據(jù)庫(kù)設(shè)計(jì)中的一個(gè)概念,旨在減少數(shù)據(jù)冗余和提高數(shù)據(jù)完整性。有幾種不同的范式,包括:

  • 第一范式(1NF):每個(gè)表格的每個(gè)列都是不可分割的基本數(shù)據(jù)項(xiàng)。
  • 第二范式(2NF):在1NF的基礎(chǔ)上,所有非主屬性完全依賴于主鍵。
  • 第三范式(3NF):在2NF的基礎(chǔ)上,沒(méi)有傳遞依賴,即非主屬性只能依賴于主鍵,不能依賴于其他非主屬性。

在實(shí)際應(yīng)用中,完全遵循范式化可能會(huì)導(dǎo)致查詢性能問(wèn)題,因此通常會(huì)根據(jù)實(shí)際情況進(jìn)行適度的反范式化(Denormalization),即有意引入一些冗余來(lái)優(yōu)化性能。

設(shè)計(jì)數(shù)據(jù)庫(kù)時(shí),需要在數(shù)據(jù)一致性、存儲(chǔ)效率和查詢性能之間做出權(quán)衡。

二、走進(jìn)“聚合”

“聚合”二字經(jīng)常出現(xiàn)在程序員之間、程序員與產(chǎn)品經(jīng)理的對(duì)話中,雖然產(chǎn)品不懂技術(shù)語(yǔ)言,他們只管給客戶實(shí)現(xiàn)特定需求,為技術(shù)提供產(chǎn)品原型,但是技術(shù)比較關(guān)心到底是冗余存還是范式存。

如果涉及的數(shù)據(jù)需要分表存儲(chǔ),單表查滿足不了客戶需求,這時(shí)就需要進(jìn)行數(shù)據(jù)聚合,傳統(tǒng)的方式是寫SQL,Join連接多張表,返回多張表的數(shù)據(jù)給前端,這種方式對(duì)數(shù)據(jù)庫(kù)查詢有一定的壓力,整體性能雖然較好,但缺點(diǎn)也很明顯:

隨著業(yè)務(wù)量越來(lái)越大,工程里的SQL會(huì)滿天飛,并且SQL會(huì)寫得越來(lái)越復(fù)雜,對(duì)維護(hù)SQL的人簡(jiǎn)直是噩耗!

這意味著什么?代碼寫了兩三年后,SQL已經(jīng)很難維護(hù)了,沒(méi)人敢動(dòng),每次看SQL都特別費(fèi)勁,還不知道哪一天會(huì)出現(xiàn)慢SQL。

其實(shí)除了寫SQL語(yǔ)句,我們還能通過(guò)倉(cāng)庫(kù)實(shí)現(xiàn)層做數(shù)據(jù)聚合,這就是所謂的“內(nèi)存Join”,把數(shù)據(jù)庫(kù)壓力轉(zhuǎn)移到了不要錢的CPU與內(nèi)存中,具體方法如下:

  • 查詢參數(shù)Query對(duì)象添加Boolean fillXxx字段,用于控制是否要聚合。
  • 倉(cāng)庫(kù)實(shí)現(xiàn)XxxRepositoryImpl實(shí)現(xiàn)fill(Query query, List<Model> models)方法,models就是查詢出主表的結(jié)果集,通過(guò)query.fillXxx=true控制聚合其他表的數(shù)據(jù)。

這個(gè)fillXxx就是聚合參數(shù),大多數(shù)情況下是默認(rèn)關(guān)閉的,也就是默認(rèn)還是單表查詢,如果需要聚合,由前端傳參控制,這樣既可以兼顧性能,也可以兼顧功能,我們按需聚合數(shù)據(jù)即可。

下面我們看看一個(gè)真實(shí)案例,預(yù)約單倉(cāng)庫(kù)實(shí)現(xiàn),聚合預(yù)約主體、預(yù)約記錄、預(yù)約主體評(píng)價(jià):

/**
 * 預(yù)約單倉(cāng)庫(kù)實(shí)現(xiàn)
 */
@Repository
public class AppointmentOrderRepositoryImpl extends
        BaseRepositoryImpl<AppointmentOrderMapper, AppointmentOrder, AppointmentOrderPO, AppointmentOrderQuery> implements
        AppointmentOrderRepository {


    // 聚合方法
    @Override
    public void fill(AppointmentOrderQuery query, List<AppointmentOrder> appointmentOrders) {
        Map<String, AppointmentOrder> orderId2appointment = appointmentOrders.stream().collect(Collectors.toMap(AppointmentOrder::getOrderId, o -> o));
        if (query.getFillAppointment()) {
            // 聚合預(yù)約主體
            this.fillAppointment(appointmentOrders);
        }
        if (query.getFillAppointmentRecords()) {
            // 聚合預(yù)約記錄列表
            this.fillAppointmentRecords(orderId2appointment);
        }
        if (query.getFillAppointmentComment()) {
            // 聚合預(yù)約主體評(píng)論
            this.fillAppointmentComments(appointmentOrders);
        }
    }


    private void fillAppointment(List<AppointmentOrder> appointmentOrders) {
        // 通過(guò)預(yù)約單的主體ID列表(去重)查詢主體
        Set<String> appointIds = appointmentOrders.stream().map(AppointmentOrder::getAppointId).collect(Collectors.toSet());
        List<Appointment> appointments = AppointmentQuery.builder().idIn(appointIds).build().list();
        if (CollKit.isEmpty(appointments)) {
            return;
        }
        // 按主體ID映射,把主體的信息寫到預(yù)約單內(nèi)
        Map<String, Appointment> map = appointments.stream().collect(Collectors.toMap(Appointment::getId, o -> o));
        for (AppointmentOrder appointmentOrder : appointmentOrders) {
            Appointment appointment = map.get(appointmentOrder.getAppointId());
            if (appointment != null) {
                appointmentOrder.setAppointment(appointment);
                appointmentOrder.setAppointName(appointment.getName());
            }
        }
    }


    private void fillAppointmentRecords(Map<String, AppointmentOrder> orderId2appointment) {
        // 查出指定預(yù)約單ID列表的預(yù)約記錄
        List<AppointmentRecord> appointmentRecords = AppointmentRecordQuery.builder().orderIdIn(orderId2appointment.keySet()).fillTimeSchedules(true).build().list();
        if (CollKit.isEmpty(appointmentRecords)) {
            return;
        }
        // 按預(yù)約單ID分組,讓預(yù)約單關(guān)聯(lián)多條預(yù)約記錄
        Map<String, List<AppointmentRecord>> map = appointmentRecords.stream().collect(Collectors.groupingBy(AppointmentRecord::getOrderId));
        for (String id : map.keySet()) {
            orderId2appointment.get(id).setAppointmentRecords(map.get(id));
        }
    }


    private void fillAppointmentComments(List<AppointmentOrder> appointmentOrders) {
        // 查出指定預(yù)約單ID列表的主體評(píng)論
        List<String> orderIds = appointmentOrders.stream().map(AppointmentOrder::getOrderId).collect(Collectors.toList());
        List<AppointmentComment> appointmentComments = AppointmentCommentQuery.builder().orderIdIn(orderIds).build().list();
        if (CollKit.isEmpty(appointmentComments)) {
            return;
        }
        // 按預(yù)約單ID分組,讓預(yù)約單關(guān)聯(lián)首條預(yù)約主體評(píng)論
        Map<String, List<AppointmentComment>> map = appointmentComments.stream().collect(Collectors.groupingBy(AppointmentComment::getOrderId));
        for (AppointmentOrder appointmentOrder : appointmentOrders) {
            appointmentOrder.setAppointmentComment(CollKit.isNotEmpty(map.get(appointmentOrder.getOrderId())) ? map.get(appointmentOrder.getOrderId()).get(0) : null);
        }
    }


}

需要注意的是,fill方法要對(duì)多條結(jié)果進(jìn)行批量處理,如果是單個(gè)結(jié)果,每一條結(jié)果都需要聚合多表數(shù)據(jù),那勢(shì)必會(huì)加大內(nèi)存與數(shù)據(jù)庫(kù)連接的負(fù)擔(dān)。以上預(yù)約單表聚合其他三張表,比SQL的Join多調(diào)了3次查詢,但好在邏輯清晰,減少維護(hù)SQL的負(fù)擔(dān)。

聚合查詢的打開(kāi)方式就很簡(jiǎn)單了,如果前端不傳聚合參數(shù),那么由后端接口來(lái)控制:

// 設(shè)置聚合參數(shù)(或由前端傳入)
query.setFillAppointment(true);
query.setFillAppointmentRecords(true);
query.setFillAppointmentComment(true);
// 分頁(yè)查詢后,會(huì)自動(dòng)調(diào)用fill方法,對(duì)結(jié)果集聚合
Page<AppointmentOrder> page = query.page();
// 調(diào)用聚合方法(以下代碼在D3Boot框架內(nèi)已實(shí)現(xiàn),不需要寫)
appointmentOrderRepository.fill(query, page.getRecords());

我們通過(guò)這種方式來(lái)靈活聚合多表數(shù)據(jù),不僅不用改動(dòng)Controller,還能兼顧多種數(shù)據(jù)聚合的場(chǎng)景,這種數(shù)據(jù)的聚合,不一定要聚合數(shù)據(jù)庫(kù)的數(shù)據(jù),也能聚合第三方接口的數(shù)據(jù)。倉(cāng)庫(kù)接口只關(guān)心需要提供什么數(shù)據(jù)即可,怎么聚合數(shù)據(jù)、數(shù)據(jù)源來(lái)自哪兒,是倉(cāng)庫(kù)實(shí)現(xiàn)要負(fù)責(zé)的事情。

三、寫在最后

目前這種聚合方式也只適用于大部分場(chǎng)景,對(duì)于多張大表的聚合,還得考慮是用數(shù)據(jù)庫(kù)Join還是內(nèi)存Join的方式進(jìn)行,或者在設(shè)計(jì)的時(shí)候就要考慮冗余存而不是范式存。

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

2024-04-30 08:12:05

CRUD方法JavaAC架構(gòu)

2024-04-26 08:58:54

if-else代碼JavaSpring

2022-06-23 07:05:46

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

2016-10-26 09:12:58

2017-08-24 15:02:01

前端增量式更新

2018-04-18 07:34:58

2023-09-17 23:16:46

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

2025-01-27 13:00:00

2020-11-27 14:45:57

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

2019-11-22 09:21:17

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

2016-10-13 10:57:55

phptcp專欄

2021-05-18 06:22:39

CSS 制作波浪技巧

2010-11-22 15:56:34

Mysql多表查詢

2010-10-14 14:28:03

Mysql多表查詢

2017-01-23 11:18:16

戴爾

2009-12-03 10:32:21

2020-09-16 14:01:10

Vue.js項(xiàng)目語(yǔ)言

2024-05-16 08:37:12

FLIPFirst前端動(dòng)畫思維

2013-05-22 15:31:07

AOP的CGlib實(shí)現(xiàn)

2009-06-03 15:38:37

Struts框架RBAC
點(diǎn)贊
收藏

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