JPA性能铁三角:DTO投影、分页与安全校验的完美融合
JPA性能铁三角:DTO投影、分页与安全校验的完美融合
在为移动端或小程序开发列表接口时,我们面临着一个“不可能三角”的挑战:既要响应快(高性能),又要数据准(安全),还要代码好(可维护)。很多时候,为了实现一个,我们不得不牺牲另一个。
但事实果真如此吗?
今天,我将带你完整地设计并实现一个GET /api/app/demands(小程序查询需求列表)接口。我们将看到,如何通过将DTO投影、高效分页和轻量级安全校验这三大JPA (Java Persistence API, Java持久化API) 技巧组合成一个坚不可摧的“性能铁三角”,轻松地同时满足这三个看似矛盾的目标。
业务场景:一个简洁但信息丰富的需求列表 📱
我们的需求非常明确:在小程序端,分页展示当前用户自己创建的所有“福利需求单”(SolutionDemand)。列表的每一项需要展示8个核心字段,如状态、创建时间、预算、单号等。
核心挑战:
- 性能:
SolutionDemand实体可能包含很多字段,如何只查询我们需要的那8个? - 安全:如何确保用户A绝对无法看到用户B的需求列表?
- 分页:如何实现高效、准确的数据库分页?
解决方案:构建“性能铁三角”
我们将通过分层设计,让每一层都专注于解决一个核心问题。
第一角:DTO投影——只拿我想要的(Repository层)
这是性能优化的基石。我们拒绝加载完整的SolutionDemand实体,而是命令JPA直接为我们构建一个只包含所需字段的轻量级VO (View Object, 视图对象)。
1. 定义一个精确的VO
// AppDemandCreateVO.java@Data@NoArgsConstructor@AllArgsConstructor // 关键!为构造器投影提供支持public class AppDemandCreateVO { private Integer status; private Date createdDate; private BigDecimal singleSuitAmount; // ... 其他5个字段}
2. 编写构造器投影查询
// SolutionDemandRepository.java@Query(value = \"SELECT new com.productQualification.suitselection.vo.AppDemandCreateVO(\" + \" d.status, d.createdDate, d.singleSuitAmount, d.suitQuantity, \" + \" d.giftPackagingType, d.shippingCostType, d.originType, d.demandCode) \" + \"FROM SolutionDemand d \" + \"WHERE d.solutionUser.id = :currentUserId\", countQuery = \"SELECT count(d) FROM SolutionDemand d WHERE d.solutionUser.id = :currentUserId\")Page<AppDemandCreateVO> findDemandsForUser(@Param(\"currentUserId\") Integer currentUserId, Pageable pageable);
技术解读:
SELECT new ...: 直接将查询结果映射到AppDemandCreateVO,避免了实体创建的开销。- 精确字段列表:
SELECT子句中不多不少,正好是VO需要的8个字段。
第二角:高效分页——让数据库做它擅长的事(Repository层)
技术解读:
Pageable pageable: 通过在Repository方法中加入这个参数,我们把分页的复杂工作(计算LIMIT,OFFSET,ORDER BY)完全委托给了Spring Data JPA。countQuery: 我们提供了一个专门的、极其高效的countQuery。这使得JPA在获取总记录数时,不会执行那个带有多个字段的复杂主查询,而是执行一个简单的SELECT count(*)。这是分页查询的最佳实践。
第三角:轻量级安全校验——守好业务的第一道门(Service层)
在执行主查询之前,我们增加了一道轻量级的“前置安检”,确保发起请求的用户是真实有效的。
Service层实现:
// AppSolutionDemandService.java@Transactional(readOnly = true)public Page<AppDemandCreateVO> listDemandsForApp(Integer currentUserId, PageWithSearch query) { // 1. 轻量级用户存在性校验 if (!solutionUserRepository.existsById(currentUserId)) { throw new NotFoundException(\"当前登录用户不存在\"); } // 2. 构建分页对象 Pageable pageable = query.toPageable(); // 3. 直接调用Repository的高效分页查询方法 return demandRepository.findDemandsForUser(currentUserId, pageable);}
技术解读:
existsById(currentUserId): 我们没有加载完整的SolutionUser实体,而是使用了最高效的existsById方法。它只会生成一条SELECT COUNT(*)SQL (Structured Query Language),以最小的代价完成了对“幽灵用户”的防范。
成果验证:一份“三位一体”的SQL日志 🛡️
执行这个精心设计的接口,我们得到的SQL日志完美地体现了我们的“性能铁三角”:
【最终日志】
-- 1. 轻量级安全校验Hibernate: select count(*) as col_0_0_ from solution_user solutionus0_ where solutionus0_.id=?-- 2. 高效的DTO投影分页查询Hibernate: select d.status as col_0_0_, d.created_date as col_1_0_, ...from solution_demand dwhere d.solution_user_id=?order by d.created_date desc limit ?-- 3. 高效的分页总数查询Hibernate: select count(d.id) as col_0_0_ from solution_demand d where d.solution_user_id=?
日志解读:
- 安全:第一条日志确保了用户是真实有效的。
- 精准:第二条日志的
SELECT子句只包含了VO所需的精确字段。 - 高效:整个业务只产生了三次轻量级的数据库交互,并且查询次数与分页大小无关,彻底杜绝了N+1和过度查询问题。
结论:专业API是设计出来的,不是堆砌出来的 💡
这次小程序列表接口的实现,为我们揭示了构建高性能、高安全API (Application Programming Interface) 的黄金法则:
- DTO投影是第一选择:对于任何只读的列表接口,都应优先考虑使用DTO (Data Transfer Object) 投影,从源头上杜绝过度查询。
- 分页交给JPA,但
countQuery不能忘:利用Pageable的便利,但一定要提供一个优化的countQuery,这是专业分页查询的标志。 - 安全校验前置且轻量:在执行核心业务查询前,用
existsBy...等最高效的方式完成必要的安全检查。
通过将这三大技巧融合成一个“性能铁三角”,我们构建的不再是一个简单的查询接口,而是一个真正能够从容应对高并发和大数据量挑战的、工业级的解决方案。
附录:图表化总结与深度解析 📊✨
“性能铁三角”总结表 📋
SELECT new ...Pageable + countQueryexistsBy...接口处理流程图 (Flowchart) 💡
#mermaid-svg-JHqhSV81HBOu9a7F {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JHqhSV81HBOu9a7F .error-icon{fill:#fff;}#mermaid-svg-JHqhSV81HBOu9a7F .error-text{fill:#000000;stroke:#000000;}#mermaid-svg-JHqhSV81HBOu9a7F .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-JHqhSV81HBOu9a7F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JHqhSV81HBOu9a7F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JHqhSV81HBOu9a7F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JHqhSV81HBOu9a7F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JHqhSV81HBOu9a7F .marker{fill:#2874A6;stroke:#2874A6;}#mermaid-svg-JHqhSV81HBOu9a7F .marker.cross{stroke:#2874A6;}#mermaid-svg-JHqhSV81HBOu9a7F svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JHqhSV81HBOu9a7F .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JHqhSV81HBOu9a7F .cluster-label text{fill:#000000;}#mermaid-svg-JHqhSV81HBOu9a7F .cluster-label span{color:#000000;}#mermaid-svg-JHqhSV81HBOu9a7F .label text,#mermaid-svg-JHqhSV81HBOu9a7F span{fill:#333;color:#333;}#mermaid-svg-JHqhSV81HBOu9a7F .node rect,#mermaid-svg-JHqhSV81HBOu9a7F .node circle,#mermaid-svg-JHqhSV81HBOu9a7F .node ellipse,#mermaid-svg-JHqhSV81HBOu9a7F .node polygon,#mermaid-svg-JHqhSV81HBOu9a7F .node path{fill:#2E86C1;stroke:#1B4F72;stroke-width:1px;}#mermaid-svg-JHqhSV81HBOu9a7F .node .label{text-align:center;}#mermaid-svg-JHqhSV81HBOu9a7F .node.clickable{cursor:pointer;}#mermaid-svg-JHqhSV81HBOu9a7F .arrowheadPath{fill:undefined;}#mermaid-svg-JHqhSV81HBOu9a7F .edgePath .path{stroke:#2874A6;stroke-width:2.0px;}#mermaid-svg-JHqhSV81HBOu9a7F .flowchart-link{stroke:#2874A6;fill:none;}#mermaid-svg-JHqhSV81HBOu9a7F .edgeLabel{background-color:#EBF5FB;text-align:center;}#mermaid-svg-JHqhSV81HBOu9a7F .edgeLabel rect{opacity:0.5;background-color:#EBF5FB;fill:#EBF5FB;}#mermaid-svg-JHqhSV81HBOu9a7F .cluster rect{fill:#fff;stroke:hsl(0, 0%, 90%);stroke-width:1px;}#mermaid-svg-JHqhSV81HBOu9a7F .cluster text{fill:#000000;}#mermaid-svg-JHqhSV81HBOu9a7F .cluster span{color:#000000;}#mermaid-svg-JHqhSV81HBOu9a7F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:#fff;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JHqhSV81HBOu9a7F :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}#mermaid-svg-JHqhSV81HBOu9a7F .start>*{fill:#5DADE2!important;stroke:#3498DB!important;color:#fff!important;}#mermaid-svg-JHqhSV81HBOu9a7F .start span{fill:#5DADE2!important;stroke:#3498DB!important;color:#fff!important;}#mermaid-svg-JHqhSV81HBOu9a7F .check>*{fill:#F39C12!important;stroke:#D68910!important;color:#fff!important;}#mermaid-svg-JHqhSV81HBOu9a7F .check span{fill:#F39C12!important;stroke:#D68910!important;color:#fff!important;}#mermaid-svg-JHqhSV81HBOu9a7F .query>*{fill:#2ECC71!important;stroke:#28B463!important;color:#fff!important;}#mermaid-svg-JHqhSV81HBOu9a7F .query span{fill:#2ECC71!important;stroke:#28B463!important;color:#fff!important;}#mermaid-svg-JHqhSV81HBOu9a7F .result>*{fill:#1ABC9C!important;stroke:#16A085!important;color:#fff!important;}#mermaid-svg-JHqhSV81HBOu9a7F .result span{fill:#1ABC9C!important;stroke:#16A085!important;color:#fff!important;}失败通过返回 Page开始:listDemandsForAppStep 1: 轻量级用户校验
userRepository.existsById()抛出 NotFoundExceptionStep 2: DTO投影分页查询
demandRepository.findDemandsForUser()完成:返回成功响应
关键交互时序图 (Sequence Diagram) 🔄
#mermaid-svg-6oqbMjQSW2fWgfRF {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6oqbMjQSW2fWgfRF .error-icon{fill:hsl(348.1481481481, 75.7009345794%, 46.9607843137%);}#mermaid-svg-6oqbMjQSW2fWgfRF .error-text{fill:rgb(44.5981308413, 225.9018691588, 190.0887850466);stroke:rgb(44.5981308413, 225.9018691588, 190.0887850466);}#mermaid-svg-6oqbMjQSW2fWgfRF .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-6oqbMjQSW2fWgfRF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6oqbMjQSW2fWgfRF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6oqbMjQSW2fWgfRF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6oqbMjQSW2fWgfRF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6oqbMjQSW2fWgfRF .marker{fill:#020101;stroke:#020101;}#mermaid-svg-6oqbMjQSW2fWgfRF .marker.cross{stroke:#020101;}#mermaid-svg-6oqbMjQSW2fWgfRF svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6oqbMjQSW2fWgfRF .actor{stroke:hsl(168.1481481481, 35.7009345794%, 31.9607843137%);fill:#1ABC9C;}#mermaid-svg-6oqbMjQSW2fWgfRF text.actor>tspan{fill:#333;stroke:none;}#mermaid-svg-6oqbMjQSW2fWgfRF .actor-line{stroke:grey;}#mermaid-svg-6oqbMjQSW2fWgfRF .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6oqbMjQSW2fWgfRF .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6oqbMjQSW2fWgfRF #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6oqbMjQSW2fWgfRF .sequenceNumber{fill:#fff;}#mermaid-svg-6oqbMjQSW2fWgfRF #sequencenumber{fill:#333;}#mermaid-svg-6oqbMjQSW2fWgfRF #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6oqbMjQSW2fWgfRF .messageText{fill:#333;stroke:#333;}#mermaid-svg-6oqbMjQSW2fWgfRF .labelBox{stroke:hsl(168.1481481481, 35.7009345794%, 31.9607843137%);fill:#1ABC9C;}#mermaid-svg-6oqbMjQSW2fWgfRF .labelText,#mermaid-svg-6oqbMjQSW2fWgfRF .labelText>tspan{fill:#333;stroke:none;}#mermaid-svg-6oqbMjQSW2fWgfRF .loopText,#mermaid-svg-6oqbMjQSW2fWgfRF .loopText>tspan{fill:#333;stroke:none;}#mermaid-svg-6oqbMjQSW2fWgfRF .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(168.1481481481, 35.7009345794%, 31.9607843137%);fill:hsl(168.1481481481, 35.7009345794%, 31.9607843137%);}#mermaid-svg-6oqbMjQSW2fWgfRF .note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#mermaid-svg-6oqbMjQSW2fWgfRF .noteText,#mermaid-svg-6oqbMjQSW2fWgfRF .noteText>tspan{fill:#333;stroke:none;}#mermaid-svg-6oqbMjQSW2fWgfRF .activation0{fill:hsl(48.1481481481, 75.7009345794%, 41.9607843137%);stroke:hsl(48.1481481481, 75.7009345794%, 31.9607843137%);}#mermaid-svg-6oqbMjQSW2fWgfRF .activation1{fill:hsl(48.1481481481, 75.7009345794%, 41.9607843137%);stroke:hsl(48.1481481481, 75.7009345794%, 31.9607843137%);}#mermaid-svg-6oqbMjQSW2fWgfRF .activation2{fill:hsl(48.1481481481, 75.7009345794%, 41.9607843137%);stroke:hsl(48.1481481481, 75.7009345794%, 31.9607843137%);}#mermaid-svg-6oqbMjQSW2fWgfRF .actorPopupMenu{position:absolute;}#mermaid-svg-6oqbMjQSW2fWgfRF .actorPopupMenuPanel{position:absolute;fill:#1ABC9C;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6oqbMjQSW2fWgfRF .actor-man line{stroke:hsl(168.1481481481, 35.7009345794%, 31.9607843137%);fill:#1ABC9C;}#mermaid-svg-6oqbMjQSW2fWgfRF .actor-man circle,#mermaid-svg-6oqbMjQSW2fWgfRF line{stroke:hsl(168.1481481481, 35.7009345794%, 31.9607843137%);fill:#1ABC9C;stroke-width:2px;}#mermaid-svg-6oqbMjQSW2fWgfRF :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}ControllerServiceRepository\"数据库\"listDemandsForApp(userId, query)1. userRepository.existsById(userId)执行 SELECT COUNT(*) FROM solution_user2. demandRepository.findDemandsForUser(...)2a. 执行 SELECT new VO(...) ... LIMIT ...2b. 执行 SELECT count(...) ... (countQuery)返回 Page返回 PageControllerServiceRepository\"数据库\"
实体状态图 (State Diagram) 🚦
此接口为只读操作,不改变任何实体状态。
#mermaid-svg-yX41iboitadB6YXk {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#mermaid-svg-yX41iboitadB6YXk .error-icon{fill:hsl(24.0718562874, 69.8744769874%, 58.137254902%);}#mermaid-svg-yX41iboitadB6YXk .error-text{fill:rgb(32.1589958159, 121.4895397489, 181.3410041839);stroke:rgb(32.1589958159, 121.4895397489, 181.3410041839);}#mermaid-svg-yX41iboitadB6YXk .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-yX41iboitadB6YXk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yX41iboitadB6YXk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yX41iboitadB6YXk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yX41iboitadB6YXk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yX41iboitadB6YXk .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-yX41iboitadB6YXk .marker.cross{stroke:#0b0b0b;}#mermaid-svg-yX41iboitadB6YXk svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yX41iboitadB6YXk defs #statediagram-barbEnd{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-yX41iboitadB6YXk g.stateGroup text{fill:hsl(204.0718562874, 29.8744769874%, 43.137254902%);stroke:none;font-size:10px;}#mermaid-svg-yX41iboitadB6YXk g.stateGroup text{fill:#fff;stroke:none;font-size:10px;}#mermaid-svg-yX41iboitadB6YXk g.stateGroup .state-title{font-weight:bolder;fill:#fff;}#mermaid-svg-yX41iboitadB6YXk g.stateGroup rect{fill:#3498DB;stroke:hsl(204.0718562874, 29.8744769874%, 43.137254902%);}#mermaid-svg-yX41iboitadB6YXk g.stateGroup line{stroke:#0b0b0b;stroke-width:1;}#mermaid-svg-yX41iboitadB6YXk .transition{stroke:#0b0b0b;stroke-width:1;fill:none;}#mermaid-svg-yX41iboitadB6YXk .stateGroup .composit{fill:#f4f4f4;border-bottom:1px;}#mermaid-svg-yX41iboitadB6YXk .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-yX41iboitadB6YXk .state-note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#mermaid-svg-yX41iboitadB6YXk .state-note text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-yX41iboitadB6YXk .stateLabel .box{stroke:none;stroke-width:0;fill:#3498DB;opacity:0.5;}#mermaid-svg-yX41iboitadB6YXk .edgeLabel .label rect{fill:#3498DB;opacity:0.5;}#mermaid-svg-yX41iboitadB6YXk .edgeLabel .label text{fill:#fff;}#mermaid-svg-yX41iboitadB6YXk .label div .edgeLabel{color:#fff;}#mermaid-svg-yX41iboitadB6YXk .stateLabel text{fill:#fff;font-size:10px;font-weight:bold;}#mermaid-svg-yX41iboitadB6YXk .node circle.state-start{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-yX41iboitadB6YXk .node .fork-join{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-yX41iboitadB6YXk .node circle.state-end{fill:hsl(204.0718562874, 29.8744769874%, 43.137254902%);stroke:#f4f4f4;stroke-width:1.5;}#mermaid-svg-yX41iboitadB6YXk .end-state-inner{fill:#f4f4f4;stroke-width:1.5;}#mermaid-svg-yX41iboitadB6YXk .node rect{fill:#3498DB;stroke:hsl(204.0718562874, 29.8744769874%, 43.137254902%);stroke-width:1px;}#mermaid-svg-yX41iboitadB6YXk .node polygon{fill:#3498DB;stroke:hsl(204.0718562874, 29.8744769874%, 43.137254902%);stroke-width:1px;}#mermaid-svg-yX41iboitadB6YXk #statediagram-barbEnd{fill:#0b0b0b;}#mermaid-svg-yX41iboitadB6YXk .statediagram-cluster rect{fill:#3498DB;stroke:hsl(204.0718562874, 29.8744769874%, 43.137254902%);stroke-width:1px;}#mermaid-svg-yX41iboitadB6YXk .cluster-label,#mermaid-svg-yX41iboitadB6YXk .nodeLabel{color:#fff;}#mermaid-svg-yX41iboitadB6YXk .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-yX41iboitadB6YXk .statediagram-state .divider{stroke:hsl(204.0718562874, 29.8744769874%, 43.137254902%);}#mermaid-svg-yX41iboitadB6YXk .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-yX41iboitadB6YXk .statediagram-cluster.statediagram-cluster .inner{fill:#f4f4f4;}#mermaid-svg-yX41iboitadB6YXk .statediagram-cluster.statediagram-cluster-alt .inner{fill:hsl(24.0718562874, 69.8744769874%, 58.137254902%);}#mermaid-svg-yX41iboitadB6YXk .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-yX41iboitadB6YXk .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-yX41iboitadB6YXk .statediagram-state rect.divider{stroke-dasharray:10,10;fill:hsl(24.0718562874, 69.8744769874%, 58.137254902%);}#mermaid-svg-yX41iboitadB6YXk .note-edge{stroke-dasharray:5;}#mermaid-svg-yX41iboitadB6YXk .statediagram-note rect{fill:#fff5ad;stroke:hsl(52.6829268293, 60%, 73.9215686275%);stroke-width:1px;rx:0;ry:0;}#mermaid-svg-yX41iboitadB6YXk .statediagram-note rect{fill:#fff5ad;stroke:hsl(52.6829268293, 60%, 73.9215686275%);stroke-width:1px;rx:0;ry:0;}#mermaid-svg-yX41iboitadB6YXk .statediagram-note text{fill:#333;}#mermaid-svg-yX41iboitadB6YXk .statediagram-note .nodeLabel{color:#333;}#mermaid-svg-yX41iboitadB6YXk .statediagram .edgeLabel{color:red;}#mermaid-svg-yX41iboitadB6YXk #dependencyStart,#mermaid-svg-yX41iboitadB6YXk #dependencyEnd{fill:#0b0b0b;stroke:#0b0b0b;stroke-width:1;}#mermaid-svg-yX41iboitadB6YXk :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}查询操作
核心类图 (Class Diagram) 🏗️
展示了Service与Repository的依赖关系和关键方法。
#mermaid-svg-oYPpzZVEzR46iFA6 {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#mermaid-svg-oYPpzZVEzR46iFA6 .error-icon{fill:hsl(102.5806451613, 38.9121338912%, 58.137254902%);}#mermaid-svg-oYPpzZVEzR46iFA6 .error-text{fill:rgb(124.1694560668, 65.2112970711, 148.2887029287);stroke:rgb(124.1694560668, 65.2112970711, 148.2887029287);}#mermaid-svg-oYPpzZVEzR46iFA6 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-oYPpzZVEzR46iFA6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oYPpzZVEzR46iFA6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oYPpzZVEzR46iFA6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oYPpzZVEzR46iFA6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oYPpzZVEzR46iFA6 .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-oYPpzZVEzR46iFA6 .marker.cross{stroke:#0b0b0b;}#mermaid-svg-oYPpzZVEzR46iFA6 svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oYPpzZVEzR46iFA6 g.classGroup text{fill:hsl(282.5806451613, 0%, 43.137254902%);fill:#fff;stroke:none;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-oYPpzZVEzR46iFA6 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-oYPpzZVEzR46iFA6 .nodeLabel,#mermaid-svg-oYPpzZVEzR46iFA6 .edgeLabel{color:#fff;}#mermaid-svg-oYPpzZVEzR46iFA6 .edgeLabel .label rect{fill:#9B59B6;}#mermaid-svg-oYPpzZVEzR46iFA6 .label text{fill:#fff;}#mermaid-svg-oYPpzZVEzR46iFA6 .edgeLabel .label span{background:#9B59B6;}#mermaid-svg-oYPpzZVEzR46iFA6 .classTitle{font-weight:bolder;}#mermaid-svg-oYPpzZVEzR46iFA6 .node rect,#mermaid-svg-oYPpzZVEzR46iFA6 .node circle,#mermaid-svg-oYPpzZVEzR46iFA6 .node ellipse,#mermaid-svg-oYPpzZVEzR46iFA6 .node polygon,#mermaid-svg-oYPpzZVEzR46iFA6 .node path{fill:#9B59B6;stroke:hsl(282.5806451613, 0%, 43.137254902%);stroke-width:1px;}#mermaid-svg-oYPpzZVEzR46iFA6 .divider{stroke:hsl(282.5806451613, 0%, 43.137254902%);stroke:1;}#mermaid-svg-oYPpzZVEzR46iFA6 g.clickable{cursor:pointer;}#mermaid-svg-oYPpzZVEzR46iFA6 g.classGroup rect{fill:#9B59B6;stroke:hsl(282.5806451613, 0%, 43.137254902%);}#mermaid-svg-oYPpzZVEzR46iFA6 g.classGroup line{stroke:hsl(282.5806451613, 0%, 43.137254902%);stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 .classLabel .box{stroke:none;stroke-width:0;fill:#9B59B6;opacity:0.5;}#mermaid-svg-oYPpzZVEzR46iFA6 .classLabel .label{fill:hsl(282.5806451613, 0%, 43.137254902%);font-size:10px;}#mermaid-svg-oYPpzZVEzR46iFA6 .relation{stroke:#0b0b0b;stroke-width:1;fill:none;}#mermaid-svg-oYPpzZVEzR46iFA6 .dashed-line{stroke-dasharray:3;}#mermaid-svg-oYPpzZVEzR46iFA6 #compositionStart,#mermaid-svg-oYPpzZVEzR46iFA6 .composition{fill:#0b0b0b!important;stroke:#0b0b0b!important;stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 #compositionEnd,#mermaid-svg-oYPpzZVEzR46iFA6 .composition{fill:#0b0b0b!important;stroke:#0b0b0b!important;stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 #dependencyStart,#mermaid-svg-oYPpzZVEzR46iFA6 .dependency{fill:#0b0b0b!important;stroke:#0b0b0b!important;stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 #dependencyStart,#mermaid-svg-oYPpzZVEzR46iFA6 .dependency{fill:#0b0b0b!important;stroke:#0b0b0b!important;stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 #extensionStart,#mermaid-svg-oYPpzZVEzR46iFA6 .extension{fill:#0b0b0b!important;stroke:#0b0b0b!important;stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 #extensionEnd,#mermaid-svg-oYPpzZVEzR46iFA6 .extension{fill:#0b0b0b!important;stroke:#0b0b0b!important;stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 #aggregationStart,#mermaid-svg-oYPpzZVEzR46iFA6 .aggregation{fill:#9B59B6!important;stroke:#0b0b0b!important;stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 #aggregationEnd,#mermaid-svg-oYPpzZVEzR46iFA6 .aggregation{fill:#9B59B6!important;stroke:#0b0b0b!important;stroke-width:1;}#mermaid-svg-oYPpzZVEzR46iFA6 .edgeTerminals{font-size:11px;}#mermaid-svg-oYPpzZVEzR46iFA6 :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}\"依赖\"\"依赖\"\"创建\"AppSolutionDemandService-SolutionUserRepository userRepository-SolutionDemandRepository demandRepository+listDemandsForApp() : PageSolutionUserRepository+existsById() : booleanSolutionDemandRepository+findDemandsForUser() : Page«VO»AppDemandCreateVO
实体关系图 (Entity Relationship Diagram) 🔗
用ER图的形式更直观地展示查询涉及的表和字段。
#mermaid-svg-ruEMh83kYtnWyJqe {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ruEMh83kYtnWyJqe .error-icon{fill:hsl(208.1632653061, 79.674796748%, 56.7647058824%);}#mermaid-svg-ruEMh83kYtnWyJqe .error-text{fill:rgb(198.0914634145, 104.8719512193, 22.4085365853);stroke:rgb(198.0914634145, 104.8719512193, 22.4085365853);}#mermaid-svg-ruEMh83kYtnWyJqe .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ruEMh83kYtnWyJqe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ruEMh83kYtnWyJqe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ruEMh83kYtnWyJqe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ruEMh83kYtnWyJqe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ruEMh83kYtnWyJqe .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-ruEMh83kYtnWyJqe .marker.cross{stroke:#0b0b0b;}#mermaid-svg-ruEMh83kYtnWyJqe svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ruEMh83kYtnWyJqe .entityBox{fill:#E67E22;stroke:hsl(28.1632653061, 39.674796748%, 41.7647058824%);}#mermaid-svg-ruEMh83kYtnWyJqe .attributeBoxOdd{fill:#ffffff;stroke:hsl(28.1632653061, 39.674796748%, 41.7647058824%);}#mermaid-svg-ruEMh83kYtnWyJqe .attributeBoxEven{fill:#f2f2f2;stroke:hsl(28.1632653061, 39.674796748%, 41.7647058824%);}#mermaid-svg-ruEMh83kYtnWyJqe .relationshipLabelBox{fill:hsl(208.1632653061, 79.674796748%, 56.7647058824%);opacity:0.7;background-color:hsl(208.1632653061, 79.674796748%, 56.7647058824%);}#mermaid-svg-ruEMh83kYtnWyJqe .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-ruEMh83kYtnWyJqe .relationshipLine{stroke:#0b0b0b;}#mermaid-svg-ruEMh83kYtnWyJqe :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}SOLUTION_USERintidPKSOLUTION_DEMANDintidPKintsolution_user_idFKintstatusdatetimecreated_datevarchardemand_code创建
思维导图 (Markdown Format) 🧠



