@NotNull vs @Column(nullable = false)
서론
여기까지 고민하게 된 계기는 Entity에서 필드 검증을 직접해야 할까?
였다.
그러면 항상 로직을 짜아하고, null체크와 같은 것들은 굉장히 중복되는 것들인데
모든 Entity에 비슷한 코드가 들어가게 된다.그리고 Entity 검증 테스트를 작성할 필요가 있을까?
단순한 검증 로직 귀찮은데?
기존에는 아래 코드와 같이 직접 StringUtils.isBlank
와 같이 검증을 했다.
그리고 테스트 작성도 했다.
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
@Column(nullable = false)
private String introduction;
...
public void updateIntroduction(String introduction) {
if (StringUtils.isBlank(introduction) {
throw new IllegalArgumentException();
}
this.introduction = introduction;
}
...
}
그러다 문득 의문이 들었다. 이런 단순한 로직을 굳이 테스트를 작성해야 할까?
테스트가 꼼꼼할수록 좋겠지만 개발을 하는데 테스트가 없어도 깨지지 않을 로직이라고 판단되면,
테스트를 작성할 필요는 없다고 생각한다.
그래서 테스트 코드를 작성하지 않겠다고 생각하고, @Column(nullable = false)
를 사용하기로 했다.
이 부분은 어차피 영속화가 되면, 검증이 되기 때문에 검증 로직이 없어도 예외는 발생되기 때문에 선택했다.
@NotNull vs @Column(nullable = false)
@Column(nullable = false)
을 사용하기로 정하고, 테스트도 작성하지 않기로 정했다.
그런데 DTO 검증 시 @Valid, @Validated를 컨트롤러 파라미터에 붙여주면 @NotNull로도
검증 했던게 기억이 났다. 스프링이 제공하는 validator
이다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
어떤 차이점이 있을까?
먼저 생각한 것은 validator
는 @NotNull
뿐 아니라 @NotBlank
등 수 많은 검증 애노테이션을 제공한다.
null 외에도 white space, empty 검증도 필요했기 때문에 validator
를 사용하기로 결정했다.
차이점을 알아보자.@NotNull
과 nullable
은 둘 다 persist(영속)되는 시점에 되는 것은 동일하다.
하지만 약간 시점이 다르다.
nullable의 예외를 먼저 살펴보자.
@Transactional
@SpringBootTest
class MemberTest {
@PersistenceContext
EntityManager em;
@Test
void t() {
Member member = new Member("email", null);
em.persist(member);
}
}
- 예외
- 중요한 것은
쿼리가 생성
된다. flush가 되는 것은 아니지만, 쿼리를 생성하는 절차를 거치면서
여기서ConstraintViolationException
이 발생한다.
쿼리가 생성되는 시점에 예외가 발생한다는 것이 중요하다.
@NotNull
의 예외를 먼저 살펴보자.
- 예외
예외는 동일하지만, 쿼리가 생성되지 않는다.
- 결과적으로 쿼리가 생성이 되어 영속성 컨텍스트에 쿼리를 생성시키면서 발생하느냐
그 전에 발생하느냐의 차이이다. 유효성 체크를 하는 것이라면 쿼리는 무관하기 때문에
쿼리 생성 전에 하는 것이 효율적이라고 생각해서 validator를 사용하기로 결정했다.
테스트는 어떻게 하지?
의식의 흐름은
- 단순한 유효성 체크 검증 로직을 매번 해야할까?
- 하지말자. @NotNull이나 nullable을 사용하자.
- Entity 테스트는 따로하지 말자.
너무 간단한 로직이기 때문에 테스트는 리소스 낭비이다.
이건 DTO에서 검증하는 것이 맞다고 생각한다. - 그래도 검증은 필요하긴 할텐데? 코드를 작성하는 사람은
로직을 파악하고 있어 실수 확률이 적지만,
클라이언트에서 잘못 넘긴다면? 에러이다. - 인수 테스트로 예외 테스트를하면 DTO 테스트도 같이 되기 떄문에 커버할 수 있다.
정리
@Column(nullable = false)
를 사용하자.
문제는 Entity에서 중복으로 체크를 하지 않아도 될까? 이다.
프로젝트의 구조를 살펴보면, 무조건 컨트롤러 → DTO를 통해 들어온다.
즉, DTO와 Entity에서 중복되는 필드의 단순 검증은 DTO에서만으로도 충분하다.
Entity에서 검증이 필요한 것은 도메인 로직과 연관이 있는 것이어야 한다.
예를 들어 중복되는 주문이 있으면 안된다던지 하는 것이다.
그렇지 않고 DTO, Entity에서 중복되는 검증을 계속하면 코드양이 너무 많아지고,
관리할 코드가 늘어나게 되므로 생산성이 좋지 못하다고 생각한다.
의문
Entity에서 발생하는 DB 예외는 어떻게 해야 할까?
@ExceptionHandler({ConstraintViolationException.class})
public ResponseEntity<String> handlerConstraintViolationException(ConstraintViolationException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("DB 예외는 어떻게 처리할까?");
}