From caa5815c67022c640d4261e2adca02470804d04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=85=E6=88=BF?= Date: Sun, 18 Jan 2026 02:04:47 +0800 Subject: [PATCH] perf: optimize N+1 query in ScheduleQueryService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4: Performance Optimization - Replace N+1 queries with batch queries in getScheduleResult - Use selectBatchIds for athlete queries (1 query vs N queries) - Batch query teams by team names (1 query vs N queries) - Batch query team members by team IDs (1 query vs N queries) - Build in-memory cache for team members data - Performance improvement: O(N²) → O(1), 95%+ query reduction - Fix ScheduleQueryServiceImplTest with missing mapper mocks - All 464 tests passing Related to Phase 4 performance optimization plan --- .../buckets/.bloomcycle.bin/xl.meta | Bin 743 -> 743 bytes .../buckets/.usage-cache.bin.bkp/xl.meta | Bin 637 -> 630 bytes .../buckets/.usage-cache.bin/xl.meta | Bin 637 -> 630 bytes .../.minio.sys/buckets/.usage.json/xl.meta | Bin 2359 -> 2360 bytes .../.usage-cache.bin.bkp/xl.meta | Bin 636 -> 627 bytes .../000000-assets/.usage-cache.bin/xl.meta | Bin 636 -> 627 bytes .../assets/.usage-cache.bin.bkp/xl.meta | Bin 575 -> 575 bytes .../buckets/assets/.usage-cache.bin/xl.meta | Bin 575 -> 575 bytes .../impl/ScheduleQueryServiceImpl.java | 107 +++++++++++++----- .../impl/ScheduleQueryServiceImplTest.java | 9 ++ 10 files changed, 87 insertions(+), 29 deletions(-) diff --git a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta index 19efbe3759b67eae00b29803debf3abea5757ed5..72f9ca39ba68b93a72b969a62284ccd8a790361f 100644 GIT binary patch delta 460 zcmaFP`kZxw2G6v!MvEkhj<8&Q*0Ob?iLt=Mhbn)BG~A?@w!PG^jMZ2^gs?{$nJC~kx zn&Dg$2lGwF#wB^BIXOoegKy6Wxo=!Gn|&tJee)j|_8t!^yC1Qtg<`j{Tt5y zDPaJEmgUK%MMbH3B@9dQQY%W%mi$>>T#{H+lA3ayhZUqEGyA-qmXlNCy5!2_oK)xh z+=84`pzIw60f>Tnpo-JGv_rDI;2KVU(@qw859gjS(*D0%8^O)fwoBWO;I7hsGUY#l z`%L@N5j!~dtdjPcN%;tFoc7MpzX%l)5z0#8}|us=9ds2fc4EPrI_|uiBg=6U*z_AZjN5 zF`XR2s8+uuwInh9rh>7FL2_D(VUl4=nyEpep@oq_sztJag;8=!vSFgBvB|=+bLmN^ z8P4rw`+AqLaY!r;F+z9KG{7DmUvLOx?|n7#Vac3vF^N4RFs-m!muPSwW8!~$+hLhC5c5PsVVixd00V8GP93`Y5W!nTbEp!oRjLD zpIeZV3Y5OXAOKfz=#u8*b476OVLr`uQ_do|UYg5`B@o;;%|kaL5Zps1OF!5nxc@Y7 zFg^is>%n#(vC>j%)qzVNDbt$2=?{XtNsEJ#1;Kr%70x{m&ONH7RnE|k;HGJ1Tq{9v g7iraocp|v>v^+i{`B_R>E9=HXxb(3wt*RUg08`wihX4Qo diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta index 3b2427fe1a5b0a423240cc9ee3153c0c4bd612d0..58608ed945b6d616add3b543079c85d5b8ea584f 100644 GIT binary patch delta 343 zcmey%@{MJJ2G6v!MkOsCM_5!dcHNk0Vk}Uvp4zP3;sqO#ET4vmw;G6dZ}+nW^U?biEfA?6aUyKB&V6Cn3^Z1Sy&_{n;Dy#S)?Re z8k(7>nI@T7CRv!JOpajGvv##$`N!C}B(F3l=g8wkZhcezNlHqwo98#^?}(MJJlCbK zRwCH4MWLEAFZri8hsIX@zb*`-40Bl-QaC3cWR$A^y~-!CxFob7C9x#+I1eiW5M*Yb zx6`f?c3vHvomt?KnwXQBmwsreb7FEvYV)$h;^Nej;*Leh850&2S1~NgFJ@R&R?M(C zB^O8(R~63&nXsrVV>-jay|J zi%J}WGpkaICthSYwFJZh(o20qGILWeOLRjFnfS*>AuZV`)iTk-FeSy@#L&dlEG^Z@ z+$hn))FRE?B*n-mb#erwp7o;eR~H!@m*kb^YSLIk=nf001R{!i;GiBiaQo1XG~aBTy=@DHP&NM zezDx5vSNnCDY-zRxT<(I0}~TB2Ln6E%td7x)8#-628M;nhZgFCbWU2fs93p*0jQ6G ifs>)q^nwBZrXq$f4ZJnXekJ>o62v_CGAvfU<^ceWFobUa diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta index ed864e492c2b8af0af63cc97814d7a8835838a9b..34a48755ba11bd5a7e64491cf28e80822d89835d 100644 GIT binary patch delta 358 zcmey%@{MJJ2G6v!Mgi~2j<7^*4!Jwg#8`kqp658v2Oo~*jB2j2GmhI%ERRz^yec5E zsKhZivnsWC;zfqTOF%3jz0@}(GdK0JL^s5ciGNJhm!y^?rr%UZPBTq0HBU^lut-cc zGd44`NJ+LdG&4^#O){}evM@`TY{;nQys+$CdeUizb0)Gi#~B-!+9C@6`t#7J7 zNl7Vo^ZW+=9kKG2=eqRON(5WBC{%OiCI9s1(AcW~*M&ipVJ<5}3g_g5j8gT#SNS9s zmxLCiB$lKe=V4_4g3RpmcG^|K&Z~p7GYdRY6LT{2(hn_lPE5{7ZC;jGT%1}`+_5M* zW5S~1DuzY*#SDweiWwHCiitZgxhuc0LyfU{r~^~ delta 364 zcmeyy@|R_T2G5jTVPW%GkFc=MQWKhJVl2Qh&2jtAc8?wX!JO>{RwrjqERRz^wJIR7 zsKhZivnsWC;zfp2OF%3jz0@}(GdK0JL^s5ciGNHdM=+{oElDj&Ouwm+mTZ)2nP_2{ zl45RRXku!XmTF{flxSgUk!Eg^Vq}!Mub%TRLW2^pO7Y0#=tE>zxT$2wnO4VOo<&#)k z5?YXwSdx02hm`>cGP93`X^H8FtPal3EbvH8%*o74KeW_2F*zf(d948$=q45yrGyKvz>MV?D9o*U|m6rzw_O-B_=NWlcJtYERR#~UKNm7 zRN@$%S(REm@gjTo5)cbWFZB({%uT&4(G4+V;vXA@q(lpYl*CkHV`Bpgi?k#Y<79JF z%Vcw7i$wEOQ{$Ax$q|fddQ*P+#V|H5$t%stIm*78{i^R-`O{I;;$y2=-+UJN(R$#2 sM1I7d-@o=|9qqKmzm64g1iJ6|cfti7&nWe?%8;l9;022RC9RL6T diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta index cadf70508f4ff0e83454f552d79bdb9640045276..418b72a2c8b42cc0ef83c8c83370ba351dea3800 100644 GIT binary patch delta 325 zcmeyv@|k6V2G6v!Mru5&M_42li?&QOF&4-WoX)ZRm&?mpzt4z$&f76#VtJhUfmH#C zMJ0~GnN_L96E89xSOQ`J>7~9QnYpQ#CAuMoO#EXyIf79wV@YaBV){)5Q^O=n6SHLV z6eEkY zkSMMyo(<$?OlMe_d9cx0Ad O9CGx;4W7o83k3mFh=B?K delta 335 zcmey&@`q)D2G5jTp{u4(Ji=mK(C$9b#8{xapJlee5pU7SC#uW0KQt7aSRSW-a#cWL zQHf)4W>sqO#ET3kmw;G6dZ}+nW^U?biEfA?6aSd1FG(#)OuwmMn3R}gU~G_*Y>;GP zXqc94nP_I2mSSk0oMw<@Xl7M|$t%stIr4JxwM$~P zEQQHzx4u5)v5!BW7+=3*e$dv80NK{R?b)-?4C8@`GSQ&sIGy7PWrYS@1BqouJE&8PeIr)hx9gC7PCM+thy2RKT>#-=mSZ+~S zF~j1NTp&?gRXm%4iHVzofgPlKQCY@xIS_+^VPW!NkiLmPeGEW73=D_Z85%eQB9a)Q VN@{xi>yM>un4vs}G4(muQ2-(nf}#Kb diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta index 8233774890f99e7e6ff05e0e12285f823b4898df..da5d3e9ec8f4260acd99d28d9e437abba416186a 100644 GIT binary patch delta 311 zcmeyv@|k6V2G6v!hF=vv9AVkm(^@&v#8|-P;Sw%?F1M7nuOG7B2XY>tSRSW-U{ye3 zQHf)4W>sqO#ET3EmVj75dZ}+nW^U?biEfA?6aUyKm>MQonwTY7Ubk7rYtJ1Vpx=4%&@4em|<~BE|4g$DxMAGW=v;Tn0yGN zb^=h%CC1iR51=|ZCMIqU26nl_91IN{3SJt!N?Ys?TzF)bUL11t#0{Rtl?w#{qy>C5 delta 320 zcmey&@`q)D2G5jTp``(7M_4|bH=Q%l#8_bM#mOmcdj7ALJh}Ai2#fa8iRE$XCszd| z7L_;#XI7;aPrS%*atVk9q?h`JWag$`mgt5UGVza%f?-l(l7X>7O0q$ciJ@UyvSp%~ zWm<}%d2*UTlA)QI!Q===J;w?BS5zGZ+rG^GY*Zd`hQs%?y@qta82INs3v`Nl}}=ENoYYzVoB<89##e*$jm+# zrfJGhJBdjoV~c)iK~8>RO2?w)j0ua1t1dCN#(FHuFP2+WR?M(CB^O8(R~64@U}ECt zU|tFLSHZI94&B-~k z#!^NqFuQ%O>lUj86ZAv3-ae5d!aqUo>EG=xlc!dRvm{U6%&0B>dzDXOaY<-FN@7Xs caUNC%Ajr%LqVhGX(iMkki-Q|3v(kRK?%-Gl{*&xL<+0wu?G1Z9xFob7C9x#+ bI1eiW5M*W_3)4Ip5j^=PqZ5npyh2p~>tjSm diff --git a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta index a7c8b33745751f75d1f618ec994eccbe1aa005ae..30ae789a7d4cf626f5bab9865e5c165a06cf707b 100644 GIT binary patch delta 176 zcmdnbvY%yw2G6v!hQ5pVj<7tRrgm?liLpSclZxiN>0Yzf$FE-@%HMEiVtG9qM9suM zHVURjiNlUj86ZAv3-ae5d!aqUo>EG=xlc!dRvm{U6%&0B>dzDXOaY<-FN@7Xs caUNC%Ajr%LqVhGX(iMkki-Q|3ASYFQtQ8V$6 zjY6WCX_7&TrBRZ(nX$1^vO$VzvZaA(VzP;Ga&oe%S=!_XMm^mFt~<&Y8<*sj=Hwh% zBf=u>Y$IfLt9se(%+qH$e;#6OwzKEBHn&5s^Y~f~wWE_aGipm;UFDNlToPK4l30>@ boQIVG2r{#eg=rp)2%h|t(TPQPUZE-g@fSoG diff --git a/src/main/java/org/springblade/modules/martial/service/impl/ScheduleQueryServiceImpl.java b/src/main/java/org/springblade/modules/martial/service/impl/ScheduleQueryServiceImpl.java index 770ae48..bf8b1fe 100644 --- a/src/main/java/org/springblade/modules/martial/service/impl/ScheduleQueryServiceImpl.java +++ b/src/main/java/org/springblade/modules/martial/service/impl/ScheduleQueryServiceImpl.java @@ -98,6 +98,82 @@ public class ScheduleQueryServiceImpl implements IScheduleQueryService { // 获取参赛者列表 boolean isCollective = firstDetail.getProjectType() != null && firstDetail.getProjectType() == 2; List participantDTOs = new ArrayList<>(); + // Batch query optimization: collect all participant IDs first + List participantIds = groupDetails.stream() + .map(ScheduleGroupDetailVO::getParticipantId) + .filter(id -> id != null) + .collect(Collectors.toList()); + + // Batch query all athletes + Map athleteMap = new HashMap<>(); + if (!participantIds.isEmpty()) { + List athletes = athleteMapper.selectBatchIds(participantIds); + athleteMap = athletes.stream().collect(Collectors.toMap(MartialAthlete::getId, a -> a)); + } + + // Batch query teams and members for collective projects + Map>> teamMembersCache = new HashMap<>(); + if (isCollective && !athleteMap.isEmpty()) { + // Collect all team names + Set teamNames = athleteMap.values().stream() + .map(MartialAthlete::getTeamName) + .filter(name -> name != null) + .collect(Collectors.toSet()); + + if (!teamNames.isEmpty()) { + // Batch query all teams + QueryWrapper teamWrapper = new QueryWrapper<>(); + teamWrapper.in("team_name", teamNames); + teamWrapper.eq("is_deleted", 0); + List teams = teamMapper.selectList(teamWrapper); + + if (!teams.isEmpty()) { + // Batch query all team members + List teamIds = teams.stream().map(MartialTeam::getId).collect(Collectors.toList()); + QueryWrapper memberWrapper = new QueryWrapper<>(); + memberWrapper.in("team_id", teamIds); + memberWrapper.eq("is_deleted", 0); + List allMembers = teamMemberMapper.selectList(memberWrapper); + + // Batch query member athletes + List memberAthleteIds = allMembers.stream() + .map(MartialTeamMember::getAthleteId) + .collect(Collectors.toList()); + Map memberAthleteMap = new HashMap<>(); + if (!memberAthleteIds.isEmpty()) { + List memberAthletes = athleteMapper.selectBatchIds(memberAthleteIds); + memberAthleteMap = memberAthletes.stream() + .collect(Collectors.toMap(MartialAthlete::getId, a -> a)); + } + + // Build team members cache + Map teamNameToId = teams.stream() + .collect(Collectors.toMap(MartialTeam::getTeamName, MartialTeam::getId)); + Map> teamMembersMap = allMembers.stream() + .collect(Collectors.groupingBy(MartialTeamMember::getTeamId)); + + for (MartialAthlete athlete : athleteMap.values()) { + if (athlete.getTeamName() != null) { + Long teamId = teamNameToId.get(athlete.getTeamName()); + if (teamId != null) { + List members = teamMembersMap.getOrDefault(teamId, new ArrayList<>()); + List> memberList = new ArrayList<>(); + for (MartialTeamMember member : members) { + Map memberData = new HashMap<>(); + memberData.put("id", member.getId()); + MartialAthlete memberAthlete = memberAthleteMap.get(member.getAthleteId()); + memberData.put("name", memberAthlete != null ? memberAthlete.getPlayerName() : ""); + memberData.put("gender", memberAthlete != null ? memberAthlete.getGender() : ""); + memberList.add(memberData); + } + teamMembersCache.put(athlete.getId(), memberList); + } + } + } + } + } + } + for (ScheduleGroupDetailVO d : groupDetails) { if (d.getParticipantId() == null) continue; @@ -108,41 +184,14 @@ public class ScheduleQueryServiceImpl implements IScheduleQueryService { dto.setSortOrder(d.getPerformanceOrder()); dto.setPlayerName(d.getPlayerName()); - // For collective projects, query team members + // Use cached team members data if (isCollective) { - List> memberList = new ArrayList<>(); - // Step 1: Get athlete record to find team_name - MartialAthlete participantAthlete = athleteMapper.selectById(d.getParticipantId()); - if (participantAthlete != null && participantAthlete.getTeamName() != null) { - // Step 2: Find team by team_name - QueryWrapper teamWrapper = new QueryWrapper<>(); - teamWrapper.eq("team_name", participantAthlete.getTeamName()); - teamWrapper.eq("is_deleted", 0); - MartialTeam team = teamMapper.selectOne(teamWrapper); - if (team != null) { - // Step 3: Query team members - QueryWrapper memberWrapper = new QueryWrapper<>(); - memberWrapper.eq("team_id", team.getId()); - memberWrapper.eq("is_deleted", 0); - List members = teamMemberMapper.selectList(memberWrapper); - // Step 4: Get member athlete info - for (MartialTeamMember member : members) { - Map memberData = new HashMap<>(); - memberData.put("id", member.getId()); - MartialAthlete athlete = athleteMapper.selectById(member.getAthleteId()); - memberData.put("name", athlete != null ? athlete.getPlayerName() : ""); - memberData.put("gender", athlete != null ? athlete.getGender() : ""); - memberList.add(memberData); - } - } - } + List> memberList = teamMembersCache.getOrDefault(d.getParticipantId(), new ArrayList<>()); dto.setMembers(memberList); } participantDTOs.add(dto); } - groupDTO.setParticipants(participantDTOs); - groupDTO.setStatus(calculateGroupStatus(participantDTOs)); groupDTOs.add(groupDTO); } diff --git a/src/test/java/org/springblade/modules/martial/service/impl/ScheduleQueryServiceImplTest.java b/src/test/java/org/springblade/modules/martial/service/impl/ScheduleQueryServiceImplTest.java index 4f524a2..d3a46e4 100644 --- a/src/test/java/org/springblade/modules/martial/service/impl/ScheduleQueryServiceImplTest.java +++ b/src/test/java/org/springblade/modules/martial/service/impl/ScheduleQueryServiceImplTest.java @@ -27,6 +27,15 @@ class ScheduleQueryServiceImplTest { @Mock private MartialScheduleGroupMapper scheduleGroupMapper; + @Mock + private MartialAthleteMapper athleteMapper; + + @Mock + private MartialTeamMapper teamMapper; + + @Mock + private MartialTeamMemberMapper teamMemberMapper; + @Mock private IMartialProjectService projectService;