💡스프링으로 일정관리 API 만들기
//페이징 처리 된 일정 조회
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 형식으로 변경//일정 업데이트. 이름, 내용 각각 업데이트 및 트랜잭션 처리
@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("비밀번호 오류","비밀번호가 일치하지 않습니다.");
}
}
//하나의 일정 추가
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());
}
//특정 일정 하나 조회
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());
}
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()));
}
}
문제점
Timestamp 타입의 시간 정보와 실제 출력되는 시간 정보가 일치하지 않음DB에 표시되는 시간 : "2025-05-01 11:00:00"
출력되는 시간 : "2025-05-01T20:00:00" //9시간 늦음
원인
해결 방법 1
serverTimezone=UTC를 serverTimezone=Asia/Seoul로 변경한다.해결 방법2
Calendar kstTime = Calendar.getInstance(TimeZone.getTimeZone("Asia/Seoul"));
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";
문제점
ResultSet 의 next() 메서드로 조건문 처리하게 되면 정상적인 상황에서도 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("없는 요청","해당 정보는 없습니다.");
}