본문 바로가기
쿤즈 Dev/Spring Boot

[Spring Boot] 엔티티에서 Setter 사용에 대한 생각

by :)Koon 2023. 1. 8.

이번 포스팅에서는 고민하고 있는 생각들을 적어보려 한다. 그래서 이번에 생각해본 주제는 데이터베이스와 바로 직결되어 있는 엔티티, 그 중에서도 엔티티의 데이터가 변경되는 Setter(세터) 메서드에 대한 고민들이다.

생각하고 고민하던 것들을 나열해 본다.


엔티티는 소중하다

엔티티는 굉장히 소중한 객체이며 클래스라 생각한다. 데이터베이스 테이블과 동일한 데이터를 담는 곳이기 때문이다. 따라서 이 데이터가 잘못되었거나 없다거나 부문별하게 변경될 수 있다 생각하면 아찔하다.

그래서 엔티티를 다룰때에는 반드시 소중하게 다뤄야 할 필요가 있다. 새로운 엔티티가 생성되는 것인지, 이미 만들어진 엔티티를 수정하는 것인지 판단해야한다. 혼자 작업을 할 때에는 이런것들은 개발자 한 사람이 다 생각하기때문에 전혀 문제가 없다고 느껴지지만 여러사람이 작업을 진행할 때에는 문제가 발생한다. 모두가 나와같은 생각으로 개발을 진행하지 않기 때문이다. 그래서 규칙이 필요하다.


엔티티를 생성할때 규칙

엔티티는 기본적으로 캡슐화가 잘 되어 있어야 한다. 따라서 무분별하게 누군가가 수정하거나 목적없이 엔티티를 DTO처럼 사용하게 해서는 안될것이다. 그래서 아래와 같이 사용해 본다.

@Entity
@Table(name = "member")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
	...
}

엔티티를 나타내는 애노테이션과 테이블을 매핑하는 애노테이션은 기본이다. 여기에 추가적으로 사용할 Lombok 애노테이션이 @Getter와 @NoArgsConstructor 이다.

 

@Getter 는 모든 멤버필드에 getXX() 메서드를 만들어주는 역할을 한다. 롬복을 잘 사용하면 이런 부분이 굉장히 편하다.

@NoArgsConstructor 역시 인자가 없는 생성자를 만들어 준다. 다만 이 생성자를 PROTECTED 옵션을 주어 외부에서는 사용할 수 없고 상속받은 클래스까지만 사용할 수 있도록 한다. 이렇게 사용하면 어떤 일이 발생하게 될까?

 

Service 레이어에서 보통 DB에 저장하기위해서 엔티티를 생성한다. 이렇게 생성할때 new Memeber(); 와 같이 사용하면 에러가 발생한다. 이유는 위에서 설명했듯 인자가 없는 생성자는 사용할 수 없기 때문이다. 그래서 이때는 Builder 패턴이나 Static Factory Method를 만들어서 사용한다. 이 둘의 비교난 다음 포스팅에서 정리해보겠다.

 

정적 팩토리 메서드를 사용하면 아래와 같이 만들 수 있다.

@Entity
@Table(name = "member")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "member_id", nullable = false)
	private Long id;

	@Column(name = "email", nullable = false)
	@Email
	private String email;

	@Column(name = "nickname", nullable = false)
	private String nickname;
    
	public static Member createMember(String email, String nickname) {
		Member member = new Member();
		member.email = email;
		member.nickname = nickname;
		return member;
	}
}

외부에서 Member 클래스의 인스턴스를 만들기 위해서는 반드시 createMember() 라는 메서드를 호출해야 사용이 가능하다. 따라서 무분별하게 Member() 라는 생성자 클래스를 통해서 만들거나 setter를 통해서 만들지 못하게 되었다.

 

물론 static 메서드 안의 로직은 다른 방법들로 구현해 보는것이 더 좋다. 단순히 예일 뿐이다.


엔티티는 무분별하게 변경되어선 안된다

엔티티는 무분별하게 변경되어선 안된다. 당연히 무분별하게 변경하지는 않을 것이다. 특별한 이유가 있으니 변경점이 발생하고 그 때문에 변경을 할 것이다. 다만 변경하는 이유가 문제다. 여러가지 이유때문에 변경하지만 변경하는 메서드는 setter를 이용한다면 왜 변경되는지 알 수가 없다. 그리고 엔티티 외부에서 임의로 누구나 필드에 접근하고 값을 변경한다. 이 또한 문제다.

 

무분별하다는 말은 다시 말하면 변경할 이유가 정확하게 명시되어야 하고 그 변경은 엔티티에게 직접 위임해줘야 한다. 스스로 값을 변경하는 메서드를 만들어서 변경해줘야한다. 외부에서 마음대로 값을 변경하면 중복 변경시점이 발생할 경우 데이터의 무결성이 파괴된다. 그래서 필요한 변경점은 반드시 제공자를 만들어 주어야 한다.


변경이 필요한 엔티티는 변경하는 제공자를 만들자

제공자란 어렵지 않다. 단순하게 메서드를 하나 만들어 보자. 위에서 만든 엔티티에 메서드를 만들어 보면 이렇다.

@Entity
@Table(name = "member")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "member_id", nullable = false)
	private Long id;

	@Column(name = "email", nullable = false)
	@Email
	private String email;

	@Column(name = "nickname", nullable = false)
	private String nickname;
	
	public static Member createMember(String email, String nickname) {
		Member member = new Member();
		member.email = email;
		member.nickname = nickname;
		return member;
	}
	
	public void updateEmail(String email) {
		this.email = email;
	}

멤버의 이메일을 업데이트 하려 한다. 만약 @Setter 가 붙어있다면 Service layer 같은 곳에서 엔티티를 호출하고 setEmail() 메서드를 호출 할 것이다. 하지만 위 엔티티는 setter 메서드가 없다. 그래서 별도 public 메서드를 만들어 주었다. 이 메서드 안에서 본인의 값을 변경하도록 엔티티에게 위임하였다.


스프링 부트의 변경감지(Dirty Check)

스프링 부트에는 변경감지라는 기능이 있다. 이는 Repository를 이용하여 호출된 엔티티는 값이 변경되면 하이버네이트가 알아서 변경된 update 쿼리문을 DB로 전송한다. 그래서 따로 update 쿼리를 만들어 주지 않아도 되는 편리함이 있다.

 

만약 위에서 정리한 내용처럼 setter를 사용해서 변경한다면, 그리고 그러한 변경점이 여러곳이라면 역시 디비에 저장된 데이터는 무결성이 깨져버린 데이터가 된다. (여기서 Transaction 이나 동시성 문제는 또 다시 생각해 보아야 할 문제다.) 그래서 update메서드를 만들어 주고 이 메서드는 곧 update 쿼리를 만들어 줄 메서드가 된다.


이번에는 간략하지만 엔티티를 다루를때 고려해야할 부분에 대해서 생각해 보았다. 지금은 자바보다는 코틀린을 이용한 스프링을 다루는 중인데 이 부분이 생각할 것이 많았다. 자바랑 다르게 코틀린 언어의 특성을 고려해서 만들다보니 애매한 부분들이 많았기 때문이다. 조금 더 학습이 필요한 부분이고 다음 포스팅에서는 @DynamicUpdate에 대해서 다뤄보겠다.

댓글