ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [과제] 일정 관리 앱 만들기 도전 Lv3
    내일배움캠프/과제 2025. 3. 25. 18:01

    ✅ Lv3. 연관 관계 설정

    1️⃣ 과제 조건

    • 작성자와 일정의 연결
      • 설명
        • 동명이인의 작성자가 있어 어떤 작성자가 등록한 ‘할 일’인지 구별할 수 없음
        • 작성자를 할 일과 분리해서 관리합니다.
        • 작성자 테이블을 생성하고 일정 테이블에 FK를 생성해 연관관계를 설정해 봅니다.
      • 조건
        • 작성자 테이블은 이름 외에 이메일, 등록일, 수정일 정보를 가지고 있습니다.
          • 작성자의 정보는 추가로 받을 수 있습니다.(조건만 만족한다면 다른 데이터 추가 가능)
        • 작성자의 고유 식별자를 통해 일정이 검색이 될 수 있도록 전체 일정 조회 코드 수정.
        • 작성자의 고유 식별자가 일정 테이블의 외래키가 될 수 있도록 합니다.

    2️⃣ 작성자 등록

    먼저 작성자 일정과 작성자를 연결하려면 작성자 정보가 있어야해서 작성자를 등록해준다.

     

    1. Writer Entity 작성

    public class Writer {
    
        private Long id; // PK
    
        private String name; // 이름
    
        private String email; // 이메일
    
        private LocalDateTime regDt; // 등록일
    
        private LocalDateTime modDt; // 수정일
    }

     

    2. 작성자 저장을 위한 데이터 요청 Dto 작성

    이메일은 형식을 확인하기 위해 @Email 사용

    public class WriterReqDto {
    
        @Schema(description = "이름")
        @NotBlank
        private String name;
    
        @Schema(description = "이메일")
        @NotBlank @Email
        private String email;
    }

     

    3. 작성자 반환을 위한 응답 Dto 작성

    public class WriterResDto {
    
        @Schema(description = "PK")
        private Long id;
    
        @Schema(description = "이름")
        private String name;
    
        @Schema(description = "이메일")
        private String email;
    
        @Schema(description = "등록일")
        private String regDt;
    
        @Schema(description = "수정일")
        private String modDt;
    }

     

    4. WriterController

    @PostMapping
    public BaseResponse<WriterResDto> saveWriter(@RequestBody @Valid WriterReqDto dto) {
        return BaseResponse.from(writerService.saveWriter(dto));
    }

     

     

    5. Service

    public WriterResDto saveWriter(WriterReqDto dto) {
        LocalDateTime now = LocalDateTime.now(); // 현재 시각
        Writer writer = new Writer(dto.getName(), dto.getEmail(), now, now);
        return new WriterResDto(writerRepository.saveWriter(writer));
    }

     

    6. Repository

    public Writer saveWriter(Writer writer) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("writer").usingGeneratedKeyColumns("id");
    
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", writer.getName());
        parameters.put("email", writer.getEmail());
        parameters.put("regDt", writer.getRegDt());
        parameters.put("modDt", writer.getModDt());
    
        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); // PK Return
        writer.setId(key.longValue());
    
        return writer;
    }

     

    3️⃣ 작성자 상세 조회

    1.  Controller

    @GetMapping("/{id}")
    public BaseResponse<WriterResDto> findWriterById(@Schema(description = "작성자 PK") @PathVariable Long id) {
        return BaseResponse.from(writerService.findWriterById(id));
    }

     

    2. Service

     public WriterResDto findWriterById(Long id) {
        Writer writer = writerRepository.findWriterById(id)
                .orElseThrow(() -> new CustomException(CommonExceptionResultMessage.NOT_FOUND, "회원 조회 실패: id: " + id + " 에 해당하는 회원 없음"));
    
        return new WriterResDto(writer);
    }

     

    3. Repository

    public Optional<Writer> findWriterById(Long id) {
        StringBuilder query = new StringBuilder()
                .append("SELECT id, name, email, reg_dt, mod_dt FROM writer \n")
                .append(" WHERE id = ? \n");
    
        List<Writer> resultList = jdbcTemplate.query(query.toString(), this.writerRowMapper(), id);
        return resultList.stream().findAny();
    }

     

    4️⃣ 일정 등록 시 작성자 정보 연결

    1. Schedule Entity에 작성자 정보 연결

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

     

     

    2. 일정 등록 dto에 작성자 email 정보 입력 받도록수정

    public class ScheduleReqDto {
    
        @Schema(description = "일정 내용")
        @NotBlank
        private String schedule;
    
        @Schema(description = "비밀번호")
        @NotBlank
        private String password;
    
        @Schema(description = "작성자 이메일")
        @NotBlank @Email
        private String email;
    }

     

    3.Service

    작성자 email을 기반으로 writer 정보를 조회 후 schedule entity에 set

    public ScheduleResDto saveSchedule(ScheduleReqDto dto) {
        Writer writer = writerRepository.findWriterByEmail(dto.getEmail())
                .orElseThrow(() -> new CustomException(CommonExceptionResultMessage.NOT_FOUND, "회원 조회 실패: email: " + dto.getEmail() + " 에 해당하는 회원 없음"));
    
        LocalDateTime now = LocalDateTime.now(); // 현재 시각
        String encodePw = passwordEncoder.encode(dto.getPassword()); // 비밀번호 암호화
    
        Schedule schedule = new Schedule(dto.getSchedule(), encodePw, now, now, writer);
        return new ScheduleResDto(scheduleRepository.saveSchedule(schedule));
    }

     

    4.Repository

    작성자 id 도 같이 insert

    public Schedule saveSchedule(Schedule schedule) {
        ...
        
        parameters.put("writer_id", schedule.getWriter().getId()); // 작성자 PK 저장
    
        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); // PK Return
        schedule.setId(key.longValue());
    
        return schedule;
    }

     

    5️⃣ 일정 조회 변경

     

    1. 응답 Dto변경, 작성자 명과 작성자 이메일 추가

    public class ScheduleResDto {
    
        @Schema(description = "PK")
        private Long id;
    
        @Schema(description = "일정 내용")
        private String schedule;
    
        @Schema(description = "작성자 명")
        private String writerNm;
    
        @Schema(description = "작성자 이메일")
        private String email;
    
        @Schema(description = "등록일")
        private String regDt;
    
        @Schema(description = "수정일")
        private String modDt;
    }

     

    2. Controller 기존에는 목록 조회 조건에 작성자 명을 조건으로 두었지만 작성자 PK값인 id를 조건으로 변경

    @GetMapping
    public BaseResponse<List<ScheduleResDto>> findAllSchedule(
            @RequestParam(required = false) Long writerId
            , @RequestParam(required = false) String modDt
    ) {
        return BaseResponse.from(scheduleService.findAllSchedule(writerId, modDt));
    }

     

    3. Service

    public List<ScheduleResDto> findAllSchedule(Long writerId, String modDt) {
        return scheduleRepository.findAllSchedule(writerId, modDt).stream()
                .map(ScheduleResDto::new) // 일정 목록 조회 후  mapping
                .collect(Collectors.toList());
    }

     

    4. Repository

    일정과 작성자를 외래키로 연결 하였기 때문에 Join 문을 활용하여 조회

    public List<Schedule> findAllSchedule(Long writerId, String modDt) {
        List<Object> params = new ArrayList<>();
    
        StringBuilder query = new StringBuilder()
                .append("SELECT a.id, a.schedule, a.password, a.reg_dt, a.mod_dt\n")
                .append(" ,b.id AS writer_id, b.email, b.name, b.reg_dt as writer_reg_dt, b.mod_dt as writer_mod_dt\n")
                .append(" FROM schedule a JOIN writer b ON a.writer_id = b.id\n")
                .append(" WHERE a.del_dt IS NULL \n");
    
        if (writerId != null) { // 작성자 PK 검색조건이 있을 경우
            query.append(" AND b.id = ? \n");
            params.add(writerId);
        }
    
        if (StringUtils.isNotBlank(modDt)) { // 수정일 검색조건이 있을 경우
            query.append(" AND DATE(a.mod_dt) = ? \n");
            params.add(modDt);
        }
    
        query.append(" ORDER BY mod_dt DESC");
    
        return jdbcTemplate.query(query.toString(), params.toArray(), this.scheduleRowMapper());
    }
    
     public Optional<Schedule> findScheduleById(Long id) {
        StringBuilder query = new StringBuilder()
                .append("SELECT a.id, a.schedule, a.password, a.reg_dt, a.mod_dt\n")
                .append(" ,b.id AS writer_id, b.email, b.name, b.reg_dt as writer_reg_dt, b.mod_dt as writer_mod_dt\n")
                .append(" FROM schedule a JOIN writer b ON a.writer_id = b.id\n")
                .append(" WHERE a.id = ? AND a.del_dt IS NULL \n");
    
        List<Schedule> resultList = jdbcTemplate.query(query.toString(), this.scheduleRowMapper(), id);
        return resultList.stream().findAny();
    }

     

    6️⃣ 일정 수정 변경

     

    1. 작성자 명을 일정 수정시 수정하지 않기에  일정 수정용 Dto 생성

    public class ScheduleModDto {
        @Schema(description = "일정 내용")
        @NotBlank
        private String schedule;
    
        @Schema(description = "비밀번호")
        @NotBlank
        private String password;
    }

     

    2.Controller

    @PutMapping("/{id}")
    public BaseResponse<ScheduleResDto> updateSchedule(
            @Schema(description = "일정 PK") @PathVariable Long id,
            @RequestBody @Valid ScheduleModDto dto) {
        return BaseResponse.from(scheduleService.updateSchedule(id, dto));
    }

     

    3.Service

    public ScheduleResDto updateSchedule(Long id, ScheduleModDto dto) {
        // 유효한 일정인지 조회
        Schedule schedule = this.validSchedule(id, dto.getPassword());
    
        // 새로운 정보 update
        schedule.setSchedule(dto.getSchedule()); // 일정
        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);
    }

     

    4.Repository

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

     

     

     7️⃣ 트러블슈팅

    작성자를 등록할 때 email을 중복 검사하지 않으니 일정 등록 시 작성자를 특정할 수 없었다.

     

    그래서 email 컬럼에 unique 를 설정해주고 작성자 등록 시 email 중복 검사 로직 추가

     

    1. Service

    public WriterResDto saveWriter(WriterReqDto dto) {
        Optional<Writer> writerOpt = writerRepository.findWriterByEmail(dto.getEmail());
    
        // 해당 이메일로 가입된 회원이 있을 경우
        if (writerOpt.isPresent()) {
            throw new CustomException(CommonExceptionResultMessage.DUPLICATE_FAIL, "이미 사용 중인 이메일입니다.");
        }
    
        LocalDateTime now = LocalDateTime.now(); // 현재 시각
        String encodePw = passwordEncoder.encode(dto.getPassword()); // 비밀번호 암호화
    
        Writer writer = new Writer(dto.getName(), dto.getEmail(), encodePw, now, now);
        return new WriterResDto(writerRepository.saveWriter(writer));
    }

     

    2. Repository

    public Optional<Writer> findWriterByEmail(String email) {
        StringBuilder query = new StringBuilder()
                .append("SELECT id, name, email, password, reg_dt, mod_dt FROM writer \n")
                .append(" WHERE email = ? \n");
    
        List<Writer> resultList = jdbcTemplate.query(query.toString(), this.writerRowMapper(), email);
        return resultList.stream().findAny();
    }

     

    Optional로 만약 존재할 경우 Exception throw

     

     

Designed by Tistory.