자바 미니 프로젝트 - 일정관리 API

Zyoon·2025년 5월 13일

미니프로젝트

목록 보기
7/35
post-thumbnail

💡스프링으로 일정관리 API 만들기


설계

  • 일정 게시글 등록, 조회, 수정, 삭제 기능
  • 전체 일정 조회, 특정 일정 조회
  • 일정 등록 시 비밀번호 등록.
  • 수정, 삭제시 비밀번호 일치여부 확인
  • 전체 조회는 수정일 기준 내림차순으로 조회. 5개 까지 조회가능(페이징 처리)

주요 코드 정리


리스트 조회

//페이징 처리 된 일정 조회
public List<CalendarInfoDto> getPagedCalendarListBySearch(SearchDto searchDto){

    verifyCalendarListBySearchTime(searchDto);

    return repository.selectPagedCalendarListBySearch(searchDto);
}

//시간 정보 검증하여 조건 검색
private void verifyCalendarListBySearchTime(SearchDto searchDto){
    if(searchDto.getSearchTime().isPresent()){
        String searchTime = searchDto.getSearchTime().get();
        if(searchTime.contains("-")){
            String[] splitDate = searchTime.split("-");

            searchDto.setFirstTime(changeStringToLocalDateTime((splitDate[0])));
            searchDto.setLastTime(changeStringToLocalDateTime((splitDate[1])).plusDays(1));

        }else {
            searchDto.setFirstTime(changeStringToLocalDateTime((searchTime)));
        }
    }
}

//String 을 LocalDateTime 으로 변경
private LocalDateTime changeStringToLocalDateTime(String dateStr){
    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd");
    LocalDate ld = LocalDate.parse(dateStr, fmt);
    return ld.atStartOfDay();
}
  • yyyymmdd-yyyymmdd 형식으로 들어온 String 데이터를 LocalDataTime 형식으로 변경
  • 변경된 데이터 타입을 SearchDto 에 담아서 DB 조회

일정 업데이트 및 삭제

//일정 업데이트. 이름, 내용 각각 업데이트 및 트랜잭션 처리
@Transactional
public void updateOneCalendarById(CalendarInfoDto dto){

    if(!repository.verifyPasswordById(dto)){
        throw new CustomException("password","비밀번호가 일치하지 않습니다.");
    }
    if(dto.getName() != null && !dto.getName().isEmpty()){
        dto.setMemberId(repository.selectOneMemberIdById(dto));
        repository.updateOneNameById(dto);
    }
    if(dto.getContent() != null && !dto.getContent().isEmpty()){
        repository.updateOneContentById(dto);
    }
}

//일정 삭제
public void deleteOneCalendarById(CalendarInfoDto dto){

    if(repository.verifyPasswordById(dto)){
        System.out.println(dto);
        repository.deleteOneCalendarById(dto);
    }else {
        throw new CustomException("비밀번호 오류","비밀번호가 일치하지 않습니다.");
    }
}
  • namecontent 가 다른 테이블에 있기 때문에 각각 처리
  • 둘 중 하나가 DB 업데이트에 문제가 있을 시 하나만 업데이트 되는 것을 방지하기 위하여 트랜잭션 처리
  • 수정, 삭제 시 입력한 비밀번호를 DB 정보와 확인하여 일치할 경우에만 로직 수행

일정 추가, 수정 , 삭제

//하나의 일정 추가 
public int insertOneCalendar(CalendarInfoDto dto){
    String sql = "INSERT INTO calendar_db.calendar_challenge (memberId, content, password) VALUES (?, ?, ?)";
    return jdbcTemplate.update(sql, dto.getMemberId(), dto.getContent(), dto.getPassword());
}

//일정 내용 업데이트
public boolean updateOneContentById(CalendarInfoDto dto){
    String sql = "UPDATE calendar_db.calendar_challenge SET content = ?, modifyDate = NOW() where id = ?";
    return jdbcTemplate.update(sql, dto.getContent(), dto.getId()) == 1;
}

//일정 삭제
public int deleteOneCalendarById(CalendarInfoDto dto){
    String sql = "DELETE FROM calendar_db.calendar_challenge WHERE id = ?";
    return jdbcTemplate.update(sql, dto.getId());
}
  • JDBCTemplate 활용하여 일정 추가, 수정, 삭제

특정 일정 조회

    //특정 일정 하나 조회
    public CalendarInfoDto selectOneCalendarById(CalendarInfoDto dto){

    String sql = "SELECT c.id,c.memberId ,name, content,c.enrollDate, c.modifyDate \n" +
                "FROM calendar_db.calendar_challenge AS c \n" +
                "JOIN calendar_db.member AS m ON c.memberId = m.id\n" +
                "WHERE c.id = ?";

    return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
        CalendarInfoDto result = new CalendarInfoDto();
        result.setContent(rs.getString("content"));
        result.setName(rs.getString("name"));
        result.setEnrollDate(rs.getTimestamp("enrollDate").toLocalDateTime());
        result.setMemberId(rs.getInt("memberId"));
        result.setModifyDate(rs.getTimestamp("modifyDate").toLocalDateTime());
        return result;
    }, dto.getId());
}
  • JDBCTemplate.queryForObject 활용
  • ResultSet 을 람다식으로 실행

비밀번호 검증

//입력된 비밀번호와 DB 비밀번호 검증
public boolean verifyPasswordById(CalendarInfoDto dto){

    String sql = "SELECT password FROM calendar_db.calendar_challenge WHERE id = ?";

    String password = jdbcTemplate.queryForObject(sql, String.class, dto.getId());

    return password.equals(dto.getPassword());
}
  • 입력받은 비밀번호와 DB 의 비밀번호 비교하여 boolean 값 리턴

예외 처리

//예외 처리 클래스
public class CustomException extends RuntimeException{
    private final String errorCode;

    public CustomException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }
    public String getErrorCode() {
        return errorCode;
    }
}

//ExceptionHandler
@RestControllerAdvice
public class CalendarExceptionHandler extends RuntimeException{

    //에러 처리
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ExceptionDto> handleCustomException(CustomException e) {
        return ResponseEntity.badRequest().body(new ExceptionDto(e.getErrorCode(), e.getMessage()));
    }
}

어려웠던 부분


DB에서 받은 데이터의 시간 변경

문제점

  • DB에서 저장되어있는 Timestamp 타입의 시간 정보와 실제 출력되는 시간 정보가 일치하지 않음
DB에 표시되는 시간 : "2025-05-01 11:00:00"
출력되는 시간 : "2025-05-01T20:00:00" //9시간 늦음

원인

  • DB의 타임존은 별도의 설정 없이 SYSTEM을 따르고 있다. 즉, 한국 시간 기준으로 시간이 저장된다.
  • 하지만 application.properties 의 타임존은 UTC 를 따르고 있다.
  • 이 타임존이 다르기 때문에 데이터 출력시에 시간차가 발생하는 것이다.

해결 방법 1

  • application.properties에서 타임존을 변경
  • serverTimezone=UTC를 serverTimezone=Asia/Seoul로 변경한다.

해결 방법2

  • Repository 에서 타임존 변경 변수 생성
Calendar kstTime = Calendar.getInstance(TimeZone.getTimeZone("Asia/Seoul"));
  • 해당 변수를 ResultSet 을 불러오는 곳에 추가하여 직접 변경해준다.
ResultSet rs = stmt.executeQuery();
if (rs.next()){
    dto.setEnrollDate(rs.getTimestamp("enrollDate", kstTime).toLocalDateTime());
    dto.setModifyDate(rs.getTimestamp("modifyDate", kstTime).toLocalDateTime());
}

해결 방법3

  • MySql Connection 생성 시, URL 부분 변경
//UTC로 적용된 URL
String URL = "jdbc:mysql://localhost:3306/calendar_db?serverTimezone=UTC";

//한국 시간으로 적용된 URL
String URL = "jdbc:mysql://localhost:3306/calendar_db?serverTimezone=Asia/Seoul";
  • 해당 URL 데이터의 UTC 부분을 Asia/Seoul 로 변경해준다.

DB 조회결과 없을 시 예외 처리

문제점

  • ResultSetnext() 메서드로 조건문 처리하게 되면 정상적인 상황에서도 DB 한 줄이 누락되게 된다.
ResultSet rs = stmt.executeQuery();
if(!rs.next()){
	throw new CustomException("없는 요청","해당 정보는 없습니다.");
}
while (rs.next()) {
  CalendarInfoDto dtoTemp = new CalendarInfoDto();
  dtoTemp.setId(rs.getInt("id"));
  dtoTemp.setMemberId(rs.getInt("memberId"));
  dtoList.add(dtoTemp);
}

원인

  • next() 메거드는 데이터 존재 여부를 확인하는 용도로도 쓰이지만, 메서드 사용 시 데이터를 가져오는 ‘커서’를 다음으로 이동하게 된다.
  • 즉, if문으로 판별하게 되면 조건문을 거치지 않더라도 next() 메서드를 소모하게 되면서 DB 의 첫 줄 부터가 아닌 그 다음줄부터 데이터를 읽어오게 된다.

해결

  • 조회하는 데이터가 단일 데이터일 경우 if-else 문을 위치만 변경해준다.
if(!rs.next()){
	dto.setName(rs.getString("name"));
}else{
	throw new CustomException("없는 요청","해당 정보는 없습니다.");
}
  • 조회하는 데이터가 List 같은 경우일 경우 list.empty() 를 활용해 준다.
while (rs.next()) {
    CalendarInfoDto dtoTemp = new CalendarInfoDto();
    dtoTemp.setName(rs.getString("name"));
    dtoList.add(dtoTemp);
}
if(dtoList.isEmpty()){
    throw new CustomException("없는 요청","해당 정보는 없습니다.");
}

Github & API 명세서

📓일정관리 API Github

📙일정관리 API 명세서

profile
기어 올라가는 개발

0개의 댓글