오늘은 일정 관리 앱 백엔드 서버 만들기 과제를 시도해보았다.
어제 조회까지는 만들었었는데, 요구사항을 제대로 만족한 거 같지 않아 오늘 다시 만들어보았다.
요구사항 순서에 따라 한 번 풀이 해보자 !
( 일단은 레벨 0 단계는 추후에 다시 정리할 것이다 ! )
1️⃣ 일정 생성(일정 작성하기)
- 일정 생성 시, 포함되어야할 데이터
- 할일, 작성자명, 비밀번호, 작성/수정일을 저장
- 작성/수정일은 날짜와 시간을 모두 포함한 형태
- 각 일정의 고유 식별자(ID)를 자동으로 생성하여 관리
- 최초 입력 시, 수정일은 작성일과 동일
그렇다면 한 번 생성을 만들어 보자.
( DTO
와 Entity
가 있다는 과정 하에 생략하고 만들겠다. )
1) Controller
우리의 Controller
는 RestController
이다. ( Data
를 반환해야 되기 때문이다.)
그리고 @RequestMapping()
을 통해서 공통 url
을 가지도록 했다.
@RestController
@RequestMapping("/schedules")
public class ScheduleController {
private final ScheduleService scheduleService;
public ScheduleController(ScheduleService scheduleService) {
this.scheduleService = scheduleService;
}
@PostMapping
public ResponseEntity<ScheduleResponseDto> saveSchedule(@RequestBody ScheduleRequestDto requestDto) {
return new ResponseEntity<>(scheduleService.saveSchedule(requestDto), HttpStatus.CREATED);
}
}
- Post Mapping
이며, 우리는 RequestDto
를 요청 받을 것이다. 이때 파라미터를 @RequestBody
를 통하여 Json
의 데이터를 Dto
와 매핑해주도록 해주어야 한다.
- 그러한 Dto 를 서비스에게 전달해주면 된다.
2) Service
@Service
public class ScheduleServiceImpl implements ScheduleService {
private final ScheduleRepository scheduleRepository;
public ScheduleServiceImpl(ScheduleRepository scheduleRepository) {
this.scheduleRepository = scheduleRepository;
}
@Override
public ScheduleResponseDto saveSchedule(ScheduleRequestDto dto) {
// 요청받은 데이터를 schedule entity 로 변환
Schedule schedule = new Schedule(dto.getTask(), dto.getAuthor(), dto.getPwd());
return scheduleRepository.saveSchedule(schedule);
}
- 서비스의 경우 Repository
를 필드로 선언 해야한다.
- 그리고 saveSchedule()
에서는 Controller
에서 넘겨받은 dto
를 파라미터로 받고 있는 걸 볼 수 있다.
- 그리고 이 schedule
을 객체 Entity
로 만들어서 Repository
에 보내준다.
3) Repository / interface 생략
@Repository
public class JdbcTemplateScheduleRepository implements ScheduleRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcTemplateScheduleRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public ScheduleResponseDto saveSchedule(Schedule schedule) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("schedule").usingGeneratedKeyColumns("id");
String nowTimestamp = changeTimestamp();
Map<String, Object> parameters = new HashMap<>();
parameters.put("task", schedule.getTask());
parameters.put("author", schedule.getAuthor());
parameters.put("pwd", schedule.getPwd());
parameters.put("created", nowTimestamp);
parameters.put("updated", nowTimestamp);
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
return new ScheduleResponseDto(key.longValue(), schedule.getTask(), schedule.getAuthor(), nowTimestamp, nowTimestamp);
}
}
- JPA
가 아닌 JDBC
를 이용하고 있기 때문에, 필드로 jdbcTemplate
를 선언한 걸 볼 수 있다.
- 우리는 jdbcTemplate
에서 SimpleJdbcInsert
라는 인터페이스를 가져와 사용해 줄 것이다.
- 이 때, 요구사항에 지금의 시간으로 작성일과 수정일을 넣어주어야 하기 때문에 하나의 변수로 선언해주었다. (같은 시간을 공유해주기 위해서 하나의 변수로 선언해주었다.)
- 그런 후 Map
에 해당 데이터들을 넣어준다. 그런 후 jdbcInsert
에 우리가 만들었던 Map
을 넣어준다. 이 부분이 이해가 안 간다면 이전 포스팅을 읽어보고 오는 걸 추천한다.
4) 확인
- 실제로 Postman
에서 확인 하여 보니, task
와 author
, pwd
만 입력해도 적절하게 201 Created
가 뜨면서 데이터가 등록된 걸 볼 수있다. DB 에서도 아래와 같이 잘 들어가 있는걸 확인할 수 있다.
2️⃣ 전체 일정 조회(등록된 일정 불러오기)
- 다음 조건을 바탕으로 등록된 일정 목록을 전부 조회
- 수정일 (형식 : YYYY-MM-DD)
- 작성자명
- 조건 중 한 가지만을 충족하거나, 둘 다 충족을 하지 않을 수도, 두 가지를 모두 충족할 수도 있습니다.
- 수정일 기준 내림차순으로 정렬하여 조회
처음에는 이 말이 뭔지 몰랐다. 근데 튜터님한테 가서 요구사항에 대해 물어보니, 작성자명과 수정일을 기준으로 전체 데이터를 조회해라는 것이었다. 그러니까, 전체조회이지만? 조건에 맞는 데이터를 조회하라는 것이다.
수정일을 조건으로 해서 그것만 만족해도 되고 작성자 명만 만족해도 된다. 조회할 때 모든걸 다 조회하는데 여기서 Where 문만 너가 선택한 작성자명과 작성일을 기준으로 일치하는 모든 데이터를 조회하는 거다. 그래서 여기서 RequestParam 이 필요하다 !
내가 메모장으로 썼을 때 이해한 바는 위와 같았다 ^ㅡ^
1) Controller
@GetMapping
public ResponseEntity<List<ScheduleResponseDto>> findAllSchedule(
@RequestParam("author") String author,
@RequestParam("updated") String updated
) {
return new ResponseEntity<>(scheduleService.findAllSchedule(author, updated), HttpStatus.OK);
}
- 조회이니 GetMapping
인 걸 알 수 있다.
- 여기서 RequestParam
이 있는데, Query String
이라고 보면 된다.
- 우리는 해당 API
에 접근하기 위해 url
에 "scheduls/{QueryString}"
으로 접근 할 것이다 !
- 구체적으로 예시를 들자면 " scheduls? author=&updated=2025-03-20 "
이런식으로 말이다.
- 우리는 이 ?
뒤에 있는 부분 즉, QueryString(RequestParam)
을 받아온다고 생각하면 된다.
- 구글로 따지면 검색어라고 생각하면 된다 ! https://www.google.com/search?q=%검색
- 그래서 우리는 이 url
속 파라미터를 가져와서 데이터를 찾기 위해 @RequestParam
을 사용하는 것이다.
2) Service
@Override
public List<ScheduleResponseDto> findAllSchedule(String author, String updated) {
return scheduleRepository.findAllSchedule(author, updated);
}
3) Repository
@Override
public List<ScheduleResponseDto> findAllSchedule(String author, String updated) {
return jdbcTemplate.query("SELECT * FROM schedule WHERE (author = ?) OR (DATE(updated) = ?) ORDER BY updated", scheduleRowMapper(), author, updated);
}
- 그래서 우리는Where
절에 url
속 작성한 작성자와 수정일이 일치하는 데이터만 조회하는 쿼리문을 작성해주면 된다 !
4) TEST
이러면 작성자가 A 이거나 2025-03-20 일 날의 데이터를 모두 조회할 수 있다.
3️⃣ 선택 일정 조회(선택한 일정 정보 불러오기)
- 선택한 일정 단건의 정보를 조회할 수 있습니다.
- 일정의 고유 식별자(ID)를 사용하여 조회합니다.
1) Controller
- 이번에는 Param
로 id
값을 받아올 것이다.
@GetMapping("/{id}")
public ResponseEntity<ScheduleResponseDto> findScheduleById(@PathVariable Long id) {
return new ResponseEntity<>(scheduleService.findScheduleById(id), HttpStatus.OK);
}
2) Service
- id
가 같은 Entity
를 받아와서 그 Entity
를 dto
에 담아준다.
@Override
public ScheduleResponseDto findScheduleById(Long id) {
Schedule schedule = scheduleRepository.findScheduleByIdOrElseThrow(id);
return new ScheduleResponseDto(schedule);
}
3) Repository
@Override
public Schedule findScheduleByIdOrElseThrow(Long id) {
List<Schedule> result = jdbcTemplate.query("SELECT * FROM schedule WHERE id = ?", scheduleRowMapperV2(), id);
return result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exists id = " + id));
}
- 아이디아 일치 하면 조회하는 것이고, 만약 조회했을 때 result
가 비어있다면 우리는 NOT FOUND
라는 status 를 날릴 것이다.
4) TEST
- 단건 조회는 쉽다. : >
4️⃣ 선택한 일정 수정
- 선택한 일정 내용 중 할일, 작성자명 만 수정 가능
- 서버에 일정 수정을 요청할 때 비밀번호를 함께 전달합니다.
- 작성일 은 변경할 수 없으며, 수정일 은 수정 완료 시, 수정한 시점으로 변경합니다.
1) Controller
- 선택한 일정이기 때문에 id
값도 주어야하고, 수정이기 때문에 Json
형태로 들어온 데이터를 DTO
형태로 매핑해주어야한다.
@PatchMapping("/{id}")
public ResponseEntity<ScheduleResponseDto> editSchedule(
@PathVariable Long id,
@RequestBody ScheduleRequestDto dto
) {
return new ResponseEntity<>(scheduleService.editSchedule(id, dto.getTask(), dto.getAuthor(), dto.getPwd()), HttpStatus.OK);
}
2) Service
@Transactional
@Override
public ScheduleResponseDto editSchedule(Long id, String task, String author, String pwd) {
// 먼저 pwd 맞는지 확인
if (pwd.isEmpty() || (StringUtils.isEmpty(task) && StringUtils.isEmpty(author))) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
// id 가 실제 DB 있는지 없는지 확인 = > 있으면 계속 진행. / 없으면 Notfound
scheduleRepository.findScheduleByIdOrElseThrow(id);
// pwd 올바른지 아닌지 확인
if (!scheduleRepository.findScheduleByPwd(id, pwd)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Does not exist id =" + id);
}
scheduleRepository.editSchedule(id, task, author);
return new ScheduleResponseDto(scheduleRepository.findScheduleByIdOrElseThrow(id));
}
- 주석대로 먼저 url
속 id
값이 실제로 존재하는지 확인 해 보아야 한다.
- 그리고 pwd
가 올바른지 확인해 보아야 하고,
- pwd
올바르면 우리는 edit
해야한다.
- 그런 후 수정된 DB
에서 Entity
를 가져와서 Dto
에 담아주면 된다.
나중에는 아래부분을 고려해보아야 한다!
Entity 라는 객체로 전달 받는 걸 고려
3) Repository
@Override
public int editSchedule(Long id, String task, String author) {
return jdbcTemplate.update("UPDATE schedule " +
"SET task = CASE\n" +
" WHEN ? is not null THEN ?\n" +
" ELSE task END,\n" +
" author = CASE\n" +
" WHEN ? is not null THEN ?\n" +
" ELSE author END,\n" +
" updated = ? \n " +
"WHERE id = ?", task, task, author, author, changeTimestamp(), id);
}
복잡해보일 수 있는데 쿼리문은 아래와 같다.
update schedule
set task = CASE
WHEN ? is not null THEN ?
ELSE task END,
author = CASE
WHEN ? is not null THEN ?
ELSE author END
WHERE id = ?
4) Test
5️⃣ 선택한 일정 삭제
- 선택한 일정을 삭제할 수 있습니다.
- 서버에 일정 수정을 요청할 때 비밀번호를 함께 전달합니다.
1) Controller
- 여기서 id
값을 받아와야 하며, Return
값은 <Void>
로 주면 된다. Void
가 뭔지 모른다면 이전 블로그 글을 찾고 해보면 좋을 거 같다 !
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteSchedule(
@PathVariable Long id,
@RequestBody ScheduleRequestDto dto
) {
// 실제 DB 에 반영하도록 해야됨.
scheduleService.deleteSchedule(id,dto.getPwd());
return new ResponseEntity<>(HttpStatus.OK);
}
2) Service
@Transactional
@Override
public void deleteSchedule(Long id, String pwd) {
// 비밀번호 검증을 하고, 삭제가 가능하게 !
if (!scheduleRepository.findScheduleByPwd(id, pwd)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Does not exist id =" + id);
}
scheduleRepository.deleteSchedule(id);
}
- 트랜잭션으로 했는데 추후에 고쳐보아야 겠다.
3) Repository
@Override
public void deleteSchedule(Long id) {
jdbcTemplate.update("DELETE FROM schedule WHERE id = ?", id);
}
- 이런식의 쿼리만 작성해주면 된다.
- 여기 Service
에서 언급 안했던 함수가 있는데 바로 아래 함수이다.
- 해당 함수는 pwd
검증을 위한 함수라고 보면 된다.
public boolean findScheduleByPwd(Long id, String pwd) {
List<Schedule> result = jdbcTemplate.query("SELECT * FROM schedule WHERE id = ? AND pwd = ?", scheduleRowMapperV2(), id, pwd);
return result.stream().findAny().isPresent();
}
4) TEST
200 OK
떠 있는 걸 확인할 수 있다 !
✅ 오늘의 회고
- 일단 과제는 필수기능 까지 완성 했는데, 묘하게 마음에 들지 않는다.
- 아직은 허접한 느낌? 유효성 검증과 예외 처리에 대해서도 적절하게 처리를 해 보아야 하는데 그건 이번 주말이나 다음주 쯤에 도전해볼 생각이다.
- 그리고, CodeKATA 에서 새로운 크롬 확장프로그램을 이용하게 되었는데 너무 편하다 ㅠㅠ ! 팀원분이 알려주셔서 해당 확장프로그램을 이용해서 Git Commit 을 하게 되었는데 진짜 코드카타 시간이 10분은 줄어든 거 같아서 좋다 : >
'백엔드 부트캠프 > TIL' 카테고리의 다른 글
[내일배움캠프Spring-26일차] JDBC 페이징 (2) | 2025.03.25 |
---|---|
[내일배움캠프Spring-25일차] 일정 관리 과제 도전 Lv3 (0) | 2025.03.24 |
[내일배움캠프Spring-23일차] JDBC 를 이용한 CRUD 이해하기 [MySql] (0) | 2025.03.20 |
[내일배움캠프Spring-22일차] Layer 이해하기 (3) | 2025.03.19 |
[내일배움캠프Spring-21일차] Spring 입문 주차 2 (0) | 2025.03.18 |