케네스로그

엔티티 Null체크, @Column과 @NonNull 어떤걸 써야할까? 본문

Dev/스프링

엔티티 Null체크, @Column과 @NonNull 어떤걸 써야할까?

kenasdev 2023. 2. 19. 23:24
반응형

엔티티를 정의하면서 궁금했었고, SpringDeepDive 스터디에서 나왔던 질문이었다.
@Column(nullable=false)와 @NotNull 두가지의 null 방지 어노테이션을 사용할 수 있다.
과연 실제 코드에서는 어떤 차이를 보일까?

@Column과 @NonNull

jakarta.persistence의 @Column(nullable=false)의 설명은 다음과 같다.

Specifies the mapped column for a persistent property or field. If no Column annotation is specified, the default values apply. Whether the database column is nullable.



lombok의 @NotNull의 설명은 다음과 같다.

If put on a parameter, lombok will insert a null-check at the start of the method / constructor’s body, throwing a NullPointerException with the parameter’s name as message. If put on a field, any generated method assigning a value to this field will also produce these null-checks.


설명으로 유추해봤을 땐, @Column은 데이터베이스에 엔티티를 저장할 때, 필드가 null인지 여부를 확인하여 Exception을 발생시킨다.
반면, @NonNull은 메소드나 생성자 수준에서 필드에 대한 null 체크를 하여 NPE를 발생시킨다.

질문에 대한 내 답은 '@NonNull을 사용하는 것이 좋다'였다.
그 이유는, Repository Layer에서 데이터베이스를 접근하기 전에 생성자를 사용하는 Service Layer에서 널체크를 진행할 수 있기때문이다. 다른 레이어로 이동하기 전에 예외를 잡아낼 수 있는 것이 당연히 더 효율적일거라 생각했다.

코드로 직접 확인해보자

간단한 예제로 Member entity, Controller, Service, Repository를 만들었다.

public class Member {
	@EmbeddedId
	private MemberId id;
	
	@Column(nullable=false)
	private String name;
	
	@Column(nullable=false)
	private Integer age;
}

public class MemberController {

	private final MemberService memberService;

	@PostMapping
	public ResponseEntity<Void> join(@RequestBody JoinRequest request) {
		log.info("*** controller start ***");
		memberService.join(request);
		log.info("*** controller end ***");
		return ResponseEntity.created(URI.create("/")).build();
	}
}

public class MemberService {
	public class MemberRepository memberRepository;
	public void join(JoinRequest request) {
		log.info("*** service start ***");
		MemberId newId = new MemberId("M".concat(UUID.randomUUID().toString()));
		
		Member newMember = Member.build()
                            .id(newId)
                            .name(request.getName())
                            .age(request.getAge())
                            .build();
		log.info("registering new member..");
		memberRepository.save(newMember);
		log.info("*** service end ***");
	}
}

@Column을 사용한 null check 구현 예제이다.
이 상태에서 request를 보낼 때, age항목을 제외하고 요청을 날려보았다.

로그를 확인해봤을때, controller에서 요청을 받고, service 메소드에서 엔티티를 생성한 후, repository를 통해 생성된 엔티티를 등록하는 과정에서 예외가 발생한다.

public class Member {
	@EmbeddedId
	private MemberId id;
	
	@NonNull
	private String name;
	
	@NonNull
	private Integer age;
}

이번에는 Member 엔티티에서 @NonNull을 사용해서 제약조건을 걸었다.

똑같은 요청을 전송했을때 발생한 로그이다.
여기서 봤을 때, service 내부에서 엔티티를 생성하는 과정에서 예외가 발생함을 확인할 수 있었다.

그래서 결론은,

Service 레이어에서 널체크가 가능한 @NonNull을 사용하는 것이 더 낫다!
위의 예시에서는 service 내부에서의 로직이 짧기때문에 크게 와닿지않았다.
하지만, 엔티티 생성이후 등록까지의 과정에서 로직이 많으면 많을수록 비효율적일것이라 생각했다.
무엇보다도 서비스 레이어 내에서 예외를 잡을 수 있는 것과 레포리토리 레이어까지 진행된 후에 예외를 잡는 것은 큰 차이라고 생각되었다. 가능하다면 controller단에서, 또는 Argument Resolver에서 널체킹이 이루어지는것이 베스트이지않을까 생각했다.

반응형