코틀린에서는 var
로 선언된 property(프로퍼티)
에 대해 setter
를 막는 private set
기능을 제공하지 않습니다.
스프링 + 코틀린 환경에서 개발하다보면 var
로 선언된 프로퍼티의 setter
를 막아야 하는 경우가 존재합니다.
하지만 왜 코틀린에서는 프로퍼티의 private set
기능을 제공하지 않을까요?
이에 대한 예제는 JPA
를 사용하기 위한 Entity
를 구성하는데 있어서 발생합니다.
처음 코틀린으로 엔티티를 작성하면 보통 다음과 같이 작성하게 됩니다.
@Entity
@Table(name = "person")
class Person(
@Column(name = "name")
val name: String,
@Column(name = "age")
val age: Int,
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
var id: Long? = null
}
근데 이렇게 엔티티로 생성한 Person
객체의 상태를 변경하려면 어떻게 해야 할까요?
name
과 age
프로퍼티는 val
로 선언했으니 setter
없이 getter
만 존재하게 됩니다.
그래서 Person
객체의 상태를 변경할 수가 없습니다.
그렇다고 var
로 선언하자니 아무 의미 없는 setter
가 생겨 JPA
의 규칙에 어긋납니다.
그럼 var
에 private set
을 해야 하는데 다음 코드는 컴파일 에러가 발생합니다.
@Entity
@Table(name = "person")
class Person(
@Column(name = "name")
var name: String
private set, // compile error
@Column(name = "age")
var age: Int
private set, // compile error
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
var id: Long? = null
}
제목에서 말했듯이 코틀린에서는 프로퍼티에 대해 private set
기능을 제공하지 않습니다.
그러면 실제로 개발할 때는 어떻게 우회하여 해결할까요?
@Entity
@Table(name = "person")
class Person(
name: String,
age: Int,
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
var id: Long? = null
@Column(name = "name")
var name = name
private set
@Column(name = "age")
var age = age
private set
fun eatRiceCakeSoup() {
age++
}
}
본 해결책은 제가 개발할 때 사용하는 방식으로 가장 합리적으로 우회했다고 생각하는 방식입니다.
하지만 생성자에도 변수를 작성해야하고, 클래스의 바디에도 작성해야 하기 때문에
단순한 엔티티가 길어지는 문제점을 가지고 있습니다.
@Entity
@Table(name = "person")
class Person(
@Column(name = "name")
val name: String,
@Column(name = "age")
val age: Int,
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
var id: Long? = null
fun eatRiceCakeSoup(): Person {
val person = Person(
name = name,
age = age + 1,
)
person.id = id
return person
}
}
이렇게 엔티티를 변경할 때마다 새로운 엔티티를 생성하게 되면 불변성 유지도 되고
setter
를 막을 수도 있지만, JPA
의 더티 체킹 기능을 사용할 수 없다는 최대의 문제점이 있습니다.
더티 체킹을 이용하지 않으면 엔티티가 변경될 때 UPDATE
문을 날리기 위해서
엔티티의 변경마다 save()
메소드를 호출해야 합니다.
이는 로직을 더럽히는데 주축을 담당하게 될 수도 있습니다.
아래 참조한 문서들을 살펴보면 코틀린 공식 사이트에서 왜 안되게 해두었는가를 두고 싸우고 있는 것을 볼 수 있습니다.
owner
키워드를 제공하여 private set
인 프로퍼티를 만들 수 있게 해야한다는 주장도 있고
다른 개발자들도 위 해결책-1을 이용하여 해결하는 것 같으며,
코틀린의 공식 입장도 없기 때문에 딱히 이유는 없는 것 같습니다.
추후에 업데이트를 통해 해결되었으면 좋겠습니다.
Private setter for var in primary constructor
How to use custom setter in Kotlin class constructor body
In kotlin, how to make the setter of properties in primary constructor private?
엔티티는 open으로 설정 안하시나요? JPA는 지연 로딩 시 프록시 객체를 기반 클래스에서 상속받아서 만들어야 해서 클래스에 open을 써줘야 하고 그러면 private set은 쓰지 못하는 걸로 알고 있습니다. 저는 그래서 protected set으로 하는 걸로 합의봤습니다. 예제 엔티티는 연관관계가 없어서 그런 건가요?