-
[과제] 일정 관리 앱 만들기 도전 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
'내일배움캠프 > 과제' 카테고리의 다른 글
[과제] 일정 관리 앱 만들기 도전 Lv5, Lv6 (0) 2025.03.25 [과제] 일정 관리 앱 만들기 도전 Lv4 (0) 2025.03.25 [과제] 일정 관리 앱 만들기 필수 Lv2 (0) 2025.03.25 [과제] 일정 관리 앱 만들기 필수 Lv1 (0) 2025.03.25 [과제] 키오스크 도전 Lv2 (2) 2025.03.13 - 작성자와 일정의 연결