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

DDD死黨:?jiǎn)我娌樵兝?/h1>

數(shù)據(jù)庫(kù) MySQL
本文從一個(gè)日常開(kāi)發(fā)場(chǎng)景出發(fā),提出兩個(gè)關(guān)鍵問(wèn)題:代碼過(guò)于繁瑣,容易出錯(cuò),同時(shí)開(kāi)發(fā)效率低下;對(duì)性能設(shè)計(jì)關(guān)注不足,容易遺漏,產(chǎn)生性能問(wèn)題;對(duì)于性能問(wèn)題,從 MySQL B+Tree 進(jìn)行推演,總結(jié)出該場(chǎng)景下的最佳使用實(shí)踐,并將其提取為規(guī)范。

基于索引的單表查詢,是 MySQL 正確打開(kāi)方式!

基于 QueryObject 的聲明式查詢,是簡(jiǎn)單查詢的正確使用方式!

1、應(yīng)用場(chǎng)景

單表查詢?cè)跇I(yè)務(wù)開(kāi)發(fā)中占比最大,是所有 CRUD Boy 的入門必備,所有人在 JavaBean 和 SQL 之間樂(lè)此不疲。

整體架構(gòu)如下圖所示:

這是一個(gè)簡(jiǎn)單的分層架構(gòu),主要有:

  • 接入層:接收用戶或其他服務(wù)的請(qǐng)求,對(duì)參數(shù)進(jìn)行基本驗(yàn)證。
  • 服務(wù)層:執(zhí)行簡(jiǎn)單的業(yè)務(wù)邏輯,比如業(yè)務(wù)驗(yàn)證、數(shù)據(jù)轉(zhuǎn)換、數(shù)據(jù)組裝等。
  • 數(shù)據(jù)訪問(wèn)層。在 ORM 框架基礎(chǔ)之上完成對(duì)數(shù)據(jù)庫(kù)的訪問(wèn)。
  • 數(shù)據(jù)庫(kù)層。負(fù)責(zé)數(shù)據(jù)存儲(chǔ)和查詢。

其中 ORM 框架尤為重要,幫我們完成 對(duì)象 與 關(guān)系數(shù)據(jù) 間的相互轉(zhuǎn)換。因此,不少人認(rèn)為玩好 ORM 就成為了高級(jí)開(kāi)發(fā)人員。而實(shí)際情況是:該部分是最枯燥、最沒(méi)有技術(shù)含量的“技能”。

目前,最常見(jiàn)的 ORM 便是 MyBatis 和 JPA,以一個(gè)簡(jiǎn)單的分頁(yè)查詢 User 為例做一個(gè)簡(jiǎn)短介紹。

按照用戶狀態(tài)分頁(yè)查詢 User 信息:

  • 用戶狀態(tài)和分頁(yè)參數(shù)必填。
  • 其他參數(shù)手機(jī)號(hào)、生日區(qū)間選填。

查詢?nèi)雲(yún)⑷缦拢?/span>

@Data
public class QueryUserByStatus {
    private Integer status;
    private String mobile;
    private Date birthAfter;
    private Date birthBefore;
    private Pageable pageable;
}

接口簽名如下:

Page<User> queryByStatus(QueryUserByStatus queryByStatus);

這個(gè)是最簡(jiǎn)單的 case,分別使用 MyBatis 和 Jpa 進(jìn)行實(shí)現(xiàn)。

(1)MyBatis

MyBatis是一款基于 Java 語(yǔ)言的持久層框架,它為SQL映射、數(shù)據(jù)處理和事務(wù)管理提供了優(yōu)秀的支持。MyBatis已成為使用最廣泛的ORM框架之一,它支持極為靈活的自定義SQL,同時(shí)也提供了與Spring Framework和Spring Boot等流行框架的集成方案,為Java程序員提供了極大的便利。

基于MyBatis實(shí)現(xiàn)的核心代碼如下:

@Autowired
private MyBatisUserMapper userMapper;
public Page<MyBatisUser> queryByStatus(QueryUserByStatus query){
    // 狀態(tài)不填
    if (query.getStatus()  null){
        throw new IllegalArgumentException("status can not null");
    }
    // 分頁(yè)必填
    if (query.getPageable()  null){
        throw new IllegalArgumentException("pageable can not null");
    }
    MyBatisUserExample userExample = new MyBatisUserExample();
    MyBatisUserExample.Criteria criteria = userExample.createCriteria();
    // 添加狀態(tài)過(guò)濾
    criteria.andStatusEqualTo(query.getStatus());
    // 添加手機(jī)號(hào)過(guò)濾
    if (query.getMobile() != null){
        criteria.andMobileEqualTo(query.getMobile());
    }
    // 添加生日過(guò)濾
    if (query.getBirthAfter() != null){
        criteria.andBirthAtGreaterThan(query.getBirthAfter());
    }
    // 添加生日過(guò)濾
    if (query.getBirthBefore() != null){
        criteria.andBirthAtLessThan(query.getBirthBefore());
    }
    // 添加分頁(yè)信息
    userExample.setOffset(query.getPageable().offset());
    userExample.setRows(query.getPageable().getPageSize());
    // 查詢數(shù)據(jù)
    long totalItems = this.userMapper.countByExample(userExample);
    List<MyBatisUser> users = this.userMapper.selectByExample(userExample);
    // 封裝結(jié)果
    return new Page<>(users, query.getPageable(), totalItems);
}

(2)Jpa

JPA是Java Persistence API(Java持久化API)的簡(jiǎn)稱,它是Sun官方提供的一套標(biāo)準(zhǔn)的ORM框架(對(duì)象關(guān)系映射框架)。JPA提供了一種以面向?qū)ο蠓绞絹?lái)管理關(guān)系型數(shù)據(jù)庫(kù)的方法,使開(kāi)發(fā)人員可以使用對(duì)象而不是SQL來(lái)操作數(shù)據(jù)庫(kù)。JPA提供了一套公共的API,使開(kāi)發(fā)人員可以在不同的ORM實(shí)現(xiàn)(如Hibernate、EclipseLink等)中自由切換。

基于Jpa實(shí)現(xiàn)的核心代碼如下:

@Autowired
private JpaUserRepository jpaUserRepository;
public Page<JpaUser> queryByStatus(QueryUserByStatus queryByStatus){
    // 狀態(tài)必填
    if (queryByStatus.getStatus()  null){
        throw new IllegalArgumentException("status can not null");
    }
    // 分頁(yè)必填
    if (queryByStatus.getPageable()  null){
        throw new IllegalArgumentException("pageable can not null");
    }
    // 構(gòu)建分頁(yè)參數(shù)
    Pageable pageable = PageRequest.of(queryByStatus.getPageable().getPageNo(), queryByStatus.getPageable().getPageSize());
    // 構(gòu)建過(guò)濾條件
    Specification<JpaUser> spec = Specification.where((root, query, cb) -> {
        List<Predicate> predicates = Lists.newArrayList();
        // 添加狀態(tài)過(guò)濾
        Predicate statusPredicate = cb.equal(root.get("status"), queryByStatus.getStatus());
        predicates.add(statusPredicate);
        // 添加手機(jī)過(guò)濾
        if (queryByStatus.getMobile() != null){
            Predicate mobilePredicate = cb.equal(root.get("mobile") , queryByStatus.getMobile());
            predicates.add(mobilePredicate);
        }
        // 添加生日過(guò)濾
        if (queryByStatus.getBirthAfter() != null){
            Predicate birthAfterPredicate = cb.greaterThan(root.get("birthAt") , queryByStatus.getBirthAfter());
            predicates.add(birthAfterPredicate);
        }
        // 添加生日過(guò)濾
        if (queryByStatus.getBirthBefore() != null){
            Predicate birthBeforePredicate = cb.lessThan(root.get("birthAt") , queryByStatus.getBirthBefore());
            predicates.add(birthBeforePredicate);
        }
        // 組合過(guò)濾條件
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    });
    // 查詢數(shù)據(jù)
    org.springframework.data.domain.Page<JpaUser> all = this.jpaUserRepository.findAll(spec, pageable);
    // 封裝結(jié)果
    return new Page<>(all.getContent(), queryByStatus.getPageable(), all.getTotalElements());
}

(3)問(wèn)題分析

通常情況下,使用哪個(gè) ORM 框架,都是由公司規(guī)范規(guī)定,一般人沒(méi)辦法左右。但,無(wú)論使用哪個(gè)框架,面對(duì)的問(wèn)題基本是一致的。

這種開(kāi)發(fā)模型,存在以下幾個(gè)問(wèn)題:

  • 過(guò)于繁瑣,開(kāi)發(fā)效率低:一個(gè)簡(jiǎn)單的查詢請(qǐng)求,包括參數(shù)驗(yàn)證、ORM API調(diào)用、數(shù)據(jù)轉(zhuǎn)換等工作,涉及多個(gè)層次多個(gè)類的協(xié)調(diào)一致,常見(jiàn)問(wèn)題包括:
  • 重復(fù)性勞動(dòng):沒(méi)有什么技術(shù)含量,首先是使用 “字段” 或 “屬性” 調(diào)用各種 API,然后是各種類型間的轉(zhuǎn)化,枯燥無(wú)味。
  • 容易出錯(cuò):涉及參數(shù)和字段較多,容易設(shè)置錯(cuò)位,比如參數(shù)設(shè)置錯(cuò)誤、對(duì)象轉(zhuǎn)換時(shí)字段設(shè)置錯(cuò)誤等。
  • 性能瓶頸:實(shí)際開(kāi)發(fā)中,性能瓶頸并沒(méi)有在 ORM 框架本身,主要是對(duì) MySQL 使用不當(dāng),特別是沒(méi)有發(fā)揮索引的優(yōu)勢(shì),常見(jiàn)問(wèn)題包括:
  • 沒(méi)有合適索引:設(shè)計(jì)之初并未考慮索引,或者對(duì)索引缺乏有效的管理。
  • 參數(shù)丟失導(dǎo)致無(wú)法使用索引:參數(shù)丟失導(dǎo)致最左匹配原則被破壞,無(wú)法高效的使用索引。
  • 返回結(jié)果過(guò)多導(dǎo)致性能低下:一次性返回大量數(shù)據(jù),增加 DB 和 應(yīng)用程序的負(fù)載,最終導(dǎo)致性能低下。

2、MySQL 查詢正確打開(kāi)方式

MySQL 常見(jiàn)的查詢優(yōu)化手段非常多:

  1. 索引優(yōu)化:分析表數(shù)據(jù)和查詢需求,創(chuàng)建合適的索引來(lái)提高查詢效率。
  2. SQL語(yǔ)句優(yōu)化:優(yōu)化SQL語(yǔ)句的寫法,避免使用子查詢、聯(lián)合查詢、多層嵌套等耗費(fèi)資源的操作。
  3. 數(shù)據(jù)庫(kù)結(jié)構(gòu)優(yōu)化:合理設(shè)計(jì)數(shù)據(jù)庫(kù)結(jié)構(gòu),避免冗余數(shù)據(jù)以及過(guò)多分表分庫(kù)導(dǎo)致性能低下。
  4. 控制結(jié)果集大小:查詢的結(jié)果集越大,查詢時(shí)間就越長(zhǎng)。盡量限制結(jié)果集大小,避免不必要的計(jì)算。
  5. 數(shù)據(jù)庫(kù)連接池優(yōu)化:通過(guò)優(yōu)化數(shù)據(jù)庫(kù)連接池的配置,避免連接池滿載以及連接超時(shí)等問(wèn)題,提高數(shù)據(jù)庫(kù)處理效率。
  6. 數(shù)據(jù)庫(kù)批量操作優(yōu)化:通過(guò)批量操作來(lái)減少單次與數(shù)據(jù)庫(kù)的交互次數(shù),提高執(zhí)行效率。

在眾多優(yōu)化方式中選擇最主要的一項(xiàng)便是:索引優(yōu)化:

  1. 提升基于 WHERE 條件的查詢性能:在 WHERE 條件中使用了索引,可以更快地定位到匹配行,避免全表掃描。
  2. 提升基于范圍查詢的查詢性能:如果僅需要一個(gè)范圍,而不是整個(gè)表的數(shù)據(jù),索引可以提高查詢效率。
  3. 提升排序和分組查詢性能:索引可以讓 MySQL 更快地執(zhí)行排序和聚合,快速定位數(shù)據(jù),而不是遍歷整個(gè)表。

(1)B+Tree 與 高效查詢

B+Tree 在 MySQL 中極為重要,它既是一種數(shù)據(jù)的組織結(jié)構(gòu),比如聚簇索引。又是查詢優(yōu)化最重要的一種手段,比如非聚簇索引。

B+Tree

B+Tree 在 MySQL 中是如此重要,它是 MySQL 使用的默認(rèn)索引。B+Tree 索引不僅可以加速單個(gè)鍵值查詢,還可以支持范圍查找并為查詢結(jié)果排序。此外,B+Tree 還可以支持高效的插入和刪除操作,當(dāng)在一個(gè) B+Tree 索引中插入或刪除記錄時(shí),B+Tree 索引通過(guò)特定規(guī)則進(jìn)行拆分和合并來(lái)實(shí)現(xiàn)重新平衡。
在 MySQL 中,B+Tree 索引不僅適用于普通表,還適用于主鍵索引、唯一索引、輔助索引等。因此,了解 B+Tree 索引的設(shè)計(jì)和原理對(duì)于開(kāi)發(fā)高效、可擴(kuò)展的 MySQL 應(yīng)用程序至關(guān)重要。

以下是一個(gè) B+Tree 的示意圖:

B+Tree作為一種數(shù)據(jù)組織方式,有以下幾個(gè)特點(diǎn):

  • 非葉子節(jié)點(diǎn)只存儲(chǔ)關(guān)鍵字和頁(yè)碼,而不保存數(shù)據(jù)。這也是B+Tree和B-Tree的主要區(qū)別,這種特性使得B+Tree可以更快的查找特定關(guān)鍵字。
  • 葉子節(jié)點(diǎn)包含所有數(shù)據(jù)和關(guān)鍵字,形成一個(gè)有序鏈表。這種結(jié)構(gòu)使得B+Tree在范圍查詢時(shí)更高效。
  • 支持高效范圍查找,基于在葉子節(jié)點(diǎn)形成的有序鏈表,可以更快地查找滿足查詢條件的數(shù)據(jù)。
  • 支持快速的插入和刪除,基本上所有的操作都可以在O(log n)的時(shí)間復(fù)雜度內(nèi)完成。
  • 分級(jí)結(jié)構(gòu)可以支持多級(jí)索引查找,充分利用磁盤I/O緩存和預(yù)取來(lái)提高查詢效率。

索引

MySQL 中最常見(jiàn)的索引包括:

  • 聚簇索引(主鍵索引):一個(gè)表只能有一個(gè)聚簇索引,對(duì)應(yīng)表的數(shù)據(jù)存儲(chǔ)方式,即數(shù)據(jù)按照聚簇索引來(lái)排序和存儲(chǔ),葉節(jié)點(diǎn)存儲(chǔ)了完整的數(shù)據(jù)行。在使用聚簇索引進(jìn)行查找時(shí),只需查找一次聚簇索引就能找到需要的數(shù)據(jù)行。
  • 非聚簇索引(輔助索引):一個(gè)表可以有多個(gè)非聚簇索引,節(jié)點(diǎn)存儲(chǔ)了完整的索引和指向數(shù)據(jù)行信息(指針或主鍵)。查詢時(shí)需要查找兩次索引,第一次查詢索引信息,第二次查找數(shù)據(jù)行。

如下圖所示:

這種先查輔助索引再查主鍵索引的行為,我們稱之為“回表”。

看一個(gè)回表的例子:

table: id, category, publisher, status, title\
index: idx_categity(category,status)

查詢語(yǔ)句:select * from tb_news where category = 2 and publisher = 14

執(zhí)行邏輯如圖所示:

  • 索引中存在 category 列,category = 2 的過(guò)濾在引擎層完成,返回?cái)?shù)據(jù)的主鍵。
  • 引擎完成 category = 2 過(guò)濾后,需要 publisher 和全部數(shù)據(jù),所以進(jìn)行回表操作。
  • 從主鍵索引表中獲取全部數(shù)據(jù),在內(nèi)存中執(zhí)行 publisher = 14 的過(guò)濾。
  • 將滿足條件的數(shù)據(jù)放入到 Result 中進(jìn)行返回。

一般情況下,回表的性能損失還是可接受的,可以在發(fā)現(xiàn)問(wèn)題后進(jìn)行處理。可將更多精力放在提升研發(fā)效率上。

(2)高性能查詢

基于 B+Tree 數(shù)據(jù)結(jié)構(gòu)的特點(diǎn),在以下場(chǎng)景可以高效使用索引:

  • 全值匹配:與索引中的所有列進(jìn)行匹配;
  • 匹配最左前綴:并非與索引中的所有列進(jìn)行匹配,從索引左側(cè)進(jìn)行匹配。
  • 匹配列前綴:匹配某一列的開(kāi)頭部分(like ‘a(chǎn)aa%’)。
  • 匹配范圍值: 大于、等于、小于等。
  • 精確匹配列然后范圍匹配:先精確匹配,然后進(jìn)行范圍匹配。
  • 只訪問(wèn)索引查詢:如果索引中存在查詢所需所有數(shù)據(jù),就沒(méi)有必要追溯原數(shù)據(jù)。
  • 支持查詢中的order by、group by:order by、group by 與 where 條件組合,如果符合最左匹配,及可提升性能。

以下幾種情況無(wú)法使用索引:

  • 不是從最左列開(kāi)始查詢,無(wú)法使用索引。
  • 不能跳過(guò)索引中的列對(duì)后面的列進(jìn)行查詢。
  • 如果索引使用范圍查詢,則后面所有列無(wú)法使用索引進(jìn)行優(yōu)化。

(3)查詢規(guī)范

在了解 MySQL B+Tree 的內(nèi)部實(shí)現(xiàn)之后,可以推導(dǎo)出一套規(guī)范,來(lái)對(duì)查詢性能進(jìn)行保障。

原則

  • 僅使用 MySQL 的單表查詢,避免多表 Join 引入的性能問(wèn)題(多表查詢解決方案見(jiàn):內(nèi)存Join)。
  • 每個(gè)查詢,必須有對(duì)應(yīng)的索引對(duì)性能進(jìn)行保障,也就是所有的查詢必須走索引。
  • 謹(jǐn)慎處理入?yún)⒑头祷刂怠?/li>
  1. 對(duì)入?yún)⑦M(jìn)行嚴(yán)格驗(yàn)證,避免因?yàn)閰?shù)丟失或參數(shù)過(guò)多造成的性能問(wèn)題。
  2. 對(duì)返回值進(jìn)行驗(yàn)證,避免一次性返回過(guò)多數(shù)據(jù)操作性能問(wèn)題。

規(guī)范

對(duì)于一個(gè)查詢請(qǐng)求,需要具備:

  • 統(tǒng)一使用 Query Object 模式,對(duì)入?yún)⑦M(jìn)行封裝,以便接口的升級(jí)和擴(kuò)展。
  • 每一組查詢,可以存在: get(單條返回值)、list(多條返回值)、count(統(tǒng)計(jì)計(jì)數(shù))、page(分頁(yè))開(kāi)頭的多個(gè)方法,操作后面緊跟 By + 維度。
  • 維度結(jié)構(gòu)應(yīng)該與表的索引結(jié)構(gòu)保持一致,以保障所有的查詢,都能應(yīng)用索引。
  • 索引維度體現(xiàn)在方法簽名中,并且保障滿足最左匹配原則。
  • 多維索引,可以基于最左匹配原則生成多組方法;索引列(A,B),可以生成 A、AandB 兩組方法。

假如在order表中存在一個(gè)索引(user_id, status),那么可以存在以下查詢:

// 可以支持多組高效查詢
// User維度查詢對(duì)象
@Data
public class QueryOrderByUser {
    // user id 不能為 null,不然無(wú)法使用索引
    @NotNull
    private Long userId;
    private Integer status;
    private Pageable pageable;
}
// User 和 Status 維度查詢
@Data
public class QueryOrderByUserAndStatus {
    // user id 不能為 null,不然無(wú)法使用索引
    @NotNull
    private Long userId;
    // status 不能為 null,不然無(wú)法使用索引
    @NotNull
    private Integer status;
    private Pageable pageable;
}
// 查詢服務(wù)如下
public interface OrderService {
    // User 維度查詢
    List<Order> listByUser(QueryOrderByUser query);
    Long countByUser(QueryOrderByUser query);
    Page<Order> pageByUser(QueryOrderByUser query);
    // User 和 Status 維度查詢
    List<Order> listByUserAndStatus(QueryOrderByUserAndStatus query);
    Long countByUserAndStatus(QueryOrderByUserAndStatus query);
    Page<Order> pageByUserAndStatus(QueryOrderByUserAndStatus query);
}

這樣便可以在性能和擴(kuò)展性間找到一個(gè)良好的平衡點(diǎn)。

  • 性能。由 MySQL 的索引進(jìn)行保障,可能不是最優(yōu)解(存在回表)但絕對(duì)不是最差情況。
  • 擴(kuò)展性。默認(rèn)查詢維度(get、list、count、page)基本能滿足日常業(yè)務(wù)開(kāi)發(fā);查詢條件也可基于 Query Object 進(jìn)行擴(kuò)展;

3、框架與標(biāo)準(zhǔn)化

我們需要一個(gè)框架,在滿足原則和規(guī)范前提下,靈活的定制簡(jiǎn)單數(shù)據(jù)查詢,但又不能過(guò)于靈活,需要對(duì)使用方式進(jìn)行嚴(yán)格限制。

靈活定制,快速開(kāi)發(fā),提升效率,降低bug;對(duì)使用進(jìn)行限制,是為了將掌控權(quán)控制在開(kāi)發(fā),不會(huì)因?yàn)槭褂貌划?dāng)造成線上問(wèn)題。因此,對(duì)框架有如下要求:

  • 支持靈活的查詢定義,無(wú)需手寫 SQL。
  • 支持常見(jiàn)的查詢,包括過(guò)濾、排序、分頁(yè)等。
  • 多 ORM 支持,提供對(duì) MyBatis 和 Jpa 框架支持。

框架整體流程如下:

該模式下,開(kāi)發(fā)查詢功能只需:

  • 根據(jù)業(yè)務(wù)需求定義 QueryObject,主要包括過(guò)濾、排序、分頁(yè)等。
  • 使用 QueryObject 調(diào)用 QueryRepository 相關(guān)接口完成查詢,常見(jiàn)功能包括:?jiǎn)螚l查詢、列表查詢、計(jì)數(shù)查詢、分頁(yè)查詢等。

只需在QueryObject上進(jìn)行定義,無(wú)需編寫 SQL,由框架對(duì) QueryObject 進(jìn)行解析,完成動(dòng)態(tài)查詢。

核心功能全部在 QueryRepository 中,其核心流程如下:

流程如下:

  • 驗(yàn)證參數(shù):基于 Spring Validate 框架完成基本的參數(shù)校驗(yàn)。
  • 解析QueryObject:從 QueryObject 中提取信息,轉(zhuǎn)為為 ORM 的查詢對(duì)象。
  • 設(shè)置最大返回值:【可配】設(shè)置最大返回值,避免結(jié)果太多造成性能低下。
  • 執(zhí)行查詢:調(diào)用 ORM 框架的查詢接口執(zhí)行查詢命令。
  • 處理查詢結(jié)果:【可配】對(duì)查詢結(jié)果進(jìn)行處理打印日志 or 異常中斷。

為了支持多個(gè) ORM 框架,整體結(jié)構(gòu)設(shè)計(jì)如下:

核心模塊包括:

  • API:提供統(tǒng)一的接口和配置能力,對(duì)使用方式進(jìn)行規(guī)范;。
  • MyBatis 實(shí)現(xiàn):基于 MyBatis 實(shí)現(xiàn) API 中定義的全部功能,完成與 MyBatis 框架的集成。
  • JPA 實(shí)現(xiàn):基于 JPA 實(shí)現(xiàn) API 中定義的全部功能,完成與 JPA 框架的集成。

(1)統(tǒng)一 API

提供統(tǒng)一的接口和配置能力,對(duì)使用方式進(jìn)行規(guī)范。其中包括兩大部分:

  1. 注解:使用注解在 QueryObject 的字段上添加配置信息,使其具備過(guò)濾能力;
  2. QueryRepository接口:提供一組標(biāo)準(zhǔn)的 API,實(shí)現(xiàn)常見(jiàn)的 get、list、count 和 page 查詢;

注解

注解配置于 QueryObject 之上,以聲明化的方式,對(duì)過(guò)濾功能進(jìn)行描述。

常見(jiàn)注解如下:

注解

含義

FieldEqualTo

等于

FieldGreaterThan

大于

FieldGreaterThanOrEqualTo

大于等于

FieldIn

in 操作

FieldIsNull

是否為 null

FieldLessThan

小于

FieldLessThanOrEqualTo

小于等于

FieldNotEqualTo

不等于

FieldNotIn

not in

EmbeddedFilter

嵌入查詢對(duì)象

針對(duì)之前的 User 查詢實(shí)例,對(duì)應(yīng)的 查詢對(duì)象定義如下:

@Data
public class QueryUserByStatus {
    // 狀態(tài)相等
    @FieldEqualTo("status")
    @NotNull
    private Integer status;
    // 手機(jī)號(hào)相等
    @FieldEqualTo("mobile")
    private String mobile;
    // 生日比該值大
    @FieldGreaterThan("birthAt")
    private Date birthAfter;
    // 生日比該值小
    @FieldLessThan("birthAt")
    private Date birthBefore;
    // 自動(dòng)具備分頁(yè)能力
    private Pageable pageable;
}

接口

有了 QueryObject 之后,需要一組查詢 API 以滿足各個(gè)場(chǎng)景需求,標(biāo)準(zhǔn)的 API 接口定義如下:

public interface QueryObjectRepository<E> {
    // 檢查查詢對(duì)象的有效性
    void checkForQueryObject(Class cls);
    // 單條查詢
    <Q> E get(Q query);
    // 分頁(yè)查詢
    default <Q, R> R get(Q query, Function<E, R> converter) {
        E entity = this.get(query);
        return entity  null ? null : converter.apply(entity);
    }
    // 統(tǒng)計(jì)查詢
    <Q> Long countOf(Q query);
    // 列表查詢
    default <Q, R> List<R> listOf(Q query, Function<E, R> converter) {
        List<E> entities = this.listOf(query);
        return CollectionUtils.isEmpty(entities) ? Collections.emptyList() : (List)entities.stream().filter(Objects::nonNull).map(converter).filter(Objects::nonNull).collect(Collectors.toList());
    }
    // 列表查詢
    <Q> List<E> listOf(Q query);
    // 分頁(yè)查詢
    default <Q, R> Page<R> pageOf(Q query, Function<E, R> converter) {
        Page<E> entityPage = this.pageOf(query);
        return entityPage  null ? null : entityPage.convert(converter);
    }
    // 分頁(yè)查詢
    <Q> Page<E> pageOf(Q query);
}

集成示例

有了 QueryObject 和 API 之后,便可以輕松完成各種查詢:

public class SingleQueryService {
    @Autowired
    private QueryObjectRepository<JpaUser> repository;
    public List<JpaUser> listByStatus(QueryUserByStatus query){
        return repository.listOf(query);
    }
    public Long countByStatus(QueryUserByStatus query){
        return this.repository.countOf(query);
    }
    public Page<JpaUser> pageByStatus(QueryUserByStatus query){
        return this.repository.pageOf(query);
    }
}

萬(wàn)事具備,只欠最后的 QueryObjectRepository 實(shí)現(xiàn),針對(duì)不同的 ORM 提供不同的實(shí)現(xiàn)。

(2)MyBatis 支持

基于 MyBatis Generator 的 Example 機(jī)制實(shí)現(xiàn),需要配置相關(guān)的 Generator 以生成 EntityExample 對(duì)象。

直接繼承BaseReflectBasedExampleSingleQueryRepository,注入 Mapper 實(shí)現(xiàn),指定好 Example 類即可,具體如下:

@Service
public class MyBatisBasedQueryRepository extends BaseReflectBasedExampleSingleQueryRepository {
    // 注入 MyBatis 的 Mapper 類
    public MyBatisBasedQueryRepository(MyBatisUserMapper mapper) {
        // 指定查詢所需的 Example 類
        super(mapper, MyBatisUserExample.class);
    }
}

整體架構(gòu)如下:

核心流程如下:

  • ExampleConverter 將輸入的 QueryObject 轉(zhuǎn)換為 XXXExample 實(shí)例。
  • 使用 XXXExample 實(shí)例 調(diào)用 XXXMapper 的 selectByExample 方法獲取返回值。
  • 返回值通過(guò) ResultConverter 將 Entity 轉(zhuǎn)換為最終結(jié)果。

其中,從 QueryObject 到 Example 實(shí)例的轉(zhuǎn)換為框架的核心,主要包括如下幾部分:

  • Pageable。從 QueryObject 中讀取 Pageable 屬性,并設(shè)置 Example 對(duì)象的 offset 和 rows 屬性。
  • Sort。從 QueryObject 中讀取 Sort 屬性,并設(shè)置 Example 對(duì)象的 orderByClause 屬性。
  • 過(guò)濾注解。遍歷 QueryObject 中屬性,根據(jù)注解查找到注解處理器,由注解處理器為 Example 添加 Criteria,以進(jìn)行數(shù)據(jù)過(guò)濾。

(3)Jpa 支持

基于 JPA 框架的 JpaSpecificationExecutor 實(shí)現(xiàn),EntityRepository 需繼承 JpaSpecificationExecutor 接口。

直接繼承BaseSpecificationQueryObjectRepository,注入 JpaSpecificationExecutor 和 實(shí)體對(duì)象即可,具體如下:

public class JpaBasedQueryRepository extends BaseSpecificationQueryObjectRepository {
    // 注入 JpaUserRepository 和 specificationConverterFactory(框架自動(dòng)生成)
    public JpaBasedQueryRepository(JpaUserRepository userRepository,
                                   SpecificationConverterFactory
                                           specificationConverterFactory) {
        // 指定實(shí)體對(duì)象 JpaUser
        super(userRepository, JpaUser.class, specificationConverterFactory);
    }
}

整體架構(gòu)如下:

核心流程如下:

  • SpecificationConverter 將輸入的 QueryObject 轉(zhuǎn)換為 Specification、Pageable、Sort等實(shí)例。
  • 使用 SpecificationExecutor 實(shí)例的查詢方法獲取返回值。
  • 返回值通過(guò) ResultConverter 將 Entity 轉(zhuǎn)換為最終結(jié)果。

其中,從 QueryObject 到相關(guān)輸入?yún)?shù)的轉(zhuǎn)換為框架的核心,主要包括如下幾部分:

  • Pageable。從 QueryObject 中讀取 Pageable 屬性,并轉(zhuǎn)化為 Spring data 的 Pageable 實(shí)例。
  • Sort。從 QueryObject 中讀取 Sort 屬性,并轉(zhuǎn)換為Spring data 的 Sort 實(shí)例。
  • 過(guò)濾注解。遍歷 QueryObject 中屬性,根據(jù)注解查找到注解處理器,由注解處理器將其轉(zhuǎn)化為 Predicate 實(shí)例,最終封裝為 Specification 示例。

4、小結(jié)

本文從一個(gè)日常開(kāi)發(fā)場(chǎng)景出發(fā),提出兩個(gè)關(guān)鍵問(wèn)題:

  • 代碼過(guò)于繁瑣,容易出錯(cuò),同時(shí)開(kāi)發(fā)效率低下。
  • 對(duì)性能設(shè)計(jì)關(guān)注不足,容易遺漏,產(chǎn)生性能問(wèn)題。

對(duì)于性能問(wèn)題,從 MySQL B+Tree 進(jìn)行推演,總結(jié)出該場(chǎng)景下的最佳使用實(shí)踐,并將其提取為規(guī)范。

對(duì)于代碼繁瑣問(wèn)題,提出通過(guò)在 QueryObject 上增加注解的方式來(lái)實(shí)現(xiàn)簡(jiǎn)單查詢。

兩者相結(jié)合,便形成了 Single Query 框架:

  • 提供一套注解,應(yīng)用于 QueryObject 之上,以聲明化的方式完成查詢定義。
  • 提供一套API,以 QueryObject 作為參數(shù),提供 單條、批量、統(tǒng)計(jì)、分頁(yè)等查詢。
  • 提供MyBatis和Jpa兩套實(shí)現(xiàn),快速實(shí)現(xiàn) QueryObjectRepository,以提升開(kāi)發(fā)速度。
責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2023-12-14 10:03:52

內(nèi)存數(shù)據(jù)

2023-11-27 09:19:04

Memory@AliasFor

2021-08-17 10:39:54

SQL Server數(shù)據(jù)庫(kù)優(yōu)化

2017-08-03 16:31:43

微服務(wù)架構(gòu)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)數(shù)字化

2024-04-03 09:12:03

PostgreSQL索引數(shù)據(jù)庫(kù)

2024-12-24 15:25:06

2024-01-17 09:00:00

大型語(yǔ)言模型機(jī)器學(xué)習(xí)向量搜索引擎

2025-01-26 10:10:30

2024-06-26 09:51:23

2017-09-04 17:34:17

HBASESolr查詢

2024-08-05 01:29:47

MVC架構(gòu)模式分離模型

2015-06-15 12:58:39

大數(shù)據(jù)大數(shù)據(jù)查詢

2024-03-01 13:58:13

火山引擎Serverless云原生

2013-12-16 10:20:48

MySQL數(shù)據(jù)庫(kù)

2010-09-25 16:42:45

sql語(yǔ)句

2024-03-06 09:30:13

PostgreSQL子查詢視圖

2021-09-08 09:22:23

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)

2017-08-18 14:47:31

DDD微服務(wù)架構(gòu)
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 日本黄色免费大片 | 久久精品国产一区二区电影 | 人人种亚洲 | 日韩精品一区二区三区在线播放 | 国产成人免费 | 欧美日韩国产在线观看 | 成人久久久 | 国产免费一区 | 麻豆国产一区二区三区四区 | 一级黄色日本片 | 午夜影院在线观看免费 | 久久99国产精品 | 影音先锋成人资源 | 特级黄色毛片 | 仙人掌旅馆在线观看 | 91高清在线观看 | 国产精品九九九 | 大陆一级毛片免费视频观看 | 日本91av视频 | 狠狠狠干 | 国产精品一区二区三级 | 成人亚洲综合 | 久久国产精品99久久久久久丝袜 | 亚洲综合色视频在线观看 | 亚洲国产在 | 亚洲综合大片69999 | 久久国产综合 | 雨宫琴音一区二区在线 | www.日本三级 | 国产96在线 | 天堂综合网 | 欧美黄色一区 | 久久亚洲一区二区三区四区 | 亚洲一区二区三区在线播放 | 国产在线a | 亚洲精品福利视频 | 亚洲视频三区 | 99视频免费| 国产一区二区在线免费观看 | hsck成人网| 一区二区三区国产视频 |