Develop/Spring

일정관리 앱 트러블 슈팅

mabubsoragodong 2025. 2. 4. 11:13

1. 날짜 변수 자료형

작성일과 수정일은 날짜로 저장해야하는 자료형이다. 처음에는 자바에서 날짜로 된 자료형을 다루려면 어떻게 해야하는지 몰랐다. 그래서 인텔리제이 탭키로 날짜관련 자료형을 찾아보니 LocalDateTime이라는 자료형이 있어서 무턱대고 그걸 사용했었다. 그랬더니 내가 원하는 형식은 년도-달-일 형식의 날짜까지만 나오는 형식이었는데 응답받은 json을 보니 시간과 초까지 저장되어 있었다. 그래서 이를 해결하기 위해 

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDateTime createdAt;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDateTime updatedAt;

 ResponseDto의 필드에서 Json형식을 맞추도록 하였었다. 하지만 날짜관련 자료형을 찾아보다 LocalDateTime 말고 LocalDate를 알게되었다.

자바에는 다음과 같은 시간 과련 패키지가 있다.

java.time 패키지

자바 8 버전에서 공개된 java.time 패키지에는 다음과 같은 클래스들을 제공하고 있다.

Instant : 타임스탬프 ( 기계용 시간, EPOCH )

LocalDate : 시간이 없는 날짜 ( 타임존에 대한 참조가 없음 )

LocalTime : 날짜가 없는 시간 ( 타임존에 대한 참조가 없음 )

LocalDateTime : 날짜와 시간을 결합한 클래스

ZonedDateTime : UTC 에서 시간대 및 타임존에 대한 참조를 가진 시간과 날짜를 다루는 클래스

 

LocalDate

// 현재 날의 객체 얻기 
LocalDate localDate = LocalDate.now(); // 2022-12-25 

// 특정 일자의 날짜 가져오는 정적 메소드 
LocalDate localDate = LocalDate.of( 2022, 12, 25 ); 

LocalDate.parse 은 기본적으로 yyyy-MM-dd 형식을 기준으로 날짜 정보를 가져오며 DateTimeFormatter 를 설정하여 다른 형식을 사용할 수 있다.

 

내가 사용해야할 형식은 년도-달-일 형식이었기 때문에 LocalDate 타입으로 다시 수정하였다.

 

2. 수정일 업데이트는 어떻게?

요구사항은 처음 작성 시 작성일과 수정일은 동일하게 처음 작성한 시간으로 하고 그 후 수정을 하면 수정일만 업데이트 하는 방식이었다. 

내가 생각해 본 방법은 그럼 작성일과 수정일을 업데이할 때 클라이언트가 서버에게 시간을 보내줘서 받은 시간으로 객체에 업데이트를 하는 방식이었다.

하지만 일일이 그렇게 할 필요 없이 데이터베이스에서 값을 저장하거나 업데이트하는 쿼리를 적용할 경우 날짜를 업데이트 하도록 설정할 수 있었다.

CREATE TABLE schedule (
                          id           BIGINT AUTO_INCREMENT PRIMARY KEY,  -- 각 일정의 고유 식별자 (자동 생성)
                          task        VARCHAR(255) NOT NULL,              -- 할일 (내용)
                          author      VARCHAR(100) NOT NULL,              -- 작성자명
                          password    VARCHAR(255) NOT NULL,              -- 비밀번호 (암호화 필요)
                          created_at  DATETIME DEFAULT CURRENT_TIMESTAMP, -- 작성일 (현재 시간으로 자동 설정)
                          updated_at  DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -- 수정일 (작성일과 동일, 이후 수정 시 업데이트)
);

 

작성일은 디폴트 값으로 데이터를 생성할때 현재시간으로 값이 저장될 수 있도록 하였고 수정일도 데이터를 처음 저장할 시에는 그렇게 하지만 업데이트가 될 경우에는 다시 그 때 시간으로 저장될 수 있도록 하였다. 

 

3. 디비가 자동으로 업데이트 해주니까 그럼 값을 넣을 때 지정 안해줘도 되겠지?

@Override
    public ScheduleResponseDto saveSchedule(Schedule schedule) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("schedule").usingGeneratedKeyColumns("id");

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("task", schedule.getTask());
        parameters.put("author", schedule.getAuthor());
        parameters.put("password", schedule.getPassword());
        ...

처음에 내가 작성한 리포지토리 클래스에서 일정을 저장하는 함수이다. 디비가 알아서 current timestamp로 저장을 해주겠지 하고 task, author, password만 인서트했다.

그랬더니 요렇게 널값이 들어왔다.

아니 디비가 알아서 업데이트 해준다며 왜 널값이 들어오는거지?? 하고 다시 검색해봤다.

 

일단 근본적인 원인은 인서트할 해쉬맵 자료형에 내가 작성일과 수정일을 명시해주지 않은 것이다. 그럼 왜 명시 해줘야 하지 라는 생각이 들었다. 

이유는 SimpleJdbcInsert가 DEFAULT CURRENT_TIMESTAMP를 트리거하지 않았기 때문이다. JDBC에서 명시적으로 값을 넣지 않으면, DB의 DEFAULT CURRENT_TIMESTAMP가 자동으로 설정되지 않을 수 있다.

SimpleJdbcInsert를 사용할 때, created_at과 updated_at을 parameters에 포함시키지 않으면, DB가 NULL을 받아들이고 DEFAULT CURRENT_TIMESTAMP가 적용되지 않을 수 있다.

 

그래서 

LocalDate now = LocalDate.now(); // 현재 시간 생성

Map<String, Object> parameters = new HashMap<>();
parameters.put("task", schedule.getTask());
parameters.put("author", schedule.getAuthor());
parameters.put("password", schedule.getPassword());
parameters.put("created_at", now);  // created_at 추가
parameters.put("updated_at", now);  // updated_at 추가

Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));

현재 시간을 생성 해 준뒤 작성일과 수정일도 인서트 해주었다.

처음 일정을 생성 시에는 이렇게 해주어야 하지만 일정을 업데이트 할 경우에는 그냥 업데이트 쿼리만 적용하면 수정일은 업데이트가 되기 때문에 이렇게 명시적으로 시간을 넘겨주지 않아도 되었다.

 

4. 모든 일정을 조회할때 어떨 땐 작성자 기준? 어떨 땐 수정일 기준? 어떨 땐 작성자와 수정일 모두?

요구사항에 조건 중 한 가지만을 충족하거나, 둘 다 충족을 하지 않을 수도, 두 가지를 모두 충족할 수도 있습니다.라고 되어있다. GET 요청을 보낼 때 데이터를 조회할 기준을 쿼리파라미터에 담아서 보내고 싶은데 그럼 이 요구사항을 어떻게 만족하지 싶은 고민이 생겼다. 분명 강의 들을 때 배운것 같은데 생각이 안났다 허허...

 

해결방법은 RequestParam의 required 옵션이었다. required 옵션을 false로 지정해주면 값이 꼭 들어와야하는 조건이 아니므로 요구사항을 만족할 수 있었다. 따라서 sql문을 작성할 때도 파라미터가 널이 아니면 해당파라미터로 where문을 작성해서 붙여주는 방식으로 하였다.

 

이번 과제는 설 연휴기간이 끼어있어서 어떻게는 그때도 공부를 해야했지만... 나약한 의지로 그러지 못했다. 어찌저찌 필수 기능까지는 구현했지만 좀 부족하다고 생각된다. 이제 crud는 할 줄 알겠지만 연관관계 맵핑은 잘 모르겠다. 이제 디비 테이블을 나누고 그 부분을 중점으로 공부해야겠다.