ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [과제] 일정 관리 앱 만들기 필수 Lv2
    내일배움캠프/과제 2025. 3. 25. 13:09

    ✅ Lv2. 일정 수정 및 삭제

    1️⃣ 과제 조건

    • 선택한 일정 수정
      • 선택한 일정 내용 중 할일, 작성자명 만 수정 가능
        • 서버에 일정 수정을 요청할 때 비밀번호를 함께 전달합니다.
        • 작성일 은 변경할 수 없으며, 수정일 은 수정 완료 시, 수정한 시점으로 변경합니다.
    • 선택한 일정 삭제
      • 선택한 일정을 삭제할 수 있습니다.
        • 서버에 일정 수정을 요청할 때 비밀번호를 함께 전달합니다.

     

    2️⃣ 트러블 슈팅

    이번엔 먼저 수정사항이 생각이 났다.

    Repository는 오직 DB와의 연결만을 구현하도록 하고 다른 로직을 제외하고 싶다는 생각이 들었다.

     

    1. Repository

    기존에는 ScheduleResDto 를 return 해주었는데 Entity를 return 해주도록 변경하였고, findScheduleById에서 데이터가 없을 시 throw 해주던 것을 지웠다.

    public Schedule saveSchedule(Schedule schedule) {
      
      	...
        
        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); // PK Return
        schedule.setId(key.longValue());
    
        return schedule;
    }
    
    @Override
    public List<Schedule> findAllSchedule(String modDt, String regNm) {
    
    	...
    
        return jdbcTemplate.query(query.toString(), params.toArray(), this.scheduleRowMapper());
    }
    
    @Override
    public Optional<Schedule> findScheduleById(Long id) {
       
        ...
       
        return resultList.stream().findAny();
    }

     

    2. Service

    기존에는 Repository 에서 ResDto로 변환하여 준것을 그대로 return 해주었는데 서비스는 로직 담당이므로, Repository에서 하던 서비스들을 Service로 다 옮겼다.

    @Override
    public ScheduleResDto saveSchedule(ScheduleReqDto dto) {
        LocalDateTime now = LocalDateTime.now(); // 현재 시각
        String encodePw = passwordEncoder.encode(dto.getPassword()); // 비밀번호 암호화
    
        Schedule schedule = new Schedule(dto.getSchedule(), dto.getRegNm(), encodePw, now, now);
        return new ScheduleResDto(scheduleRepository.saveSchedule(schedule));
    }
    
    @Override
    public List<ScheduleResDto> findAllSchedule(String modDt, String regNm) {
        return scheduleRepository.findAllSchedule(modDt, regNm).stream()
                .map(ScheduleResDto::new)
                .collect(Collectors.toList());
    }
    
    @Override
    public ScheduleResDto findScheduleById(Long id) {
        return scheduleRepository.findScheduleById(id)
                .map(ScheduleResDto::new)
                .orElseThrow(() -> new CustomException(CommonExceptionResultMessage.NOT_FOUND, "일정 조회 실패: ID " + id + " 에 해당하는 일정 없음"));
    }

     

    3️⃣ 일정 수정

    1. Controller

    일정 pk값과, 수정할 일정 데이터를 받음

    @PutMapping("/{id}")
    public BaseResponse<ScheduleResDto> updateSchedule(@PathVariable Long id,
            @RequestBody @Valid ScheduleReqDto dto) {
        return BaseResponse.from(scheduleService.updateSchedule(id, dto));
    }

     

    2. Service

    기존에 있던 findScheduleById를 활용하여 유효한 일정인지 먼저 조회 후

    passwordEncoder에 있는 matches 함수로 비밀번호가 일치한지 확인

     

    그 후 새로운 정보로 update 해주고 데이터를 update

    public ScheduleResDto updateSchedule(Long id, ScheduleReqDto dto) {
        // 유효한 일정인지 조회
        Schedule schedule = scheduleRepository.findScheduleById(id)
                .orElseThrow(() -> new CustomException(CommonExceptionResultMessage.NOT_FOUND, "일정 조회 실패: ID " + id + " 에 해당하는 일정 없음"));
    
        String pw = schedule.getPassword();
    
        // 비밀번호 검사
        if (!passwordEncoder.matches(dto.getPassword(), pw)) {
            throw new CustomException(CommonExceptionResultMessage.PW_MISMATCH);
        }
    
        // 새로운 정보 update
        schedule.setSchedule(dto.getSchedule()); // 일정
        schedule.setRegNm(dto.getRegNm()); // 작성자명
        schedule.setModDt(LocalDateTime.now()); // 수정 시간
    
        // 일정 수정
        int result = scheduleRepository.updateSchedule(schedule);
        if (result == 0) { // update 된 row 가 없으면 throw
            throw new CustomException(CommonExceptionResultMessage.DB_FAIL);
        }
    
        return new ScheduleResDto(schedule);
    }

     

    3. Repository

    public int updateSchedule(Schedule schedule) {
        StringBuilder query = new StringBuilder()
                .append("UPDATE schedule \n")
                .append(" SET schedule = ? \n")
                .append("   , reg_nm = ? \n")
                .append("   , mod_dt = ? \n")
                .append(" WHERE id = ?");
    
        List<Object> params = new ArrayList<>();
        params.add(schedule.getSchedule());
        params.add(schedule.getRegNm());
        params.add(schedule.getModDt());
        params.add(schedule.getId());
    
        return jdbcTemplate.update(query.toString(), params.toArray()); // update row 수 return
    }

     

    4️⃣ 일정 삭제

    1. Entity 에 삭제 여부 delDt 추가

    public class Schedule {
    
        private Long id; // PK
    
        private String schedule; // 일정 내용
    
        private String regNm; // 작성자 명
    
        private String password; // 비밀번호
    
        private LocalDateTime regDt; // 작성일
    
        private LocalDateTime modDt; // 수정일
    
        private LocalDateTime delDt; // 삭제일
    }

     

    2. 일정 삭제를 위한 DelDto

    @Data
    public class ScheduleDelDto {
        @Schema(description = "비밀번호")
        @NotBlank
        private String password;
    }

     

    3. Controller, id와 Dto를 받음

    @DeleteMapping("/{id}")
    public BaseResponse<Boolean> deleteSchedule(
            @PathVariable Long id,
            @RequestBody @Valid ScheduleDelDto dto) {
        scheduleService.deleteSchedule(id, dto);
        return BaseResponse.from(true);
    }

     

    4. Service

    똑같이 비밀번호를 검증하고 Delete 처리

    public void deleteSchedule(Long id, ScheduleDelDto dto) {
        // 유효한 일정인지 조회
        Schedule schedule = scheduleRepository.findScheduleById(id)
                .orElseThrow(() -> new CustomException(CommonExceptionResultMessage.NOT_FOUND, "일정 조회 실패: ID " + id + " 에 해당하는 일정 없음"));
    
        String pw = schedule.getPassword();
    
        // 비밀번호 검사
        if (!passwordEncoder.matches(dto.getPassword(), pw)) {
            throw new CustomException(CommonExceptionResultMessage.PW_MISMATCH);
        }
    
        schedule.setDelDt(LocalDateTime.now()); // 삭제 시간
    
        int result = scheduleRepository.deleteSchedule(schedule); // 일정 삭제
        if (result == 0) { // update 된 row 가 없으면 throw
            throw new CustomException(CommonExceptionResultMessage.DB_FAIL);
        }
    }

     

    5. Repository

    데이터를 실제로 Delete 하지 않고 삭제 여부 컬럼을 추가 해주어 soft 삭제하는 방식을 사용

    public int deleteSchedule(Schedule schedule) {
        StringBuilder query = new StringBuilder()
                .append("UPDATE schedule \n")
                .append(" SET del_dt = ? \n")
                .append(" WHERE id = ? AND del_dt IS NULL");
    
        List<Object> params = new ArrayList<>();
        params.add(schedule.getDelDt());
        params.add(schedule.getId());
    
        return jdbcTemplate.update(query.toString(), params.toArray()); // delete row 수 return
    }

    5️⃣ 리팩토링

    아래의 코드가 삭제랑 수정할 때 중복되는 코드이기에 validSchedule로 묶어주었음.

     // 유효한 일정인지 조회
    Schedule schedule = scheduleRepository.findScheduleById(id)
        .orElseThrow(() -> new CustomException(CommonExceptionResultMessage.NOT_FOUND, "일정 조회 실패: ID " + id + " 에 해당하는 일정 없음"));
    
    String pw = schedule.getPassword();
    
    // 비밀번호 검사
    if (!passwordEncoder.matches(dto.getPassword(), pw)) {
        throw new CustomException(CommonExceptionResultMessage.PW_MISMATCH);
    }

     

    private Schedule validSchedule(Long id, String inputPw) {
        // 유효한 일정인지 조회
        Schedule schedule = scheduleRepository.findScheduleById(id)
                .orElseThrow(() -> new CustomException(CommonExceptionResultMessage.NOT_FOUND, "일정 조회 실패: ID " + id + " 에 해당하는 일정 없음"));
    
        String pw = schedule.getPassword();
    
        // 비밀번호 검사
        if (!passwordEncoder.matches(inputPw, pw)) {
            throw new CustomException(CommonExceptionResultMessage.PW_MISMATCH);
        }
    
        return schedule;
    }

     

Designed by Tistory.