早期項目中手寫複雜 SQL 是核心技能,隨著 ORM 框架 (Hibernate/MyBatis/JPA) 普及、領域驅動設計 (DDD) 推廣、微服務拆分,代碼中原生 SQL 比例大幅下降。本文通過權限系統雙案例對比,剖析這一演進的深層動因與工程權衡。
核心表:organization、user、role、menu + 3 張關聯表 (role_menu, role_user, org_user)
SELECT a.uuid, a.name FROM menu a LEFT JOIN role_menu b ON a.uuid = b.menu_id LEFT JOIN role_user c ON b.role_id = c.role_id WHERE c.user_id = 'user-uuid';
RoleUser roleUser = roleRepository.findByUserId(userId);
if (roleUser == null) throw new BizException("用戶無角色");
List roleMenus = roleRepository.findMenusByRoleId(roleUser.getRoleId());
if (roleMenus.isEmpty()) throw new BizException("角色無菜單");
List
SELECT a.* FROM user a LEFT JOIN role_user b ON a.uuid = b.user_id LEFT JOIN org_user c ON a.uuid = c.user_id WHERE b.role_id = 'role-uuid' AND c.org_id = 'org-uuid';
ListroleUsers = roleRepository.findUsersByRoleId(roleId); List orgUsers = orgRepository.findUsersByOrgId(orgId); Set roleUserIds = roleUsers.stream().map(User::getId).collect(Collectors.toSet()); List result = orgUsers.stream() .filter(u -> roleUserIds.contains(u.getId())) .collect(Collectors.toList());
| 維度 | 手寫 SQL | ORM/面向對象 |
|---|---|---|
| 可讀性/意圖表達 | 需解析 JOIN/WHERE 邏輯 | 方法名即文檔,業務語義顯性 |
| 錯誤定位 | 執行報錯/結果錯,難定位 | 編譯期類型安全,運行期堆棧清晰 |
| 重構安全性 | 表/列變更需全文搜索 SQL | 重構工具自動同步實體/字段 |
| 性能可控性 | 完全控制執行計劃 | 需理解框架生成 SQL,必要時介入 |
| N+1 問題 | 不存在 | 易發生,需 fetch join / 批量查詢優化 |
| 複雜報表/聚合 | 優勢明顯 | 不適合,建議落庫視圖/物化表 |
| 測試友好度 | 需真實數據庫 | 可 Mock Repository 單元測試 |
關系數據庫是面向集合的集合論模型,OOP 是面向對象的圖模型。ORM 本質是阻抗失配的妥協橋樑。成熟工程師不執着於工具之爭,而是根據業務複雜度、性能指標、團隊能力、演進階段做動態選擇。
從「撸 SQL」到「撸代碼」,不是技術倒退,而是抽象層級提升:將「如何查」下沉給框架/中間件,業務代碼專注「查什麼、為什麼查、怎麼用結果」。這正是軟件工程「分層、解耦、領域建模」的核心價值體現。