schedule project 는 사용자가 일정을 입력하고, 해당 일정에 접근하여 비밀번호를 확인 후 수정 및 삭제를 할 수 있게합니다. 또한 사용자는 일정에 댓글을 작성할 수 있습니다.
사용자는 사용자명 (작성자명) 을 통해 리스트로 일정을 확인할 수 있고, 단일 일정을 조회하여 댓글을 함께 확인할 수 있습니다.
해당 과제는 3 Layer Architecture(Controller, Service, Repository)를 적용했습니다.
이러한 3 Layer Architecture 를 사용한 이유는 기본적으로 한 계층을 수정해도 다른 계층에 영향을 최소화하는것을 목적으로 합니다. 관심사를 분리하여 각 요소의 응집도가 높아지고 결합도는 낮아지게 합니다.
결합도가 낮아지면 각 계층을 독립적으로 테스트 할 수 있습니다.
- Controller 계층: 클라이언트로부터 HTTP 요청을 수신하고 응답을 반환해주는 역할을 담당하기 때문에 비즈니스로직이 존재하지 않고, 입출력에만 집중합니다. GET, POST, PUT, DELETE 를 각각 구분하여 작성할 수 있습니다.
- Service 계층: 비즈니스 로직을 구현합니다. Repository 에서 정보를 가져와 하나의 응답을 어떻게 구성할지 결정합니다. 원자성이 충족되어야하는 단계는 트랜잭션으로 묶어져서 관리됩니다.
- Repository 계층: 데이터를 저장, 조회하기 위해 DB에 직접 접근하고 CRUD 작업을 실제로 처리합니다.
컨트롤러에서 데이터를 전달하는 방법인 아래 세 어노테이션에 대한 정리입니다.
| 특징 | |
| @RequestParam | URI의 queryString 으로 데이터를 받습니다. ? 뒤에 작성하여 사용합니다. ex) http://localhost:8080/schedules?writer=”{writerId}” 해당하는 데이터 하나를 받아와서 사용해서 각 변수별로 데이터를 저장할 수 있지만 객체생성이 불가합니다. |
| @PathVariable | URL 의 일부를 파라미터로 사용합니다 / 뒤에 작성하여 사용합니다. ex) http://localhost:8080/schedules/{id} |
| @RequestBody | body 에 json 형태로 작성합니다. 데이터가 담긴 객체 (DTO) 를 받아와서 사용합니다. |
API 명세서
https://www.notion.so/API-2fc55c64ceb28017ac38da3959e4289b
일정 관리 앱 API 명세서 | Notion
API 명세서: 일정 관리 앱 - version: 1.0.0 - Base URL: http://localhost:8080
www.notion.so
GitHub
https://github.com/jiwon-e-e/schedule
GitHub - jiwon-e-e/schedule: sparta bootcamp basic spring assignment
sparta bootcamp basic spring assignment . Contribute to jiwon-e-e/schedule development by creating an account on GitHub.
github.com
과제 진행 및 트러블 슈팅 문서
https://kjw81024.tistory.com/33
기본 DTO 작성
사담발등에 불이 떨어졌어요1. 과제 진행 과정 LV0 ) API 명세서를 작성https://www.notion.so/API-2fc55c64ceb28017ac38da3959e4289b?source=copy_link 일정 관리 앱 API 명세서 | NotionAPI 명세서: 일정 관리 앱 - version: 1.0.0
kjw81024.tistory.com
https://kjw81024.tistory.com/35
댓글 기능 구현, 정렬
어제 댓글때문에 머리가 깨지는 줄 알았어요... 퇴실하고 컴퓨터 끄고 나서도 계속 댓글구현 어떻게 할지만 생각나서 자기전에 급하게 메모까지 했습니다... 고양이랑 자기로 약속한 시간 넘어
kjw81024.tistory.com
https://kjw81024.tistory.com/36
유저 입력 검증
유저 입력 검증 수행Schedule 을 POST 하는 과정에서 찾은 문제들을 적어봤습니다. 순서대로 한 번 확인해보겠습니다. 1. name 과 writer 가 공백으로 주어져도 스케줄이 생성됨2. 무슨 필드에서 문제가
kjw81024.tistory.com
ERD

1. Entity
일정과 댓글이 저장되는 Table 입니다.
각각은 id 를 고유값으로 가지고 있습니다.
/ Schedules
| 데이터명 | 값 | 예시 | 비고 |
| 아이디 | id | 1 | Id, JPA 가 순차적으로 부여 생성자에 포함되지 않음 |
| 일정 제목 | name | “10시 flow chart” | 최대 길이 30, 필수값 |
| 일정 내용 | contents | “ flow chart notion 에 업로드” | 최대 길이 200, 필수값 |
| 작성자명 | writer | “권지원” | 필수값 |
| 비밀번호 | pw | “1234” | 필수값 |
| 작성일 | createAt | 2026-02-03T13:45:20.4547789 | BaseEntity 에서 자동으로 부여 생성자에 포함되지 않음 |
| 수정일 | modifiedAt | 2026-02-03T13:45:20.4547789 | BaseEntity 에서 자동으로 부여 생성자에 포함되지 않음 |
public void update(String name, String writer)
사용자는 일정 제목과 작성자명만 업데이트 할 수 있습니다.
/ Comments
| 데이터명 | 값 | 예시 | 비고 |
| 아이디 | id | 1 | Id, JPA 가 순차적으로 부여 생성자에 포함되지 않음 |
| 일정 아이디 | schedule_id | 1 | Schedules 와 연결되는 FK |
| 일정 내용 | contents | “내인생이더재밌음” | 최대 길이 100, 필수값 |
| 작성자명 | writer | “냐냐냥” | 필수값 |
| 비밀번호 | pw | “1234aa” | 필수값 |
| 작성일 | createAt | 2026-02-03T13:45:20.4547789 | BaseEntity 에서 자동으로 부여 생성자에 포함되지 않음 |
| 수정일 | modifiedAt | 2026-02-03T13:45:20.4547789 | BaseEntity 에서 자동으로 부여 생성자에 포함되지 않음 |
위 두 Entity 는 BaseEntity 를 상속받아 작성일과 수정일 정보를 자동으로 갖습니다.
작성일 (createAt) 과 수정일 (modifiedAt) 은 LocalDateTime 형식으로 저장되며, 해당 Entity 가 update 되면 자동으로 modifiedAt 이 해당 시간으로 수정되어 저장됩니다.
2. Repository
JpaRepository 를 상속받아 사용하며 직접적으로 Entity 에 접근하고 관리합니다.
/ScheduleRepository
Schedule 과 관련된 Repository 단계 역할을 수행합니다.
List<Schedule> findAllByOrderByModifiedAtDesc()
Schedule 다건조회 시 일정목록을 수정일 기준으로 내림차순 정렬
JPA 가 query를 만들어주는 형식이므로 구체화가 필요없고 형식을 지켜야함
/CommentRepository
Comment 와 관련된 Repository 단계 역할을 수행합니다.
List<Comment> findByScheduleId(Long scheduleId)
특정 Schedule ID 를 가지는 CommentList를 반환
JPA 가 query를 만들어주는 형식이므로 구체화가 필요없고 형식을 지켜야함
3. Service
/ScheduleService
scheduleRepository 와 commentService 에 접근 할 수 있습니다.
Schedule 과 관련된 Service 단계 역할을 수행합니다.
@Transactional
public CreateScheduleResponse save(CreateScheduleRequest request)
사용자의 입력으로 들어온 request 에서 filed 값을 가져와서 Schedule 생성자에 넣어 Repository에 저장하고 저장된 Schedule 을 기준으로 하여 Response DTO 를 만들어 (ID 와 생성,수정일이 포함됨, PW 미포함) 반환
@Transactional(readOnly = true)
public GetScheduleResponse getOneSchedule(Long id)
조회할 단건 일정 ID 를 매개변수로 받아와서 해당 ID 를 가진 일정이 존재하는지 확인, 존재한다면 Response DTO 를 만들어 (ID 와 생성,수정일, 댓글 리스트 이 포함됨, PW 미포함) 반환
commentList 는 commentService 에 구현된 findCommentsByScheduleId 함수를 사용
@Transactional(readOnly = true)
public List<GetSchedulesResponse> getUserSchedule(String writer)
writer 를 매개변수로 받아와서 (null 또는 공백)-> 전체리스트 반환/ (존재) -> 해당 writer 가 작성한 게시물 리스트만 반환
반환 형태는 List 이며 반환할 List 가 없다면 빈 리스트로 반환됨
@Transactional
public UpdateScheduleResponse updateSchedule(Long id, UpdateScheduleRequest request)
수정할 단건 일정 ID 를 매개변수로 받아와서 해당 ID 를 가진 일정이 존재하는지 확인, 존재한다면 request 에 담긴 pw 와 비교하여 원래 일정에 저장된 pw 와 동일한지 확인, 동일하면 update로직 수행 후 DTO 형태로 return
각각에서 예외가 발생하면 해당하는 Exception 을 Throw
@Transactional
public void deleteSchedule(Long id, DeleteScheduleRequest request)
삭제할 단건 일정 ID 를 매개변수로 받아와서 해당 ID 를 가진 일정이 존재하는지 확인, 존재한다면 request 에 담긴 pw 와 비교하여 원래 일정에 저장된 pw 와 동일한지 확인, 동일하면 delete로직 수행, 반환값 없음
/CommentService
scheduleRepository 와 commentService 에 접근할 수 있습니다.
Comment와 관련된 Service 단계 역할을 수행합니다.
@Transactional
public CreateCommentResponse save(CreateCommentRequest request, Long scheduleId)
매개변수로 받아온 scheduleId 가 존재하는지 확인, 없으면 NonExistentException 을 Throw
해당 Schedule 에 달린 댓글 개수를 확인하여 10개가 넘었으면 TooManyCommentsException Throw
모두 만족하면, 사용자의 입력으로 들어온 request 에서 filed 값을 가져와서 Comment 생성자에 넣어 Repository에 저장하고 저장된 Comment를 기준으로 하여 Response DTO 를 만들어 (ID 와 생성,수정일이 포함됨, PW 미포함) 반환
@Transactional
public void delete(Long id, DeleteCommentRequest request)
삭제할 댓글 ID 를 매개변수로 받아와서 해당 ID 를 가진 댓글이 존재하는지 확인, 존재한다면 request 에 담긴 pw 와 비교하여 원래 댓글에 저장된 pw 와 동일한지 확인, 동일하면 delete로직 수행, 반환값 없음
public List<GetCommentResponse> findCommentsByScheduleId(Long scheduleId)
해당 scheduleId 를 가진 List (Comment 형태) 를 DTO 형태로 바꿔서 반환
public int getCommentCountByScheduleId(Long scheduleId)
해당 scheduleId 를 가진 List 의 size 반환
4. Controller
/ScheduleController
scheduleservice 와 validate class 에 접근할 수 있습니다
Schedule과 관련된 Controller 단계 역할을 수행합니다.
@PostMapping("/schedules")
ResponseEntity<CreateScheduleResponse> createSchedule (@RequestBody CreateScheduleRequest request){
필수값, 길이 확인, throw 되지 않았다면 save method 호출 후 반환값을 response 에 담아 body 로 반환
Status : 201 CREATED
@GetMapping("/schedules/{id}")
ResponseEntity<GetScheduleResponse> getOneSchedule (@PathVariable Long id)
단건조회 response 를 body 로 반환
Status : 200 OK
@GetMapping("/schedules")
ResponseEntity<List<GetSchedulesResponse>> getUserSchedule (@RequestParam(required = false) String writer)
다건조회 response 를 body 로 반환
Status : 200 OK
@PutMapping("/schedules/{id}")
ResponseEntity<UpdateScheduleResponse> updateSchedule (
@PathVariable Long id, @RequestBody UpdateScheduleRequest request)
필수값, 길이 확인, throw 되지 않았다면 update method 호출 후 반환값을 response 에 담아 body 로 반환
Status : 200 OK
@DeleteMapping("/schedules/{id}")
ResponseEntity<Void> deleteSchedule (@PathVariable Long id, @RequestBody DeleteScheduleRequest request)
delete method 호출
Status : 204 NO_CONTENT
/CommentController
scheduleservice 와 validate class 에 접근할 수 있습니다.
Comment와 관련된 Controller단계 역할을 수행합니다.
@PostMapping("/comments")
ResponseEntity<CreateCommentResponse> createComment
필수값, 길이 확인, throw 되지 않았다면 save method 호출 후 반환값을 response 에 담아 body 로 반환
Status : 201 CREATED
@DeleteMapping("/comments/{id}")
ResponseEntity<Void> deleteComment(@PathVariable Long id, @RequestBody DeleteCommentRequest request)
delete method 호출
Status : 204 NO_CONTENT
/ValidateString
String 값을 받아와서 해당 값이 API 명세서에 주어진 조건을 만족시켰는지 확인하는 메서드 선언
void validateNullableAndLength(String str, int length)
필수값, 길이 확인
void validateNullable(String str)
필수값 확인
5. DTO
/scheduleDto/
Schedule와 관련된 Data transfer 매개변수, 반환값으로 사용됩니다.
| DTO 명 | 기본 Schedule Entity 를 기준으로 작성 |
| CreateScheduleRequest | id, 생성일, 수정일을 제외한 field 를 갖습니다. |
| CreateScheduleResponse | 모든 final 형태의 field 를 갖습니다. 생성자가 존재합니다. |
| GetScheduleResponse | 모든 final 형태의 field 를 갖습니다. 추가로 List<GetCommentResponse> field 를 갖습니다. 댓글 리스트를 출력할 때 사용됩니다. 생성자가 존재합니다. |
| GetSchedulesResponse | 모든 final 형태의 field 를 갖습니다. 생성자가 존재합니다. |
| UpdateScheduleRequest | id, 내용, 생성일, 수정일을 제외한 field 를 갖습니다. |
| UpdateScheduleResponse | 내용, 생성일을 제외한 final 형태의 field 를 갖습니다. 생성자가 존재합니다. |
| DeleteScheduleRequest | pw 를 유일한 field 로 갖습니다. |
/commentDto/
Comment 와 관련된 Data transfer 매개변수, 반환값으로 사용됩니다.
현재 comment 는 수정과 관련된 기능을 구현하지 않았기 때문에 수정일 modifiedAt 은 생성시점에만 생성일과 동일한 시점으로 저장되고 이후 사용되지 않습니다.
| DTO 명 | 기본 Comment Entity 를 기준으로 작성 |
| CreateCommentRequest | id, 생성일, 수정일을 제외한 field 를 갖습니다. |
| CreateCommentResponse | 모든 final 형태의 field 를 갖습니다. 생성자가 존재합니다. |
| GetCommentResponse | 수정일을 제외한 모든 final 형태의 field 를 갖습니다. 생성자가 존재합니다. |
| DeleteCommentRequest | pw 를 유일한 field 로 갖습니다. |
6. Exception
개별 예외를 생성하여 처리하였습니다.
| BlankFieldException | 필수적으로 있어야하는 field 가 공백 또는 null 이면 발생 ResponseStatusException 을 상속받아 상태값 BAD_REQUEST 반환 |
| NonExistentException | 존재하지 않는 스케줄을 조회, 수정, 삭제하려고 하면 발생 |
| OverLengthException | field 최대 길이를 초과하면 발생 ResponseStatusException 을 상속받아 상태값 BAD_REQUEST 반환 |
| ToomManyCommentsException | 한 schedule 에 comment 가 10개 이상이 되면 발생 |
| WrongPasswordException | 수정, 삭제 기능에서 입력받은 pw 가 저장된 pw 와 다르면 발생 |
'SPARTA 과제 > SPRING' 카테고리의 다른 글
| 숙련) 일정관리앱 트러블슈팅 (1) (0) | 2026.02.11 |
|---|---|
| 숙련) 일정관리앱 업그레이드 초기설정 (1) | 2026.02.10 |
| 유저 입력 검증 (0) | 2026.02.04 |
| 댓글 기능 구현, 정렬 (0) | 2026.02.04 |
| 기본 DTO 작성 (0) | 2026.02.03 |