Skip to content

Commit 2dae089

Browse files
authored
Merge 8e20a47 into 763ecc8
2 parents 763ecc8 + 8e20a47 commit 2dae089

40 files changed

+2895
-708
lines changed

src/main/java/sevenstar/marineleisure/activity/controller/ActivityController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static sevenstar.marineleisure.global.exception.enums.ActivityErrorCode.*;
44

5+
import java.math.BigDecimal;
56
import java.util.Map;
67

78
import org.springframework.http.ResponseEntity;
@@ -41,7 +42,7 @@ public ResponseEntity<BaseResponse<Map<String, ActivitySummaryResponse>>> getAct
4142
@GetMapping("/{activity}/detail")
4243
public ResponseEntity<BaseResponse<ActivityDetailResponse>> getActivityDetail(@PathVariable ActivityCategory activity, @ModelAttribute ActivityDetailRequest activityDetailRequest) {
4344
try {
44-
return BaseResponse.success(activityService.getActivityDetail(activity, activityDetailRequest.latitude(), activityDetailRequest.longitude()));
45+
return BaseResponse.success(activityService.getActivityDetail(activity, new BigDecimal(activityDetailRequest.latitude()), new BigDecimal(activityDetailRequest.longitude())));
4546
} catch (RuntimeException e) {
4647
return BaseResponse.error(INVALID_ACTIVITY);
4748
}
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package sevenstar.marineleisure.activity.dto.request;
22

3-
import java.math.BigDecimal;
43
import java.time.LocalDate;
54

65
public record ActivityDetailRequest(
7-
BigDecimal latitude,
8-
BigDecimal longitude,
9-
LocalDate date
6+
Float latitude,
7+
Float longitude,
8+
LocalDate time
109
) {
1110
}

src/main/java/sevenstar/marineleisure/activity/service/ActivityService.java

Lines changed: 200 additions & 195 deletions
Large diffs are not rendered by default.

src/main/java/sevenstar/marineleisure/alert/domain/JellyfishRegionDensity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class JellyfishRegionDensity extends BaseEntity {
3030

3131
@Column(name = "region_name", nullable = false, length = 100)
3232
private String regionName;
33-
@JoinColumn(name = "species_id", nullable = false)
33+
@Column(name = "species_id", nullable = false)
3434
private Long species;
3535

3636
@Column(name = "report_date", nullable = false)

src/main/java/sevenstar/marineleisure/alert/service/JellyfishService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.springframework.transaction.annotation.Transactional;
1515
import org.springframework.web.client.RestTemplate;
1616

17+
import jakarta.annotation.PostConstruct;
1718
import lombok.RequiredArgsConstructor;
1819
import lombok.extern.slf4j.Slf4j;
1920
import sevenstar.marineleisure.alert.domain.JellyfishRegionDensity;
@@ -38,6 +39,11 @@ public class JellyfishService implements AlertService<JellyfishDetailVO> {
3839
private final JellyfishCrawler crawler;
3940
private final RestTemplate restTemplate = new RestTemplate();
4041

42+
@PostConstruct
43+
public void onStartUp() {
44+
updateLatestReport();
45+
}
46+
4147
/**
4248
* 가장최신의 지역별 해파리 발생 리스트를 반환합니다.
4349
* [GET] /alerts/jellyfish

src/main/java/sevenstar/marineleisure/forecast/repository/FishingRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,6 @@ Optional<Fishing> findTopByCreatedAtGreaterThanEqualAndCreatedAtLessThanOrderByT
136136

137137
Optional<Fishing> findFishingBySpotIdAndForecastDateAndTimePeriod(Long spotId, LocalDate forecastDate,
138138
TimePeriod timePeriod);
139+
140+
Optional<Fishing> findBySpotIdAndForecastDateAndTimePeriod(Long spotId, LocalDate forecastDate, TimePeriod timePeriod);
139141
}

src/main/java/sevenstar/marineleisure/forecast/repository/MudflatRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import jakarta.transaction.Transactional;
1414
import sevenstar.marineleisure.forecast.domain.Fishing;
1515
import sevenstar.marineleisure.forecast.domain.Mudflat;
16+
import sevenstar.marineleisure.global.enums.TimePeriod;
1617
import sevenstar.marineleisure.spot.repository.ActivityRepository;
1718

1819
public interface MudflatRepository extends ActivityRepository<Mudflat, Long> {
@@ -92,4 +93,6 @@ Optional<Mudflat> findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessT
9293

9394
Optional<Mudflat> findBySpotIdAndCreatedAtBeforeOrderByCreatedAtDesc(Long spotId, LocalDateTime createdAtBefore);
9495

96+
Optional<Mudflat> findBySpotIdAndForecastDate(Long spotId, LocalDate forecastDate);
97+
9598
}

src/main/java/sevenstar/marineleisure/forecast/repository/ScubaRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
import org.springframework.data.repository.query.Param;
1212

1313
import jakarta.transaction.Transactional;
14+
import sevenstar.marineleisure.forecast.domain.Fishing;
1415
import sevenstar.marineleisure.forecast.domain.Scuba;
1516
import sevenstar.marineleisure.forecast.domain.Surfing;
17+
import sevenstar.marineleisure.global.enums.TimePeriod;
1618
import sevenstar.marineleisure.spot.repository.ActivityRepository;
1719

1820
public interface ScubaRepository extends ActivityRepository<Scuba, Long> {
@@ -93,4 +95,6 @@ Optional<Scuba> findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessTha
9395
Long spotId,
9496
LocalDateTime startDateTime,
9597
LocalDateTime endDateTime);
98+
99+
Optional<Scuba> findBySpotIdAndForecastDateAndTimePeriod(Long spotId, LocalDate forecastDate, TimePeriod timePeriod);
96100
}

src/main/java/sevenstar/marineleisure/forecast/repository/SurfingRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import jakarta.transaction.Transactional;
1313
import sevenstar.marineleisure.forecast.domain.Fishing;
1414
import sevenstar.marineleisure.forecast.domain.Surfing;
15+
import sevenstar.marineleisure.global.enums.TimePeriod;
1516
import sevenstar.marineleisure.spot.repository.ActivityRepository;
1617

1718
public interface SurfingRepository extends ActivityRepository<Surfing, Long> {
@@ -92,4 +93,6 @@ Optional<Surfing> findFirstBySpotIdAndCreatedAtGreaterThanEqualAndCreatedAtLessT
9293
Optional<Surfing> findBySpotIdAndCreatedAtBeforeOrderByCreatedAtDesc(Long spotId, LocalDateTime createdAtBefore);
9394

9495
Optional<Surfing> findBySpotIdOrderByCreatedAt(Long spotId);
96+
97+
Optional<Surfing> findBySpotIdAndForecastDateAndTimePeriod(Long spotId, LocalDate forecastDate, TimePeriod timePeriod);
9598
}

src/main/java/sevenstar/marineleisure/meeting/controller/MeetingController.java

Lines changed: 109 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package sevenstar.marineleisure.meeting.controller;
22

33
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Set;
46
import java.util.stream.Collectors;
57

68
import org.springframework.data.domain.Slice;
@@ -27,6 +29,7 @@
2729
import sevenstar.marineleisure.meeting.dto.mapper.CustomSlicePageResponse;
2830
import sevenstar.marineleisure.meeting.dto.request.CreateMeetingRequest;
2931
import sevenstar.marineleisure.meeting.dto.request.UpdateMeetingRequest;
32+
import sevenstar.marineleisure.meeting.dto.response.GoingMeetingResponse;
3033
import sevenstar.marineleisure.meeting.dto.response.MeetingDetailAndMemberResponse;
3134
import sevenstar.marineleisure.meeting.dto.response.MeetingDetailResponse;
3235
import sevenstar.marineleisure.meeting.dto.response.MeetingListResponse;
@@ -59,21 +62,53 @@ public ResponseEntity<BaseResponse<CustomSlicePageResponse<MeetingListResponse>>
5962
@RequestParam(name = "size", defaultValue = "10") Integer size
6063
) {
6164
Slice<Meeting> not_mapping_result = meetingService.getAllMeetings(cursorId, size);
62-
List<MeetingListResponse> dtoList = not_mapping_result.getContent().stream()
63-
//TODO :: 개선예정
65+
List<Meeting> meetingList = not_mapping_result.getContent();
66+
67+
// 🚀 Map Batch 최적화로 N+1 문제 해결! (5개 쿼리만)
68+
// 1. 모든 ID 수집
69+
Set<Long> hostIds = meetingList.stream().map(Meeting::getHostId).collect(Collectors.toSet());
70+
Set<Long> spotIds = meetingList.stream().map(Meeting::getSpotId).collect(Collectors.toSet());
71+
List<Long> meetingIds = meetingList.stream().map(Meeting::getId).collect(Collectors.toList());
72+
73+
// 2. Batch 조회 (5개 쿼리만!)
74+
Map<Long, Member> hostMap = memberRepository.findAllById(hostIds)
75+
.stream().collect(Collectors.toMap(Member::getId, m -> m));
76+
77+
Map<Long, OutdoorSpot> spotMap = outdoorSpotRepository.findAllById(spotIds)
78+
.stream().collect(Collectors.toMap(OutdoorSpot::getId, s -> s));
79+
80+
Map<Long, Tag> tagMap = tagRepository.findByMeetingIdIn(meetingIds)
81+
.stream().collect(Collectors.toMap(Tag::getMeetingId, t -> t));
82+
83+
Map<Long, Long> participantCountMap = participantRepository.countByMeetingIdIn(meetingIds)
84+
.stream().collect(Collectors.toMap(
85+
result -> (Long) result[0], // meetingId
86+
result -> (Long) result[1] // count
87+
));
88+
89+
// 3. 메모리에서 조합 (추가 쿼리 없음!)
90+
List<MeetingListResponse> dtoList = meetingList.stream()
6491
.map(meeting -> {
65-
Member host = memberRepository.findById(meeting.getHostId())
66-
.orElseThrow(() -> new RuntimeException("Host not found for meeting id: " + meeting.getId()));
67-
OutdoorSpot spot = outdoorSpotRepository.findById(meeting.getSpotId())
68-
.orElseThrow(() -> new RuntimeException("Spot not found for meeting id: " + meeting.getId()));
69-
Tag tag = tagRepository.findByMeetingId(meeting.getId())
70-
.orElseThrow(() -> new CustomException(MeetingError.MEETING_NOT_FOUND));
71-
long participantCount = participantRepository.countMeetingId(meeting.getId())
72-
.map(Integer::longValue)
73-
.orElse(0L);
92+
Member host = hostMap.get(meeting.getHostId());
93+
OutdoorSpot spot = spotMap.get(meeting.getSpotId());
94+
Tag tag = tagMap.get(meeting.getId());
95+
Long participantCount = participantCountMap.getOrDefault(meeting.getId(), 0L);
96+
97+
// Null 체크 (기존 예외 처리 유지)
98+
if (host == null) {
99+
throw new RuntimeException("Host not found for meeting id: " + meeting.getId());
100+
}
101+
if (spot == null) {
102+
throw new RuntimeException("Spot not found for meeting id: " + meeting.getId());
103+
}
104+
if (tag == null) {
105+
throw new CustomException(MeetingError.MEETING_NOT_FOUND);
106+
}
107+
74108
return MeetingListResponse.fromEntity(meeting, host, participantCount, spot, tag);
75109
})
76110
.collect(Collectors.toList());
111+
77112
Long nextCursorId = null;
78113
if(not_mapping_result.hasNext() && !not_mapping_result.getContent().isEmpty()) {
79114
Meeting lastMeetingInSlice = not_mapping_result.getContent().get(size - 1);
@@ -88,6 +123,7 @@ public ResponseEntity<BaseResponse<CustomSlicePageResponse<MeetingListResponse>>
88123
);
89124
return BaseResponse.success(result_Mapping);
90125
}
126+
91127
@GetMapping("/meetings/{id}")
92128
public ResponseEntity<BaseResponse<MeetingDetailResponse>> getMeetingDetail(
93129
@PathVariable("id") Long meetingId
@@ -97,26 +133,57 @@ public ResponseEntity<BaseResponse<MeetingDetailResponse>> getMeetingDetail(
97133
@GetMapping("/meetings/my")
98134
public ResponseEntity<BaseResponse<CustomSlicePageResponse<MeetingListResponse>>> getStatusListMeeting(
99135
@RequestParam(name = "status",defaultValue = "RECRUITING") MeetingStatus status,
100-
@RequestParam(name = "role",defaultValue = "HOST") MeetingRole role,
136+
@RequestParam(name = "role",defaultValue = "GUEST") MeetingRole role,
101137
@RequestParam(name = "cursorId", defaultValue = "0") Long cursorId,
102138
@RequestParam(name = "size", defaultValue = "10") Integer size,
103139
@AuthenticationPrincipal UserPrincipal userDetails
104140
){
105141

106142
Long memberId = userDetails.getId();
107143
Slice<Meeting> not_mapping_result = meetingService.getStatusMyMeetings_role(memberId,role,cursorId,size,status);
108-
List<MeetingListResponse> dtoList = not_mapping_result.getContent().stream()
109-
//TODO :: 개선예정
144+
List<Meeting> meetingList = not_mapping_result.getContent();
145+
146+
// 🚀 Map Batch 최적화로 N+1 문제 해결! (5개 쿼리만)
147+
// 1. 모든 ID 수집
148+
Set<Long> hostIds = meetingList.stream().map(Meeting::getHostId).collect(Collectors.toSet());
149+
Set<Long> spotIds = meetingList.stream().map(Meeting::getSpotId).collect(Collectors.toSet());
150+
List<Long> meetingIds = meetingList.stream().map(Meeting::getId).collect(Collectors.toList());
151+
152+
// 2. Batch 조회 (5개 쿼리만!)
153+
Map<Long, Member> hostMap = memberRepository.findAllById(hostIds)
154+
.stream().collect(Collectors.toMap(Member::getId, m -> m));
155+
156+
Map<Long, OutdoorSpot> spotMap = outdoorSpotRepository.findAllById(spotIds)
157+
.stream().collect(Collectors.toMap(OutdoorSpot::getId, s -> s));
158+
159+
Map<Long, Tag> tagMap = tagRepository.findByMeetingIdIn(meetingIds)
160+
.stream().collect(Collectors.toMap(Tag::getMeetingId, t -> t));
161+
162+
Map<Long, Long> participantCountMap = participantRepository.countByMeetingIdIn(meetingIds)
163+
.stream().collect(Collectors.toMap(
164+
result -> (Long) result[0], // meetingId
165+
result -> (Long) result[1] // count
166+
));
167+
168+
// 3. 메모리에서 조합 (추가 쿼리 없음!)
169+
List<MeetingListResponse> dtoList = meetingList.stream()
110170
.map(meeting -> {
111-
Member host = memberRepository.findById(meeting.getHostId())
112-
.orElseThrow(() -> new RuntimeException("Host not found for meeting id: " + meeting.getId()));
113-
OutdoorSpot spot = outdoorSpotRepository.findById(meeting.getSpotId())
114-
.orElseThrow(() -> new RuntimeException("Spot not found for meeting id: " + meeting.getId()));
115-
Tag tag = tagRepository.findByMeetingId(meeting.getId())
116-
.orElseThrow(() -> new CustomException(MeetingError.MEETING_NOT_FOUND));
117-
long participantCount = participantRepository.countMeetingId(meeting.getId())
118-
.map(Integer::longValue)
119-
.orElse(0L);
171+
Member host = hostMap.get(meeting.getHostId());
172+
OutdoorSpot spot = spotMap.get(meeting.getSpotId());
173+
Tag tag = tagMap.get(meeting.getId());
174+
Long participantCount = participantCountMap.getOrDefault(meeting.getId(), 0L);
175+
176+
// Null 체크 (기존 예외 처리 유지)
177+
if (host == null) {
178+
throw new RuntimeException("Host not found for meeting id: " + meeting.getId());
179+
}
180+
if (spot == null) {
181+
throw new RuntimeException("Spot not found for meeting id: " + meeting.getId());
182+
}
183+
if (tag == null) {
184+
throw new CustomException(MeetingError.MEETING_NOT_FOUND);
185+
}
186+
120187
return MeetingListResponse.fromEntity(meeting, host, participantCount, spot, tag);
121188
})
122189
.collect(Collectors.toList());
@@ -187,5 +254,24 @@ public ResponseEntity<BaseResponse<Long>> updateMeeting(
187254
return BaseResponse.success(meetingService.updateMeeting(meetingId, memberId, request));
188255
}
189256

257+
@PostMapping("/meetings/{id}/going")
258+
public ResponseEntity<BaseResponse<GoingMeetingResponse>> goingMeeting(
259+
@PathVariable("id") Long meetingId,
260+
@AuthenticationPrincipal UserPrincipal userDetails
261+
){
262+
Long memberId = userDetails.getId();
263+
return BaseResponse.success(meetingService.goingMeeting(meetingId, memberId));
264+
}
265+
266+
@DeleteMapping("/meetings/{id}")
267+
public ResponseEntity<BaseResponse<String>> deleteMeeting(
268+
@PathVariable("id") Long meetingId,
269+
@AuthenticationPrincipal UserPrincipal userDetails
270+
){
271+
Long memberId = userDetails.getId();
272+
meetingService.deleteMeeting(memberId, meetingId);
273+
return BaseResponse.success(HttpStatus.NO_CONTENT, "success");
274+
}
275+
190276

191277
}

0 commit comments

Comments
 (0)