成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一種優雅實現多表查詢的新思路

數據庫
AC架構不是銀彈,不適用于所有場景。對于需要精細化管理接口的業務,還是要拆開一個個接口去寫;而對于小而美的微服務、單表CRUD比較多的管理后臺,采用AC架構可以節省大量重復性編碼。

哈嘍,各位代碼戰士們,我是Jensen,一個夢想著和大家一起在代碼的海洋里遨游,順便撿起那些散落的知識點的程序員小伙伴。

上一篇文章有一些小伙伴在吐槽我的AC架構:

這里我統一補充:AC架構不是銀彈,不適用于所有場景。對于需要精細化管理接口的業務,還是要拆開一個個接口去寫;而對于小而美的微服務、單表CRUD比較多的管理后臺,采用AC架構可以節省大量重復性編碼。

也有很多小伙伴私信我:使用AC架構怎么解決聯表查詢問題?本文將為大家揭曉。

本文涉及技術點:存儲方式、內存聚合。

一、冗余存還是范式存

在數據庫設計中,冗余存儲(Redundant Storage)和范式存儲(Normal Form Storage)是兩種不同的數據組織方式,它們各自有不同的優缺點。

冗余存儲的優缺點:

優點:

  • 查詢性能:冗余存儲可以通過減少連接操作來提高查詢性能,因為所需數據已經被存儲在了同一個地方。
  • 減少I/O:在查詢時,可以減少磁盤I/O操作,因為所有數據都在同一個表中。
  • 簡化應用邏輯:應用層不需要處理復雜的多表關聯,簡化了應用邏輯。

缺點:

  • 數據不一致:冗余數據可能導致數據不一致性問題,特別是在數據更新時,需要確保所有冗余的副本都被正確更新。
  • 存儲空間:冗余存儲會占用更多的存儲空間,因為相同的數據在多個地方被存儲。
  • 維護困難:隨著數據量的增加,冗余數據的維護變得更加困難,任何結構變更都可能涉及到多個表的修改。

范式存儲的優缺點:

優點:

  • 數據一致性:范式化減少了數據冗余,有助于保持數據一致性。
  • 存儲效率:通過消除重復數據,可以節省存儲空間。
  • 數據完整性:范式化有助于實施數據完整性約束,如實體完整性、參照完整性等。

缺點:

  • 查詢性能:范式化可能導致查詢性能下降,因為需要執行多表連接來獲取完整數據。
  • 增加I/O:多表連接可能會增加磁盤I/O操作,特別是在涉及大型表的情況下。
  • 復雜查詢:應用層可能需要編寫更復雜的SQL查詢來處理多表關聯。

我們再復習一下大學老師教過的數據庫范式(Normal Forms):

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

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

在實際應用中,完全遵循范式化可能會導致查詢性能問題,因此通常會根據實際情況進行適度的反范式化(Denormalization),即有意引入一些冗余來優化性能。

設計數據庫時,需要在數據一致性、存儲效率和查詢性能之間做出權衡。

二、走進“聚合”

“聚合”二字經常出現在程序員之間、程序員與產品經理的對話中,雖然產品不懂技術語言,他們只管給客戶實現特定需求,為技術提供產品原型,但是技術比較關心到底是冗余存還是范式存。

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

隨著業務量越來越大,工程里的SQL會滿天飛,并且SQL會寫得越來越復雜,對維護SQL的人簡直是噩耗!

這意味著什么?代碼寫了兩三年后,SQL已經很難維護了,沒人敢動,每次看SQL都特別費勁,還不知道哪一天會出現慢SQL。

其實除了寫SQL語句,我們還能通過倉庫實現層做數據聚合,這就是所謂的“內存Join”,把數據庫壓力轉移到了不要錢的CPU與內存中,具體方法如下:

  • 查詢參數Query對象添加Boolean fillXxx字段,用于控制是否要聚合。
  • 倉庫實現XxxRepositoryImpl實現fill(Query query, List<Model> models)方法,models就是查詢出主表的結果集,通過query.fillXxx=true控制聚合其他表的數據。

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

下面我們看看一個真實案例,預約單倉庫實現,聚合預約主體、預約記錄、預約主體評價:

/**
 * 預約單倉庫實現
 */
@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()) {
            // 聚合預約主體
            this.fillAppointment(appointmentOrders);
        }
        if (query.getFillAppointmentRecords()) {
            // 聚合預約記錄列表
            this.fillAppointmentRecords(orderId2appointment);
        }
        if (query.getFillAppointmentComment()) {
            // 聚合預約主體評論
            this.fillAppointmentComments(appointmentOrders);
        }
    }


    private void fillAppointment(List<AppointmentOrder> appointmentOrders) {
        // 通過預約單的主體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映射,把主體的信息寫到預約單內
        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) {
        // 查出指定預約單ID列表的預約記錄
        List<AppointmentRecord> appointmentRecords = AppointmentRecordQuery.builder().orderIdIn(orderId2appointment.keySet()).fillTimeSchedules(true).build().list();
        if (CollKit.isEmpty(appointmentRecords)) {
            return;
        }
        // 按預約單ID分組,讓預約單關聯多條預約記錄
        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) {
        // 查出指定預約單ID列表的主體評論
        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;
        }
        // 按預約單ID分組,讓預約單關聯首條預約主體評論
        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方法要對多條結果進行批量處理,如果是單個結果,每一條結果都需要聚合多表數據,那勢必會加大內存與數據庫連接的負擔。以上預約單表聚合其他三張表,比SQL的Join多調了3次查詢,但好在邏輯清晰,減少維護SQL的負擔。

聚合查詢的打開方式就很簡單了,如果前端不傳聚合參數,那么由后端接口來控制:

// 設置聚合參數(或由前端傳入)
query.setFillAppointment(true);
query.setFillAppointmentRecords(true);
query.setFillAppointmentComment(true);
// 分頁查詢后,會自動調用fill方法,對結果集聚合
Page<AppointmentOrder> page = query.page();
// 調用聚合方法(以下代碼在D3Boot框架內已實現,不需要寫)
appointmentOrderRepository.fill(query, page.getRecords());

我們通過這種方式來靈活聚合多表數據,不僅不用改動Controller,還能兼顧多種數據聚合的場景,這種數據的聚合,不一定要聚合數據庫的數據,也能聚合第三方接口的數據。倉庫接口只關心需要提供什么數據即可,怎么聚合數據、數據源來自哪兒,是倉庫實現要負責的事情。

三、寫在最后

目前這種聚合方式也只適用于大部分場景,對于多張大表的聚合,還得考慮是用數據庫Join還是內存Join的方式進行,或者在設計的時候就要考慮冗余存而不是范式存。

責任編輯:姜華 來源: 架構師修行錄
相關推薦

2024-04-30 08:12:05

CRUD方法JavaAC架構

2024-04-26 08:58:54

if-else代碼JavaSpring

2022-06-23 07:05:46

跳板機服務器PAM

2016-10-26 09:12:58

2017-08-24 15:02:01

前端增量式更新

2023-09-17 23:16:46

緩存數據庫

2018-04-18 07:34:58

2025-01-27 13:00:00

2020-11-27 14:45:57

開發服務器代碼

2016-10-13 10:57:55

phptcp專欄

2019-11-22 09:21:17

技術研發數據

2021-05-18 06:22:39

CSS 制作波浪技巧

2017-01-23 11:18:16

戴爾

2009-12-03 10:32:21

2010-10-14 14:28:03

Mysql多表查詢

2010-11-22 15:56:34

Mysql多表查詢

2020-09-16 14:01:10

Vue.js項目語言

2024-05-16 08:37:12

FLIPFirst前端動畫思維

2015-05-07 14:24:36

everRun

2009-06-03 15:38:37

Struts框架RBAC
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美另类视频在线 | 日韩久久久久 | 国产日韩欧美在线播放 | 国产精品a久久久久 | 日本韩国欧美在线观看 | 亚洲精品一区二 | 中文字幕在线免费观看 | 久久久久久蜜桃一区二区 | 欧美在线天堂 | 中文字幕乱码亚洲精品一区 | 国产成人精品一区二 | 中文字幕日韩一区二区 | 性做久久久久久免费观看欧美 | 午夜免费观看网站 | 日本精品一区 | 亚洲人人 | 中国免费黄色片 | 成人在线精品 | 亚洲激情综合 | 国产精品毛片一区二区在线看 | 日韩激情在线 | 中文字幕视频一区二区 | 一区二区精品 | 国产在线色| 自拍偷拍第1页 | 国产成人一区 | 成人精品在线 | 亚洲第1页| 最新中文字幕第一页视频 | 亚洲高清免费 | 欧美精品一区二区三区四区五区 | 91精品麻豆日日躁夜夜躁 | 国产一级视频在线观看 | 久久久免费毛片 | 国产欧美一区二区三区久久人妖 | 国产精品一区二区三区在线播放 | 亚洲成人av在线播放 | heyzo在线 | 天天拍天天操 | 中文字幕一页二页 | 国产91精品久久久久久久网曝门 |