새로 시작하는 프로젝트의 개발 언어를 Kotlin으로 정하게 되었고, 자바-스프링으로만 개발을 했던 나에겐 새로운 시도이자 경험이여서 한 번 JPA 적용에 관한 내용을 정리해본다.
오늘은 그 첫 번째 글로, 아래의 내용을 소개한다.
먼저 여기저기서 맨날 듣는 말.. setter "지양"을 지키기 위해서, 삽질을 했던 기록을 공유할 것이다.
그 전에 간단한 내용부터 알아보자.
💡 setter를 왜 지양해야할까?
Setter를 public으로 열어두면, 어느 메소드에서든 엔티티를 수정할 수 있다. 이 때, 엔티티는 비즈니스 로직에서 중요한 대상이며 이러한 대상이 어디서든 수정이 될 수 있다는 것은 큰 문제를 야기할 수 있다.
그럼 수정해야하는 경우에는 어떻게 해야하는데요?
Setter 대신 비즈니스 메소드로서 의미가 드러날 수 있는 메소드를 정의하고, 그러한 메소드를 이용하여 엔티티 수정을 진행하자. Setter 대신 그러한 메소드를 사용하면, 변경에 관한 문제가 발생했을 때 그 메소드만 추적해보면 된다.
이제 Setter를 쓰면 안되는 이유를 어느 정도 알았으니, 이번에는 Kotlin에서 JPA를 사용할 때 과연 Setter를 지양 ( 즉, private settter ) 하기 위한 세팅을 할 수 있을지에 대해 알아보자.
네, 농담입니다.
당연하게도 불변을 나타내는 val이 setter를 안만들어준다고, val을 사용하면 안된다.
우리는 setter를 안사용하는 것이지, 엔티티의 변경 자체를 막는 것이 아니다
이 방법은 당연히 기각.
@Entity
@Table(name = "user")
class User(
@Id
@GeneratedValue
@Column(name = "user_id")
var id: UUID? = null,
@Column(name = "email", unique = true, columnDefinition = "VARCHAR(30)")
val email: String,
@Column(name = "username", unique = true, columnDefinition = "VARCHAR(100)")
private var username: String,
@Enumerated(EnumType.STRING)
@Column(name = "role")
private var role: Role = Role.ROLE_USER,
)
위의 예시 엔티티는, username과 role의 무분별한 setter 사용을 지양하기 위해 private var로 설정해보았다.
하지만, 이 방법은 getter 마저도 private으로 가져가기 때문에 불편함이 너무 크다.
일단 기각하자.
그럼 아래처럼 setter만 private으로 설정하는 것은 어떨까?
@Entity
@Table(name = "user")
class User(
@Id
@GeneratedValue
@Column(name = "user_id")
var id: UUID? = null,
@Column(name = "email", unique = true, columnDefinition = "VARCHAR(30)")
val email: String,
username: String,
role: Role
){
@Column(name = "username", unique = true, columnDefinition = "VARCHAR(100)")
var username: String = username
private set
@Enumerated(EnumType.STRING)
@Column(name = "role")
var role: Role = role
private set
}
이 코드는 컴파일도 안된다.
왜나면, 일단 JPA의 프록시를 사용하기 위해서는 Entity 클래스는 모두 open class이여야하며 보통 아래와 같은 설정으로 all-open 설정을 하기 때문.
allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.MappedSuperclass")
annotation("javax.persistence.Embeddable")
}
all open으로 설정된 클래스는 property까지 전부 open으로 설정되기 때문에, private setter를 가질 수 없다.
따라서, 이 방법은 불가능하다.
그나마 대안이 될 수 있는 방법이다. protected는 all open의 영향을 받지 않는다, 그렇기에 protected 설정을 하면 아무 곳에서나 setter를 사용할 수 없을 것이다.
@Entity
@Table(name = "user")
class User(
@Id
@GeneratedValue
@Column(name = "user_id")
var id: UUID? = null,
@Column(name = "email", unique = true, columnDefinition = "VARCHAR(30)")
val email: String,
username: String,
role: Role
){
@Column(name = "username", unique = true, columnDefinition = "VARCHAR(100)")
var username: String = username
protected set
@Enumerated(EnumType.STRING)
@Column(name = "role")
var role: Role = role
protected set
}
근데.. 두 가지 찝찝함이 남았다.
결국 가능한 방법은 2번과 4번 방법 중 하나이다.
이러한 방법 둘 다, 솔직히 좀 별로다.
엥? 이 글은 해결법을 알려주는게 아니였냐구요?
글의 서두를 다시 읽고오자. 나는 분명 "Setter를 지양 ( 즉, private settter ) 하기 위한 세팅을 할 수 있을지에 대해 알아보자."라고 말했다. 해결하는 방법을 알아보자라고 안했다.
그냥 예전부터 든 생각이였지만, Kotlin과 JPA의 상성은 진짜 별로인거 같다.
누가 좀 해결해줬으면...
안녕하세요 좋은글 잘 읽었습니다
1번 val이 세터를 안만들면 결국 엔티티수정이 불가능한것 같은데 왜 쓰면 안되는지 잘모르겠습니다ㅜ