1️⃣ 경로 패턴 충돌 문제
문제: String 형태의 owner로 일정 조회 시 GET 요청 실행 안 됨
원인:
- /planners/{plannerId}와 /planners/{plannerOwner} 충돌
- 해결: 쿼리 파라미터 사용 (/planners?owner=홍길동)
//READ-GET (전체조회 또는 작성자명으로 조회)
@GetMapping("/planners")
public ResponseEntity<List<GetPlannerResponse>> getPlanners(
@RequestParam(required=false) String owner
) {
//작성자명으로 조회
if(owner != null) {
return ResponseEntity.status(HttpStatus.OK).body(plannerService.findByOwner(owner));
}
//전체조회(RequestParameter 없는 경우)
return ResponseEntity.status(HttpStatus.OK).body(plannerService.findAll());
}
2️⃣ 빈 리스트 반환 문제
문제: 쿼리 파라미터 owner로 조회해도 빈 리스트만 나옴
원인:
- filter(p -> p.equals(owner)) 잘못됨
- 수정: filter(p -> p.getOwner().equals(owner))
@Transactional
public List<GetPlannerResponse> findByOwner(String owner){
List<Planner> planners = plannerRepository.findAll();
//READ 작성자명으로 조회
//방법2 -> Stream()으로 가져오기
return planners.stream()
.filter(p -> owner.equals(p.getOwner())) //getOwner() 결과가 owner와 같은 planer만 필터링
.map(GetPlannerResponse::new).toList();
}
3️⃣ Spring Data JPA 메서드
문제: Repository가 interface인데 메서드를 어디에 정의?
원인:
- Spring Data JPA는 인터페이스에 메서드 시그니처만 선언하면 자동으로 구현
- 작동 원리 :Spring Data JPA가 메서드 이름을 분석해서 자동으로 쿼리를 생성함.
findBy + Owner → SELECT * FROM planner WHERE owner = ?
- 작동 원리 :Spring Data JPA가 메서드 이름을 분석해서 자동으로 쿼리를 생성함.
- 예: List<Planner> findByOwner(String owner);
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByPlannerId(Long plannerId);
long countByPlannerId(Long plannerId); //이렇게만 해도 JPA에서 자동으로
}
4️⃣ JPA 연관관계 학습
문제: @ManyToOne, @OneToMany, 연관관계 주인 등 개념 이해 부족
원인:
- @ManyToOne: Comment → Planner (외래키 있는 쪽)
Comment 입장에서:- "나는(Comment) 하나의 Planner에 속해있다"
- "여러 개의 Comment가 하나의 Planner를 참조한다"
- = Many(Comment) To One(Planner)
- @OneToMany: Planner → Comment (mappedBy)
Planner 입장에서:- "나는(Planner) 여러 개의 Comment를 가질 수 있다"
- = One(Planner) To Many(Comment)
- LAZY vs EAGER Fetch 전략
// EAGER (즉시 로딩) - 기본값이지만 비추천
@ManyToOne(fetch = FetchType.EAGER)
// Comment 조회 시 Planner도 무조건 함께 조회 (JOIN 쿼리 발생)
// LAZY (지연 로딩) - 권장! ⭐
@ManyToOne(fetch = FetchType.LAZY)
// Comment 조회 시 Planner는 안 가져옴
// planner.getTitle() 같이 실제 사용할 때만 조회
- @JoinColumn 역할
- DB에서 외래키 컬럼 이름을 planner_id로 만들겠다는 의미
- 생략하면 자동으로 planner_id 만들어지긴 하지만, 명시적으로 쓰는 게 좋음
@ManyToOne
@JoinColumn(name = "planner_id") // ⭐ DB 컬럼명 지정
private Planner planner;
5️⃣ Database vs Table 개념
문제: comments database를 만들어야 하나?
원인:
- Database는 하나 (planner)
- Table만 추가됨 (comment)
- JPA가 자동으로 테이블 생성
6️⃣ Postman 에러
문제: 415 Unsupported Media Type
원인:
- Body 탭에서 Text → JSON으로 변경
- Content-Type 헤더 자동 설정됨
7️⃣ 양방향 연관관계
문제: Planner 조회 시 Comment도 함께 가져오기
원인:
- Planner에 @OneToMany 추가
- GetPlannerResponse에 List<CommentResponse> 추가
- Fetch Join으로 N+1 문제 방지 가능
### Planner.java
...
@OneToMany(mappedBy = "planner", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
...
8️⃣ JSON 필드 순서
문제: JSON 출력 순서 변경
원인:
- @JsonPropertyOrder 어노테이션 사용
- 예: @JsonPropertyOrder({"id", "title", "owner", ...})
### GetCommentResponse.java
...
@Getter
@JsonPropertyOrder({"id", "owner", "contents", "createdAt", "modifiedAt"})
public class GetCommentResponse {
private final Long id;
private final String contents;
private final String owner;
private final LocalDateTime createdAt;
private final LocalDateTime modifiedAt;
...
### GetPlannerResponse.java
...
@JsonPropertyOrder({"id", "title", "owner", "contents", "createdAt", "modifiedAt", "comments"})
public class GetPlannerResponse {
private final Long id;
private final String title;
private final String contents;
private final String owner;
private final LocalDateTime createdAt;
private final LocalDateTime modifiedAt;
private final List<GetCommentResponse> comments;
...
9️⃣ DTO 구조 개선
문제: Planner와 Comment DTO 분리가 나을까?
원인:
- 분리가 맞음!
- dto/planner/, dto/comment/ 구조 추천
- 확장성과 관리성 향상
'Projects > [Spring] Code Refactoring Project' 카테고리의 다른 글
| [Spring Plus] AOP의 동작 흐름 제어하기 (0) | 2026.02.28 |
|---|---|
| [Spring Plus] 컨트롤러 테스트 성공시키기 (0) | 2026.02.28 |
| [Spring Plus] JPQL로 쿼리 옵션 추가하기 (0) | 2026.02.27 |
| [Spring Plus] JWT 인증 필드 추가 (0) | 2026.02.27 |
| [Spring Plus] Troubleshooting - @Transactional (0) | 2026.02.27 |