Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.slam.concertreservation.domain.concert.model.Concert;
import com.slam.concertreservation.domain.concert.model.ConcertSchedule;
import com.slam.concertreservation.domain.concert.model.ConcertScheduleWithConcert;
import com.slam.concertreservation.domain.concert.model.Seat;
import com.slam.concertreservation.domain.concert.service.ConcertService;
import com.slam.concertreservation.domain.point.model.PointHistory;
Expand Down Expand Up @@ -116,8 +117,8 @@ public ConcertSchedule getConcertSchedule(Long concertScheduleId) {
*
* @return
*/
public List<ConcertSchedule> getAvailableConcertSchedules() {
return concertService.getAvailableConcertSchedule(LocalDateTime.now());
public List<ConcertScheduleWithConcert> getAvailableConcertSchedulesWithConcert() {
return concertService.getAvailableConcertScheduleWithConcert(LocalDateTime.now());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.slam.concertreservation.domain.concert.model;

/**
* 예약 가능 공연 일정 조회 시 반환되는 공연 일정 & 공연 정보 조합 모델
* @param concertSchedule
* @param concert
*/
public record ConcertScheduleWithConcert(
ConcertSchedule concertSchedule,
Concert concert
) {}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.slam.concertreservation.domain.concert.repository;

import com.slam.concertreservation.domain.concert.model.Concert;
import java.util.List;
import java.util.Optional;

public interface ConcertRepository {

Concert save(Concert concert);

Optional<Concert> findById(Long id);

List<Concert> findAllById(List<Long> ids);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.slam.concertreservation.common.error.ErrorCode;
import com.slam.concertreservation.domain.concert.model.Concert;
import com.slam.concertreservation.domain.concert.model.ConcertSchedule;
import com.slam.concertreservation.domain.concert.model.ConcertScheduleWithConcert;
import com.slam.concertreservation.domain.concert.model.Seat;
import com.slam.concertreservation.domain.concert.repository.ConcertRepository;
import com.slam.concertreservation.domain.concert.repository.ConcertScheduleRepository;
Expand All @@ -11,6 +12,7 @@
import com.slam.concertreservation.infrastructure.persistence.redis.locking.RedissonDistributedLock;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
Expand Down Expand Up @@ -265,14 +267,31 @@ public List<Seat> getAvailableSeatsOfConcertSchedule(Long concertScheduleId) {
return availableSeats;
}

/**
* 예약 가능 공연 일정 목록 조회.
*
* @param presentDateTime
* @return
*/
public List<ConcertSchedule> getAvailableConcertSchedule(LocalDateTime presentDateTime) {
return concertScheduleRepository.findAllAvailable(presentDateTime);
@Transactional(readOnly = true)
public List<ConcertScheduleWithConcert> getAvailableConcertScheduleWithConcert(LocalDateTime presentDateTime) {

// 예약 가능한 공연 일정 조회
List<ConcertSchedule> schedules = concertScheduleRepository.findAllAvailable(presentDateTime);

// 예약 가능한 공연 ID 목록 추출
List<Long> concertIds = schedules.stream()
.map(ConcertSchedule::getConcertId)
.distinct()
.toList();

// 공연 ID 목록으로 공연 조회
Map<Long, Concert> concerts = concertRepository.findAllById(concertIds)
.stream()
.collect(
java.util.stream.Collectors.toMap(Concert::getId, concert -> concert));

// ConcertSchedule과 Concert를 결합하여 ConcertScheduleWithConcert 생성
return schedules.stream()
.filter(schedule -> concerts.get(schedule.getConcertId()) != null)
.map(schedule -> new ConcertScheduleWithConcert(
schedule,
concerts.get(schedule.getConcertId())))
.toList();
Comment thread
leonroars marked this conversation as resolved.
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.slam.concertreservation.domain.concert.repository.ConcertRepository;
import com.slam.concertreservation.infrastructure.persistence.jpa.ConcertJpaRepository;
import com.slam.concertreservation.infrastructure.persistence.jpa.entities.ConcertJpaEntity;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
Expand All @@ -25,4 +26,12 @@ public Optional<Concert> findById(Long concertId) {
return concertJpaRepository.findById(concertId)
.map(ConcertJpaEntity::toDomain);
}

@Override
public List<Concert> findAllById(List<Long> ids) {
return concertJpaRepository.findAllById(ids)
.stream()
.map(ConcertJpaEntity::toDomain)
.toList();
}
Comment on lines +30 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n -A 30 'getAvailableConcertScheduleWithConcert' --type=java -g '!*Test*'

Repository: leonroars/slam

Length of output: 7769


🏁 Script executed:

rg -n 'class ConcertScheduleWithConcert' --type=java -A 15

Repository: leonroars/slam

Length of output: 40


🏁 Script executed:

rg -n 'ConcertScheduleWithConcert' --type=java | head -20

Repository: leonroars/slam

Length of output: 2909


🏁 Script executed:

cat -n src/main/java/com/slam/concertreservation/domain/concert/model/ConcertScheduleWithConcert.java

Repository: leonroars/slam

Length of output: 402


🏁 Script executed:

rg -n -B 5 -A 10 'getAvailableConcertSchedulesWithConcert' src/main/java/com/slam/concertreservation/interfaces/ConcertReservationController.java

Repository: leonroars/slam

Length of output: 694


🏁 Script executed:

rg -n -B 3 -A 15 'class ConcertScheduleResponse' --type=java -g '!*Test*' | head -50

Repository: leonroars/slam

Length of output: 2441


findAllById doesn't guarantee all requested IDs are found — will cause NullPointerException.

Spring Data JPA's findAllById returns only entities that exist. If a ConcertSchedule references a concertId that doesn't exist in the DB, concerts.get(schedule.getConcertId()) returns null. This null Concert is then passed to ConcertScheduleResponse.from(schedule, concert), which calls concert.getName() and concert.getArtist() (lines 25-26 in ConcertScheduleResponse), causing a NullPointerException at runtime.

Either validate that all Concert IDs exist before creating results, or filter out schedules with missing concerts.

🤖 Prompt for AI Agents
In
`@src/main/java/com/slam/concertreservation/infrastructure/persistence/jpa/impl/ConcertRepositoryImpl.java`
around lines 30 - 36, The current findAllById implementation
(ConcertRepositoryImpl.findAllById) can return fewer Concerts than requested
which leads to nulls when matching schedules; update the code that builds
schedule responses to either (a) validate that the returned list from
findAllById contains all requested IDs (compare size or missing IDs) and throw a
clear exception before calling ConcertScheduleResponse.from, or (b) filter out
ConcertSchedule entries that reference missing concerts by checking the map
built from
concertJpaRepository.findAllById(ids).stream().map(ConcertJpaEntity::toDomain)
for null/missing keys and skipping those schedules before invoking
ConcertScheduleResponse.from(schedule, concert); choose one approach and apply
it where ConcertScheduleResponse.from is called to prevent NullPointerException.

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.slam.concertreservation.component.idempotency.Idempotent;
import com.slam.concertreservation.domain.concert.model.Concert;
import com.slam.concertreservation.domain.concert.model.ConcertSchedule;
import com.slam.concertreservation.domain.concert.model.ConcertScheduleWithConcert;
import com.slam.concertreservation.domain.concert.model.Seat;
import com.slam.concertreservation.domain.point.model.UserPointBalance;
import com.slam.concertreservation.domain.queue.model.Token;
Expand Down Expand Up @@ -118,14 +119,11 @@ public ResponseEntity<ConcertScheduleResponse> registerConcertSchedule(
*/
@GetMapping("/concerts/schedules/available")
public ResponseEntity<List<ConcertScheduleResponse>> getAvailableConcertSchedules() {
List<ConcertSchedule> schedules = reservationApp.getAvailableConcertSchedules();
List<ConcertScheduleResponse> responses = schedules.stream()
.map(schedule -> {
Concert concert = reservationApp.getConcert(schedule.getConcertId());
return ConcertScheduleResponse.from(schedule, concert);
})
.toList();
return ResponseEntity.ok(responses);
List<ConcertScheduleWithConcert> schedules = reservationApp.getAvailableConcertSchedulesWithConcert();

return ResponseEntity.ok(schedules.stream()
.map(r -> ConcertScheduleResponse.from(r.concertSchedule(), r.concert()))
.toList());
}

/**
Expand Down
Loading