개인과제

Spring Boot 일정 관리 API 서버 구축 및 핵심 로직 분석

정이뿡뿡 2026. 7. 1. 11:48

https://github.com/thal070909/schedule 과제링크


📝 오늘 배운 내용

Spring Boot와 JPA를 사용하여 일정 관리 서버를 구축했다. 단순히 기능만 구현하는 것이 아니라,

왜 이 어노테이션과 방식을 사용했는지 고민하며 코드를 작성했다.

💡 핵심 구현 기능 (CRUD)

  1. 일정 생성 (POST): 클라이언트로부터 DTO(데이터 전송 객체)를 통해
    제목, 내용, 작성자, 비밀번호를 입력받아 DB에 저장.
    응답 시에는 보안을 위해 비밀번호를 제외한 ResponseDto를 반환하도록 설계했다.
  2. 일정 조회 (GET):
    • 전체 조회: 등록된 모든 일정을 수정일 기준 내림차순으로 정렬하여 반환. (작성자명을 파라미터로 받아 특정 작성자의 일정만 조회하는 기능도 추가)
    • 단건 조회: URL 경로에 포함된 id 값을 통해 특정 일정을 찾아 반환.
  3. 일정 수정 (PUT): id로 일정을 찾은 뒤, 클라이언트가 입력한 비밀번호가
    DB에 저장된 비밀번호와 일치할 때만 제목과 작성자명을 수정.
    영속성 컨텍스트의 변경 감지(Dirty Checking)를 위해 @Transactional을 활용했다. (이유는 더보기 클릭)
    더보기

    1. 변경 감지(Dirty Checking)란?

    보통 데이터베이스의 데이터를 수정할 때는 SQL 문(Update 문)을 직접 써서 "이 내용을 이렇게 바꿔라"라고 명령해야 합니다. 하지만 JPA를 쓰면 그럴 필요가 없습니다.

    • JPA는 '영속성 컨텍스트'라는 메모리 공간에 엔티티를 담아둡니다. 마치 도서관에서 책을 빌려오듯, DB에서 데이터를 가져와서 내 손에 들고 있는 상태라고 생각하면 돼요.
    • Dirty Checking(더티 체킹): 내가 손에 들고 있는 이 엔티티의 제목이나 작성자 내용을 코드로 수정하기만 하면, JPA가 "어? 처음에 빌려올 때랑 내용이 달라졌네?"라고 자동으로 감지합니다.
    • 이걸 '데이터가 더러워졌다(Dirty)'고 표현해서 '더티 체킹'이라고 부르는 것입니다.

    2. 왜 @Transactional을 붙여야 하나요?

    JPA는 이 '변경 감지'를 트랜잭션(Transaction) 안에서만 수행하기 때문입니다.

    • @Transactional이 붙은 메서드가 시작되면 "이제부터 이 작업은 하나의 묶음(트랜잭션)이다!"라고 선언하는 것입니다.
    • 메서드가 진행되는 동안 엔티티의 값을 수정하면, JPA는 이 트랜잭션이 끝나는 시점에 "바뀐 게 있네? 그럼 커밋(Commit)할 때 자동으로 DB에 Update 쿼리를 날려야지!"라고 판단합니다.
    • 만약 @Transactional이 없다면? JPA는 수정된 내용을 감지하지 못하거나, DB에 반영(Commit)할 타이밍을 잡지 못해 데이터가 바뀌지 않습니다.

    💡 한 줄 요약

    "트랜잭션(@Transactional) 안에서 엔티티를 수정하면, JPA가 나중에 알아서 DB에 수정 내용을 반영(Update)해 주는 마법 같은 기능"

    이것이 바로 영속성 컨텍스트의 변경 감지입니다.

  4. 일정 삭제 (DELETE): 수정과 마찬가지로 비밀번호 일치 여부를 검증한 후,
    repository.delete()를 통해 해당 데이터를 DB에서 완전히 삭제 처리.

💻 왜 이 코드를 썼는가? (기술적 의도)

1. 왜 @Getter, @NoArgsConstructor를 썼는가?

  • 의도: JPA는 DB 테이블과 객체를 매핑할 때 기본 생성자가 필수이며, 롬복(Lombok)의 @Getter를 사용해 불필요한 보일러플레이트 코드를 줄여 유지보수성을 높였다.

2. 왜 Service 계층에서 Transactional을 썼는가?

  • 의도: 수정(update) 로직에서 @Transactional을 사용한 이유는 데이터의 일관성 때문이다. 메서드 실행 중 예외가 발생하면 작업을 원상복구(Rollback)하고, 성공하면 최종 반영(Commit)하기 위해 사용했다.

3. 왜 Optional과 orElseThrow를 썼는가?

  • 의도: DB에서 데이터를 조회할 때 데이터가 없을 가능성을 고려했다. 단순히 null을 반환하는 것보다, orElseThrow를 통해 예외 상황을 명확하게 처리하고 에러 메시지를 전달하는 것이 안정적이기 때문이다.

4. 왜 ResponseDto를 따로 만들었는가?

  • 의도: Entity 클래스는 DB 테이블과 직결되므로, 이를 그대로 외부에 노출하면 비밀번호와 같은 민감 정보가 유출될 위험이 있다. 이를 방지하고 필요한 데이터만 전달하기 위해 DTO(Data Transfer Object)를 사용했다.

🔥 트러블슈팅 (Troubleshooting)

1. Git 푸시 에러: does not match any

  • 원인: 로컬의 master 브랜치와 원격의 main 브랜치 불일치.
  • 해결: git branch -M main으로 브랜치명을 통일하여 표준을 맞춤.

2. Lombok @Getter 미동작 (빨간 줄 에러)

  • 원인: 원본 Entity 파일 내 필드 및 어노테이션 누락.
  • 해결: Entity 클래스에 필수 필드와 @Getter를 명확히 선언하여 의존성 문제 해결.

3. Repository Bean 생성 실패

  • 원인: 인터페이스가 아닌 클래스 파일로 생성을 시도하거나 패키지 누락.
  • 해결: JpaRepository를 상속받는 interface로 ScheduleRepository를 생성하여 스프링 빈 등록.

4. Postman 테스트 400 Bad Request / 404 Not Found

  • 원인: 데이터 전송 위치(Body) 오류 및 코드 수정 후 서버 미재시작.
  • 해결: raw-JSON 형식으로 Body 탭에 데이터를 담고, 코드 변경 시 항상 서버를 재시작하여 변경 사항 반영.

🎯 오늘의 회고

코드 한 줄을 작성할 때 단순히 '되게 만드는 것'이 아니라 '왜 이 방식이 최선인가'를 고민하는 것이 개발자의 기본기임을 배웠다. 특히 JPA의 영속성 컨텍스트를 활용한 수정 로직과 DTO를 통한 데이터 보호 과정을 통해 스프링 부트의 설계 철학을 조금 더 깊이 이해하게 되었다.

 

참고: 본 게시글은 프로젝트 구현 과정에서 발생한 에러 해결과 코드 작성 의도를 기록한 TIL입니다.