코프링 JPA 적용기 - 1

kshired·2022년 6월 30일
0

새로 시작하는 프로젝트의 개발 언어를 Kotlin으로 정하게 되었고, 자바-스프링으로만 개발을 했던 나에겐 새로운 시도이자 경험이여서 한 번 JPA 적용에 관한 내용을 정리해본다.

오늘은 그 첫 번째 글로, 아래의 내용을 소개한다.

Setter 지양하기 위해서 해보았던 삽질들

먼저 여기저기서 맨날 듣는 말.. setter "지양"을 지키기 위해서, 삽질을 했던 기록을 공유할 것이다.

그 전에 간단한 내용부터 알아보자.

💡 setter를 왜 지양해야할까?
Setter를 public으로 열어두면, 어느 메소드에서든 엔티티를 수정할 수 있다. 이 때, 엔티티는 비즈니스 로직에서 중요한 대상이며 이러한 대상이 어디서든 수정이 될 수 있다는 것은 큰 문제를 야기할 수 있다.

그럼 수정해야하는 경우에는 어떻게 해야하는데요?
Setter 대신 비즈니스 메소드로서 의미가 드러날 수 있는 메소드를 정의하고, 그러한 메소드를 이용하여 엔티티 수정을 진행하자. Setter 대신 그러한 메소드를 사용하면, 변경에 관한 문제가 발생했을 때 그 메소드만 추적해보면 된다.

이제 Setter를 쓰면 안되는 이유를 어느 정도 알았으니, 이번에는 Kotlin에서 JPA를 사용할 때 과연 Setter를 지양 ( 즉, private settter ) 하기 위한 세팅을 할 수 있을지에 대해 알아보자.

1. val 로 지정하기

네, 농담입니다.

당연하게도 불변을 나타내는 val이 setter를 안만들어준다고, val을 사용하면 안된다.

우리는 setter를 안사용하는 것이지, 엔티티의 변경 자체를 막는 것이 아니다

이 방법은 당연히 기각.

2. private var 지정

@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으로 가져가기 때문에 불편함이 너무 크다.

일단 기각하자.

3. setter를 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를 가질 수 없다.

따라서, 이 방법은 불가능하다.

4. protected set 설정

그나마 대안이 될 수 있는 방법이다. 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
    
}

근데.. 두 가지 찝찝함이 남았다.

  1. protected라서 찝찝하다. private으로 결국 해결하지 못했다.
  2. 코드 길이가 너무 늘어났다. 코틀린을 쓰는 의미가 없어진 느낌이다. ( 짜친다 )

5. 결과

결국 가능한 방법은 2번과 4번 방법 중 하나이다.

이러한 방법 둘 다, 솔직히 좀 별로다.

엥? 이 글은 해결법을 알려주는게 아니였냐구요?

글의 서두를 다시 읽고오자. 나는 분명 "Setter를 지양 ( 즉, private settter ) 하기 위한 세팅을 할 수 있을지에 대해 알아보자."라고 말했다. 해결하는 방법을 알아보자라고 안했다.

그냥 예전부터 든 생각이였지만, Kotlin과 JPA의 상성은 진짜 별로인거 같다.

누가 좀 해결해줬으면...

References

profile
글 쓰는 개발자

1개의 댓글

comment-user-thumbnail
2024년 3월 24일

안녕하세요 좋은글 잘 읽었습니다
1번 val이 세터를 안만들면 결국 엔티티수정이 불가능한것 같은데 왜 쓰면 안되는지 잘모르겠습니다ㅜ

답글 달기