戰(zhàn)指南:從默認(rèn)倉(cāng)儲(chǔ)到自定義倉(cāng)儲(chǔ)與類型推斷)
后端【免費(fèi)下載鏈接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.項(xiàng)目地址https://gitcode.com/gh_mirrors/mi/mikro-orm點(diǎn)擊查看免費(fèi)下載EntityRepository 是 MikroORM 在 EntityManager 之上提供的薄封裝層它天然攜帶實(shí)體類型信息免去每次find/findOne都要重復(fù)傳入實(shí)體類的負(fù)擔(dān)同時(shí)是擴(kuò)展查詢邏輯如自定義方法、批量查詢、領(lǐng)域邏輯的標(biāo)準(zhǔn)切入點(diǎn)。本文基于 MikroORM 5.9 官方文檔與倉(cāng)庫(kù)源碼系統(tǒng)講解默認(rèn)倉(cāng)儲(chǔ)的用法、自定義倉(cāng)儲(chǔ)的注冊(cè)與類型推斷、全局基類倉(cāng)儲(chǔ)的搭建以及 v6 中從倉(cāng)儲(chǔ)上移除持久化方法的緣由與替代方案幫助你在實(shí)際項(xiàng)目中正確選用倉(cāng)儲(chǔ)層。EntityRepository攜帶實(shí)體類型的 EntityManager 代理官方文檔將 EntityRepository 定義為EntityManager 之上的一層薄封裝thin layers on top ofEntityManager默認(rèn)實(shí)現(xiàn)只是把調(diào)用轉(zhuǎn)發(fā)給底層的 EntityManager 實(shí)例。從源碼看packages/core/src/entity/EntityRepository.ts 的構(gòu)造函數(shù)只接收兩個(gè)成員export class EntityRepositoryEntity extends object { constructor( protected readonly em: EntityManager, protected readonly entityName: EntityNameEntity, ) {} // ... }這意味著每個(gè)倉(cāng)儲(chǔ)實(shí)例都綁定了一個(gè)具體的實(shí)體類型entityName因此調(diào)用查詢方法時(shí)不再需要像em.find(Book, ...)那樣重復(fù)傳入實(shí)體類const booksRepository em.getRepository(Book); const books await booksRepository.find({ author: ... }, { populate: [author], limit: 1, offset: 2, orderBy: { title: QueryOrder.DESC }, }); console.log(books); // Book[]上面的調(diào)用與em.find(Book, { author: ... }, { ... })完全等價(jià)——從 EntityRepository.find 的實(shí)現(xiàn)可以看到它只是把this.entityName連同where與options一并轉(zhuǎn)發(fā)給this.getEntityManager().find(...)。populate、limit、offset、orderBy等選項(xiàng)與 EntityManager 層面的語(yǔ)義完全一致populate用于批量加載關(guān)聯(lián)orderBy配合QueryOrder枚舉控制排序方向。倉(cāng)儲(chǔ)的獲取與緩存em.getRepository()在 EntityManager.ts 中的實(shí)現(xiàn)要點(diǎn)如下getRepositoryEntity extends object, Repository extends EntityRepositoryEntity EntityRepositoryEntity( entityName: EntityNameEntity, ): GetRepositoryEntity, Repository { const meta this.metadata.get(entityName); if (!this.#repositoryMap.has(meta)) { const RepositoryClass this.config.getRepositoryClass(meta.repository) as ConstructorEntityRepositoryany; this.#repositoryMap.set(meta, new RepositoryClass(this, entityName)); } return this.#repositoryMap.get(meta) as GetRepositoryEntity, Repository; }倉(cāng)儲(chǔ)實(shí)例按元數(shù)據(jù)緩存于#repositoryMap同一個(gè)實(shí)體多次調(diào)用getRepository()返回的是同一個(gè)實(shí)例實(shí)例化時(shí)通過(guò)Configuration.getRepositoryClass(meta.repository)決定使用哪個(gè)倉(cāng)儲(chǔ)類其解析順序在 Configuration.ts 中清晰可見實(shí)體定義的repository回調(diào)優(yōu)先 → 全局配置的entityRepository次之 → 最后回退到平臺(tái)默認(rèn)倉(cāng)儲(chǔ)類this.#platform.getRepositoryClass()。這正是后面實(shí)體級(jí)自定義倉(cāng)儲(chǔ)與全局基類倉(cāng)儲(chǔ)兩條注冊(cè)路徑的底層依據(jù)。關(guān)于刷新倉(cāng)儲(chǔ)的澄清文檔特別強(qiáng)調(diào)不存在刷新倉(cāng)儲(chǔ)flushing repository這一概念。倉(cāng)儲(chǔ)上并沒有獨(dú)立的持久化上下文flush始終是針對(duì)整個(gè) Unit of Work 的。也就是說(shuō)無(wú)論你在多少個(gè)倉(cāng)儲(chǔ)上做了修改一次em.flush()會(huì)把當(dāng)前上下文identity map中所有待持久化的變更一次性寫入數(shù)據(jù)庫(kù)而不是只刷新某一個(gè)實(shí)體。這一點(diǎn)在后續(xù)移除的方法一節(jié)中體現(xiàn)得更徹底。自定義倉(cāng)儲(chǔ)擴(kuò)展查詢與領(lǐng)域邏輯創(chuàng)建并注冊(cè)自定義倉(cāng)儲(chǔ)自定義倉(cāng)儲(chǔ)只需繼承EntityRepositoryT。需要注意要訪問(wèn)驅(qū)動(dòng)特有方法如createQueryBuilder()必須使用從驅(qū)動(dòng)包導(dǎo)出的EntityRepository類型而不是mikro-orm/core中的通用類型import { EntityRepository } from mikro-orm/mysql; // 或其他驅(qū)動(dòng)包postgresql、mongo 等 export class CustomAuthorRepository extends EntityRepositoryAuthor { // 自定義方法... public findAndUpdate(...) { // ... } }注冊(cè)方式是在實(shí)體定義中傳入customRepository回調(diào)。文檔特別提示v5 起Repository()裝飾器已被移除統(tǒng)一改用Entity({ customRepository: () MyRepository })Entity({ customRepository: () CustomAuthorRepository }) export class Author { // ... }回調(diào)形式() CustomAuthorRepository是刻意設(shè)計(jì)的當(dāng)倉(cāng)儲(chǔ)內(nèi)部引用了實(shí)體類時(shí)直接傳類引用會(huì)產(chǎn)生循環(huán)依賴回調(diào)可延遲求值從而規(guī)避該問(wèn)題。注冊(cè)完成后即可通過(guò)em.getRepository()拿到自定義倉(cāng)儲(chǔ)const repo em.getRepository(Author); // 運(yùn)行時(shí)是 CustomAuthorRepository 實(shí)例從Configuration.getRepositoryClass的解析順序可見實(shí)體上指定了repository回調(diào)時(shí)它會(huì)覆蓋全局entityRepository配置與平臺(tái)默認(rèn)類這正是實(shí)體級(jí)定制生效的原理。讓類型系統(tǒng)認(rèn)識(shí)自定義倉(cāng)儲(chǔ)EntityRepositoryType運(yùn)行時(shí)注冊(cè)解決了用哪個(gè)類但em.getRepository()的靜態(tài)返回類型默認(rèn)仍是通用的EntityRepositoryT。要讓 TypeScript 推斷出具體的自定義倉(cāng)儲(chǔ)類型需要在實(shí)體上聲明EntityRepositoryType符號(hào)import { EntityRepositoryType } from mikro-orm/core; Entity({ customRepository: () AuthorRepository }) export class Author { [EntityRepositoryType]?: AuthorRepository; } const repo em.getRepository(Author); // repo 的類型是 AuthorRepositoryEntityRepositoryType在 typings.ts 中定義/** Symbol used to declare a custom repository type on an entity class (e.g., [EntityRepositoryType]?: BookRepository). */ export const EntityRepositoryType Symbol(EntityRepositoryType);而 typings.ts 中的GetRepository類型工具會(huì)優(yōu)先讀取實(shí)體上聲明的該符號(hào)類型否則回退到通用EntityRepositorytype GetRepository... Entity[typeof EntityRepositoryType] extends EntityRepositoryany | undefined ? NonNullableEntity[typeof EntityRepositoryType] : ...倉(cāng)庫(kù)測(cè)試 tests/features/decorators/legacy/decorators.test.ts 給出了真實(shí)組合范例——自定義倉(cāng)儲(chǔ)在內(nèi)部直接使用this.em.persist與this.em.flush實(shí)體通過(guò)[EntityRepositoryType]?: BookRepository聲明類型這正是文檔所述模式在測(cè)試中的落地class BookRepository extends EntityRepositoryBook { save(book: Book): void { this.em.persist(book); } flush(): Promisevoid { return this.em.flush(); } } export class Book { // ... [EntityRepositoryType]?: BookRepository; }全局自定義基類倉(cāng)儲(chǔ)若希望所有未顯式指定customRepository的實(shí)體都默認(rèn)使用某個(gè)自定義基類可通過(guò)MikroORM.init的entityRepository配置全局注冊(cè)MikroORM.init({ entityRepository: CustomBaseRepository, // ... });該配置項(xiàng)的類型聲明見 Configuration.ts在getRepositoryClass中位于實(shí)體級(jí)repository回調(diào)之后的第二優(yōu)先級(jí)。注意這一配置同樣只影響運(yùn)行時(shí)實(shí)例化不影響em.getRepository()的靜態(tài)類型推斷——若需要全局類型推斷應(yīng)在公共基類實(shí)體上聲明EntityRepositoryType見下文。深度進(jìn)階類型推斷的邊界與基類實(shí)體上的符號(hào)聲明全局配置不參與類型推斷文檔明確指出全局entityRepository配置只決定運(yùn)行時(shí)實(shí)例化哪個(gè)類TypeScript 無(wú)法從中推斷倉(cāng)儲(chǔ)類型。要讓em.getRepository()返回正確類型必須二選一在每個(gè)實(shí)體定義上顯式指定repository即自定義倉(cāng)儲(chǔ)注冊(cè)路徑在公共基類實(shí)體上聲明EntityRepositoryType讓所有繼承者默認(rèn)繼承該類型。在基類實(shí)體上聲明EntityRepositoryType當(dāng)項(xiàng)目使用公共基類實(shí)體例如統(tǒng)一管理主鍵的BaseEntity時(shí)可以在基類上一次性聲明符號(hào)類型import { EntityRepositoryType, PrimaryKey } from mikro-orm/core; export abstract class BaseEntity { [EntityRepositoryType]?: BaseRepositorythis; PrimaryKey() id!: number; }此后em.getRepository(AnyEntityExtendingBaseEntity)會(huì)直接返回BaseRepositoryT無(wú)需在每個(gè)實(shí)體上重復(fù)聲明某個(gè)實(shí)體若需要專屬倉(cāng)儲(chǔ)仍可在其定義中覆蓋customRepository并聲明更具體的符號(hào)類型。通用基類倉(cāng)儲(chǔ)的寫法當(dāng)希望所有倉(cāng)儲(chǔ)共享自定義方法時(shí)可創(chuàng)建泛型基類倉(cāng)儲(chǔ)注意泛型參數(shù)的正確傳遞import { EntityRepository, EntityManager } from mikro-orm/mysql; // 或其他驅(qū)動(dòng)包 export class BaseRepositoryEntity extends object extends EntityRepositoryEntity { // 所有自定義方法復(fù)用同一個(gè) Entity 類型參數(shù) // 父類提供的 this.em 與 this.entityName 可直接使用。 async exists(where: FilterQueryEntity): Promiseboolean { const count await this.count(where); return count 0; } async findOrCreate(where: FilterQueryEntity, data: RequiredEntityDataEntity): PromiseEntity { let entity await this.findOne(where); if (!entity) { entity this.create(data); await this.em.flush(); } return entity; } }全局注冊(cè)MikroORM.init({ entityRepository: BaseRepository, });在此基礎(chǔ)上實(shí)體專屬倉(cāng)儲(chǔ)可以繼承該基類并疊加專屬方法export class AuthorRepository extends BaseRepositoryAuthor { async findActive(): PromiseAuthor[] { return this.find({ active: true }); } }通過(guò)實(shí)體定義的repository注冊(cè)后em.getRepository(Author)返回的AuthorRepository同時(shí)具備基類的通用方法與實(shí)體專屬方法未指定專屬倉(cāng)儲(chǔ)的其他實(shí)體則繼續(xù)使用BaseRepository。v6 起從倉(cāng)儲(chǔ)中移除的持久化方法文檔后半部分聚焦一個(gè)重要的 API 變更自 v6 起以下方法不再存在于EntityRepository實(shí)例上persistpersistAndFlushremoveremoveAndFlushflush移除理由是這些方法會(huì)帶來(lái)作用域上下文的錯(cuò)覺——開發(fā)者可能以為repo.persist(...)只作用于該倉(cāng)儲(chǔ)對(duì)應(yīng)的實(shí)體類型而實(shí)際上它們只是底層 EntityManager 同名方法的捷徑持久化的始終是整個(gè) Unit of Work。因此文檔建議涉及實(shí)體持久化的操作直接使用 EntityManager倉(cāng)儲(chǔ)應(yīng)定位為自定義邏輯如封裝 QueryBuilder 用法的擴(kuò)展點(diǎn)。替代方案一通過(guò)getEntityManager()需要持久化時(shí)可用倉(cāng)儲(chǔ)的getEntityManager()方法取到底層 EntityManager 再操作。該方法的實(shí)現(xiàn)見 EntityRepository.tsgetEntityManager(): EntityManager { return this.em; }替代方案二自定義基類倉(cāng)儲(chǔ)恢復(fù)舊方法若團(tuán)隊(duì)確實(shí)希望保留倉(cāng)儲(chǔ)級(jí)持久化方法可以自定義基類倉(cāng)儲(chǔ)并全局啟用import { EntityManager, EntityRepository } from mikro-orm/mysql; export class ExtendedEntityRepositoryT extends object extends EntityRepositoryT { persist(entity: object | object[]): EntityManager { return this.em.persist(entity); } async persistAndFlush(entity: object | object[]): Promisevoid { this.em.persist(entity); await this.em.flush(); } remove(entity: object): EntityManager { return this.em.remove(entity); } async removeAndFlush(entity: object): Promisevoid { this.em.remove(entity); await this.em.flush(); } async flush(): Promisevoid { return this.em.flush(); } }MikroORM.init({ entityRepository: ExtendedEntityRepository, });注意這些方法內(nèi)部依然委托給this.em即作用域仍是整個(gè) Unit of Work。若需要同時(shí)恢復(fù)類型推斷可結(jié)合EntityRepositoryType符號(hào)如在公共基類實(shí)體上聲明一起使用。EntityRepository類中還保留了完整的查詢與映射 API如findOneOrFail、findAll、findAndCount、findByCursor、nativeUpdate、nativeDelete、upsert/upsertMany、count/countBy、getReference、populate、create/assign/merge等它們與 EntityManager 的同名方法一一對(duì)應(yīng)均為轉(zhuǎn)發(fā)實(shí)現(xiàn)詳見 EntityRepository.ts。小結(jié)MikroORM 的倉(cāng)儲(chǔ)層設(shè)計(jì)遵循薄封裝 擴(kuò)展點(diǎn)原則默認(rèn)EntityRepository只是攜帶實(shí)體類型的 EntityManager 轉(zhuǎn)發(fā)器需要復(fù)用查詢邏輯時(shí)通過(guò)Entity({ customRepository: () MyRepository })注冊(cè)實(shí)體級(jí)自定義倉(cāng)儲(chǔ)并通過(guò)EntityRepositoryType符號(hào)獲得完整的類型推斷需要全局統(tǒng)一擴(kuò)展時(shí)用MikroORM.init({ entityRepository: BaseRepository })配置基類倉(cāng)儲(chǔ)。v6 移除倉(cāng)儲(chǔ)上的持久化方法進(jìn)一步明確了倉(cāng)儲(chǔ)與 EntityManager 的分工邊界——持久化交給 EntityManager倉(cāng)儲(chǔ)專注查詢與領(lǐng)域邏輯的封裝。相關(guān)參考官方文檔 docs/docs/repositories.mdv5.9 版本對(duì)應(yīng) docs/versioned_docs/version-5.9/repositories.md、核心實(shí)現(xiàn) packages/core/src/entity/EntityRepository.ts、packages/core/src/EntityManager.ts、packages/core/src/utils/Configuration.ts測(cè)試范例 tests/features/decorators/legacy/decorators.test.ts。贊分享后端【免費(fèi)下載鏈接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.項(xiàng)目地址https://gitcode.com/gh_mirrors/mi/mikro-orm點(diǎn)擊查看免費(fèi)下載相關(guān)推薦ASP.NET Boilerplate 倉(cāng)儲(chǔ)模式Repository Pattern完全指南從默認(rèn)倉(cāng)儲(chǔ)到自定義實(shí)現(xiàn)與最佳實(shí)踐ASP.NET Boilerplate 倉(cāng)儲(chǔ)模式Repository Pattern完全指南從默認(rèn)倉(cāng)儲(chǔ)到自定義實(shí)現(xiàn)與最佳實(shí)踐 導(dǎo)讀 倉(cāng)儲(chǔ)Reposito后端Web框架依賴注入認(rèn)證鑒權(quán)Gutenberg wordpress/data Persistence Plugin 實(shí)戰(zhàn)解析從 localStorage 默認(rèn)存儲(chǔ)到自定義存儲(chǔ)的完整指南Gutenberg wordpress/data Persistence Plugin 實(shí)戰(zhàn)解析從 localStorage 默認(rèn)存儲(chǔ)到自定義存儲(chǔ)的完整指南后端前端iloader 側(cè)載指南如何讀懂全局狀態(tài)設(shè)計(jì)SideloaderMutex 與 DeviceInfoMutex 協(xié)作解析iloader 側(cè)載指南如何讀懂全局狀態(tài)設(shè)計(jì)SideloaderMutex 與 DeviceInfoMutex 協(xié)作解析 iloader 是一款用戶友好的桌面應(yīng)用移動(dòng)開發(fā)上一篇AngularJS Material v1.2 升級(jí)遷移指南破壞性變更詳解與實(shí)戰(zhàn)對(duì)照下一篇免費(fèi)爐石傳說(shuō)模改插件HsMod終極指南讓游戲體驗(yàn)提升300%創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考