MySQL을 통해 DDL을 먼저 작성한 후 JPA에서 entity를 설계하던 중, JPA constraints와 validation의 차이를 모르고 있다는 것을 알게 되었습니다.
개발 단계에서는 자동 생성된 ddl을 그대로 이용하기도 하지만, 실제 운영 시에는 최적화를 위해 DB는 별도로 설계한다고 합니다.
사실 저는 습관적으로 ddl을 먼저 작성한 것이지만, 어쨌든 DB와 엔티티에서 별도로 제약을 다루게 된다는 것이므로 JPA에서 constraints와 validation의 차이를 알아두면 좋을 것 같습니다.
회원의 닉네임을 10자 이내로 제한하고 싶은 경우를 예시로 constraints와 validation의 차이를 살펴보겠습니다.
JPA에서 constraints를 설정하는 것은 자동 생성되는 ddl에 constraints를 추가하는 것과 동일합니다.
아래는 @Column의 length를 설정한 예시입니다.
@Entity
public class Member{
...
@Column(length = 10)
private String nickname;
...
}
다음 코드를 실행하면서 constraints의 동작을 살펴보겠습니다.
@Test
public void 제약_테스트(){
Member member = Member.createMember("temp",OAuthDomain.KAKAO, "10글자가 넘는 이름");
em.persist(member);
em.flush();
}
먼저 ddl 자동 생성 시 제약이 추가되는 것을 확인할 수 있습니다.
다음으로는 Exception이 발생합니다. 메시지를 쭉 보면,
SQL 실행 시 오류가 발생한 것을 확인할 수 있습니다.
즉, constraints는 ddl 생성 시 제약을 추가해주는 것 입니다.
만약 ddl-auto 옵션을 사용하지 않는다면 어떻게 될까요?
말 그대로 ddl 생성 시 제약을 추가해주는 것이므로 아무런 의미가 없습니다.
예를 들어, DB 설계 시 nickname을 varchar(50)으로 설정하면, @Column(length = 10)으로 설정하더라도 앞서 실패했던 위 테스트 코드가 성공합니다.
이번엔 validation을 살펴보겠습니다.
validation을 이용하는 경우에는 다음과 같이 동작합니다.
Constraints를 설정할때와는 달리 DB에서 오류가 발생하는 것을 막기 위해 자체적으로 검증을 한번 더 진행하는 것입니다.
검증은 다음과 같이 이뤄집니다.
1. Validator bean이 등록됨
2. 해당 bean의 validate 메서드를 통해 검사
참고로 꼭 flush 되기 전 뿐만 아니라, 어느 순간에도 빈을 이용해서 검증할 수 있습니다.
아래는 @Size 어노테이션을 통해 크기 제한을 설정한 경우입니다.
@Entity
public class Member{
...
@Size(min = 2, max = 10)
private String nickname;
...
}
ddl-auto를 create로 설정하고, 다시 아래 코드를 실행해보겠습니다.
이름만 '검증_테스트'로 바뀌었고, 앞선 테스트 코드와 동일한 코드입니다.
@Test
public void 검증_테스트(){
Member member = Member.createMember("temp",OAuthDomain.KAKAO, "10글자가 넘는 이름");
em.persist(member);
em.flush();
}
create 문을 먼저 살펴보겠습니다.
이번에도 nickname이 varchar(10)으로, 설정한 길이로 선언된 것을 확인할 수 있습니다.
그러나 발생하는 예외는 다릅니다.
앞서는 SQL을 실행하는 과정에서 문제가 발생했다면, 이번엔 그 이전에 검사를 통과하지 못합니다. 즉, DB로 쿼리가 날라가지 않습니다.
따라서 검증은 ddl-auto 옵션을 사용하지 않더라도 유효합니다.
예를 들어, nickname을 varchar(50)으로 선언하더라도 @Size(max = 10)으로 제한하는 것이 유효합니다. (물론 DB 에서 쿼리를 작성하면 상관이 없겠죠)
※ persist 하기 전에 검사한다는 표현도 있었는데, 그렇지 않고 flush 할 때 검사합니다.
이번 글에서는 @Column(length = ~)과 @Size를 통해 비교했지만, @Column(nullable = false)와 @NotNull 등 둘의 차이가 헷갈리는 경우가 꽤 있고, 잘 알고 사용해야 합니다.