[ERROR] 프로젝트에서 발생했던 오류 정리(2024.07~08)

Yumya's record·2024년 8월 17일
0

ERROR

목록 보기
4/6
post-thumbnail

💡 LocalDate

프로젝트에서 사용자의 생일을 나타낼 때 LocalDate를 사용했다.
Test code에서 LocalDate를 사용하면서 발생한 오류를 정리하고자 한다.


유저 추가 테스트 코드에서는 데이터 베이스의 LocalDate를 가져왔을 때 결과값은 LocalDate라고 생각해 LocalDate를 받아와 비교했다.

final LocalDate birth = LocalDate.of(2024, 7, 29); //int > LocalDate
... //생략
List<User> users = userRepository.findAll(); //user 전체 조회
assertThat(users.get(0).getBirth()).isEqualTo(birth); // birth와 데이터베이스에 저장한 birth의 일치 여부 검사

하지만 유저 조회 테스트에서 실제로 결과값을 받아왔을 때 assertThat이 아닌 jsonPath를 통해 LocalDate로 비교했을 때 AssertionFailedError 에러가 발생했다.


에러의 원인을 찾아보니, jsonPath가 JSON 응답에서 값을 String으로 변환하기 때문에 LocalDate 객체도 String으로 변환하여 비교해야한다고 한다. 이는 Json이 날짜 형식을 직접 지원하지 않고 모든 데이터를 텍스트 형식으로 다루기 때문이라고 한다.

이와 다르게 assertThat은 자바 객체 간의 비교를 지원하므로 LocalDate와 같은 날짜 객체도 그대로 비교할 수 있고 한다. 그래서 LocalDate 간의 비교가 가능했던 것이다.

따라서 jsonPath를 사용할 때는 LocalDate가 아닌 String으로 값을 비교해야한다는 것을 알게 되었다.


다른 메서드를 사용하면 원하는 형태로 날짜나 연도를 받아올 수 있지만, 테스트 코드에서는 처음 저장한 값과 결과값이 일치하는 여부만 확인하면 된다. 그래서 String으로 LocalDate를 생성하여 저장한 뒤 조회 시 String으로 비교했다.

final String birth = "2024-07-29"; //String으로 생일 생성

userRepository.save(User.builder()
                .userId(id)
                .pw(pw)
                .email(email)
                .nickname(nickname)
                .username(username)
                .birth(LocalDate.parse(birth)) //String을 LocalDate로 변환해 데이터베이스에 저장
                .build());
                
... //생략

resultActions.andExpect(jsonPath("$[0].birth").value(birth)); //위에서 생성했던 String으로 값 비교

String > LocalDate

LocalDate currentDate = LocalDate.parse("2024-08-17"); //String > LocalDate
LocalDate currentDate2 = LocalDate.parse("2024-08-17", DateTimeFormatter.ISO_DATE); //DateFormatter를 지정

int > LocalDate

LocalDate currentDate = LocalDate.of(2024, 8, 17); //int > LocalDate

이외에도 다양한 메서드들이 있으니 공식 문서를 참고 바란다. 공식 문서-LocalDate


💡@RequestParam

프로젝트의 Controller에서 url를 정의하면서 @RequestParam을 사용해 파라미터 이름으로 바인딩했다.


@RequestParam은 스프링이 제공하는 요청 파라미터를 편리하게 처리할 수 있는 어노테이션이며, 파라미터 이름으로 바인딩한다. 예를 들어 '/api/user/{id}'의 요청이 들어오면 id가 바인딩된다.


하지만 프로젝트 실행 시 MissingServletRequestParameterException가 발생했다.


에러의 원인을 찾아보니, Controller에서 @RequestParam을 정의하면서 value 속성을 명시해주지 않아, 즉 바인딩되는 파라미터의 이름을 명시해주지 않았기 때문에 class 파일에서 해당 파라미터를 찾지 못해 해당 오류가 발생한다고 한다.

@GetMapping("/api/user/{id}")
public User findUser(@RequetParam String id) {
	... // 생략
}

그래서 다시 코드를 보니, 바인딩되는 파라미터의 이름을 명시해주지 않았다는 것을 알게 되었다.

@GetMapping("/api/user/{id}")
public User findUser(@RequetParam(value = "id") String id) {
	... // 생략
}

value 또는 name으로 바인딩 파라미터의 이름을 명시해줌으로써 해당 에러를 해결했다.


참고-@RequestParam


💡 Not-null property references a null or transient value

Entity field에 @NotNull 제약 조건이 설정되어 있는데, 해당 필드에 null값이 저장될 경우 발생한다.
이는 JPA Entity 영속 시 필수 field가 null이 아니여야 하기 때문이다.


유저 권한 자동 생성 테스트를 작성하다 발생했다.
유저를 생성했을 때 유저 권한을 자동으로 1로 생성해 저장시킨다.
유저를 생성할 때 생성 시간과 수정 시간이 not null로 설정되었는데, 유저를 생성했을 때 null 값이 저장되어 PropertyValueException가 발생했다.


Hibernate에서 제공하는 어노테이션인 @CreationTimestamp, @UpdateTimstamp를 사용해 생성 시간과 수정 시간을 자동으로 저장하고 있었다. 하지만, Hibernate Annotation을 사용하지 않는 추세라는 것을 알게 되어 @CreatedDate와 @LastModifiedDate로 변경해 사용하였다.


@CreatedDate와 @LastModifiedDate는 리스너를 사용해 객체가 변경될 때 데이터에 변경된 시간을 반영한다.


하지만 해당 Annotaion을 사용했음에도 테스트에서 유저 생성 시 null 값이 저장되는 현상이 발생했다.

이에, Builder 패턴으로 객체를 생성할 때 생성 시간과 수정 시간을 현재 시간으로 초기화함으로써 null이 저장되지 않도록 변경해 오류를 해결했다.

@Builder
public User(String userId, String pw, String email, String nickname, String username, LocalDate birth, Integer siren) {
    this.userId = userId;
    ... //생략
    this.create_date = LocalDateTime.now(); //현재 시간 저장
    this.update_date = LocalDateTime.now(); //현재 시간 저장
}

참고-@CreatedDate, @LastModifiedDate


💡 @Transactional

delete 메서드를 구현한 후 실행했더니, TransactionRequiredException가 발생했다.

javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call

에러가 발생하는 원인을 찾아보니, Entity를 삭제하려고 시도할 때 발생하며, 현재 스레드에서 활성화된 트랜잭션이 없어 Entity를 삭제하기 위한 트랜잭션이 존재하지 않아 발생한다고 한다.


아래 블로그 글을 참고하여 delete 메서드에 @Transactional를 사용함으로써 해결했다.


참고-delete

profile
🍀 ٩(ˊᗜˋ*)و 🍀

0개의 댓글