JPA Access Strategy + Kotlin use-site target

sarah·2023년 4월 14일
0

직면했던 이슈

코틀린 @get 을 사용하여 entity를 구현하였는데, @GeneratedValue 앞에 붙이지 않아서 save() 시 id를 assign 해주지 않았다는 에러가 발생하였다.

// 문제상황 코드
@Entity
@Entity
open class Example {
    @get:Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @get:Column(name = "example_id", nullable = false)
    open var id: Long? = null
...

그래서 @GeneratedValue 앞에 @get 을 선언해줌으로써 문제를 해결했는데,
정확한 이슈 발생이유와 해결방안이 이해가 가지 않았다.

  1. 코틀린의 @get 이 정확히 어떤 이유로 사용되는지 이해하지 못했다.
    코틀린은 프로퍼티 선언 후 getter,setter 자동생성해주는데 @get 어노테이션이 왜 필요한가? 라는 의문이 있었다.
  2. @get 을 사용하면서 @Id가 getter 메서드 위에 선언되어 사용되는 것이였다.
    jpa는 엔티티에 접근할때 reflection을 통해 getter, setter 없어도 사용된다고 했는데 즉, getter,setter를 사용하지 않고 접근한다는 것인데, getter 위에 @Id 가 적용되었는데 어떻게 동작하고 있지? 란 의문

이에 관해 jpa 접근 전략과 코틀린의 프로터피 개념에 대해 먼저 정리해보았다.
그 후 이슈 발생이유와 해결방안을 다시 정리해보겠다.

JPA Access Strategy

JPA가 Entity에 접근하는 방법은 2가지다.

  • 필드 접근
    • @Access(AccessType.FEILD)
    • 필드를 통해 엔티티에 접근한다.
  • 프로퍼티 접근
    • @Access(AccessType.PROPERTY)
    • getter, setter 메서드를 통해 엔티티에 접근한다.

@Access 어노테이션이 선언되어 있지 않으면, @Id 어노테이션이 선언된 위치(필드, getter)에 따라 access 방식이 설정된다.

/** 필드 접근 방식 */
@Entity
public class Member {
	@Id
	private Long id;
	private String name;
}

/** 프로퍼티 접근 방식 */
@Entity {
	private Long id;
	private String namel

	@Id
	public String getId() {
		return this.id;
	}
}

Kotlin Property

class Jordan {
	var name: String = "Jordan"
	var age: Int = 20
}
  • name, age를 코틀린에서는 변수가 아닌 프로퍼티라고 부른다.
  • 코틀린에서는 프로퍼티가 언뜻 보기에는 변수처럼 보이지만, getter/setter 와 같은 함수가 내장되어 있기 때문에 프로퍼티라고 부른다.
  • 코틀린에서 클래스를 선언하고, 내부에 프로퍼티를 선언하면 각 프로퍼티는 코틀린 내부에서 기본적으로 getter, setter 함수를 만들어준다.
    (그래서 코틀린에서 객체의 프로퍼티에 접근하는 것은 변수에 직접 접근하는 것이 아닌 Accessor 메서드(getter & setter) 를 통해서 이루어진다.)

use-site target

  • 코틀린은 어노테이션을 사용할 때 use-site target이라는 기능을 지원하는데, 이는 해당 어노테이션을 적용할 범위를 지정하는 것이다.
//@file:JvmName("AppKta" ) // 생성되는 클래스 파일 이름 지정
annotation class TA ()

// 예시 어노테이션
annotation class TA1()

class ClassA (
   @field:TA val first: String,
   @get:TA val second: String, // property getter
   @set:TA var second1: String, // property setter
   @property:TA val fourth: Long, // are not visible to Java
   @param:TA val fifth: String, // constructor의 parameter
   @setparam:TA var sixth: String, // setter의 parameter...set ( value )
   @property:[TA TA1] val seventh: String, // 다수의 Use-site Target annotation 적용 방법1
   @property:TA @property:TA1 val eighth: String // 다수의 Use-site Target annotation 적용 방법2
) {}

어노테이션에 대한 자세한 설명은 https://logictocore.tistory.com/50 참고


그래서 @get 이 왜 문제였느냐?

// 문제 상황
@Entity
open class Example {
    @get:Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @get:Column(name = "example_id", nullable = false)
    open var id: Long? = null
...
  • @get@GeneratedValue 앞에 선언되지 않았다.
  • @get 어노테이션으로 인해 JPA에서 프로퍼티 전략을 사용하여 entity에 접근하게 되고,
    위와 같은 상황인 코틀린 코드를 자바 코드로 변환하면 필드에는 @GeneratedValue 만 선언되고, @Id@Colum은 getter에 선언된다. ⇒ 그래서 프로퍼티 전략으로 인해 필드에 선언된 @GeneratedValue 가 적용되지 않았던 것이다.
// 문제상황 자바코드
public class Example {
   @GeneratedValue(
      strategy = GenerationType.IDENTITY
   )
   @Nullable
   private Long id;
...

	 @Id
   @Column(
      name = "example_id",
      nullable = false
   )
   @Nullable
   public Long getId() {
      return this.
   }
...

해결

@GeneratedValue 에 get 어노테이션을 적용시킨다.

// 해결 코드
@Entity
open class Example {
    @get:Id
    @get:GeneratedValue(strategy = GenerationType.IDENTITY)
    @get:Column(name = "example_id", nullable = false)
    open var id: Long? = null
...
// 자바 코드
public class Example {
   @Nullable
   private Long id;
...

	 @Id
   @GeneratedValue(
      strategy = GenerationType.IDENTITY
   )
   @Column(
      name = "example_id",
      nullable = false
   )
   @Nullable
   public Long getId() {
      return this.id;
   }
...

마지막 의문> entity에 get 사용 이유..

Intellij generated 기능을 사용해서 entity를 생성했을 때 @get 이 붙여서 생성된다.
@get 의 사용법을 알지 못한 상태에서 이슈가 발생하여 @get에 대해 정확히 알게 되었고, 그래서 현재 상황에서 @get을 사용하는 이유가 궁금했다.

일단 entity에 get을 사용한 예시를 찾아보려 했는데, 아무리 구글링을 해보아도 찾지 못했다.

현재 getter를 통해서 하이버네이트 관련 어노테이션만 적용하고 있고, getter 로직 내에 별도의 비즈니스 코드를 사용하고 있는것도 아니다. 즉, @get이 없이도 사용가능한데 왜 사용하고 있는지 궁금했다.

그리고 프로퍼티 접근보다 필드 접근 방식을 더 선호한다는 아래와 같은 글을 찾았다.
참고 https://thorben-janssen.com/access-strategies-in-jpa-and-hibernate/

5번째 이유인,, 프로퍼티 접근 방식을 사용했을때 예상치 못한 위험이 발생할 수 있다고 하니깐… 안전하게 필드 접근 방식을 사용하는게 어떤가 생각하였다.

0개의 댓글