[Spring]JPA 상속 관계에서 필드 shadowing 문제 해결하기

JUNYOUNG·2024년 12월 22일
post-thumbnail

문제 상황

서비스의 유저 타입들은 @Inheritance(strategy = InheritanceType.JOINED)@DiscriminatorColumn을 사용하여 구분한다. 이때 필드를 중복 정의하면서 shadowing 문제가 발생했는데, 다음과 같은 현상이 나타났다:

  1. Getter와 toString() 메서드의 불일치
    • toString()에서는 부모 클래스의 firm 필드를 사용해 데이터를 출력.
    • getFirm() 메서드는 자식 클래스의 firm 필드를 참조하여 null을 반환.
  2. 데이터 접근 불가 현상
    • 리플렉션으로 firm 필드 값을 직접 확인하면 데이터가 존재하지만, getter 메서드를 호출하면 null이 반환됨.
  3. Entity Graph와 LAZY 로딩 설정 문제 없음
    • 데이터 접근 문제는 JPA 설정상의 문제 때문이 아니라, 필드 shadowing으로 인해 발생.

원인 분석

1. JPA 상속 구조 검토

  • 부모 클래스(User)에는 이미 firm 필드가 정의되어 있음.
  • 자식 클래스(LawyerUser)에서도 firm 필드를 중복 정의함으로써 shadowing 현상이 발생.

2. 필드 shadowing 현상

  • JPA 엔티티에서 동일한 필드명을 부모와 자식 클래스에 정의하면 shadowing 문제가 발생한다. 이로 인해:
    • toString() 메서드는 부모 클래스의 firm 필드를 참조.
    • getFirm() 메서드는 자식 클래스의 firm 필드를 참조하여 초기화되지 않은 값을 반환.

User 클래스

@Getter
@Entity
@Table(name = "users", schema = "")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("general") // 기본값
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Integer id;
    ..
    ..
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "firm_id", nullable = true) // 소속 Firm
    private Firm firm;
}

문제 해결

수정 전 코드


@Entity
@DiscriminatorValue("layers")
@Table(name = "users_lawyer", schema = "lemon")
public class LawyerUser extends User {
@Column(name = "firm", length = 255)
private String firm; // 중복 정의된 필드

@Column(name = "license_number", length = 100)
private String licenseNumber;

@Column(name = "license_file", length = 255)
private String licenseFile;

@Column(name = "firm_phone", length = 20)
private String firmPhone;
}

해결책

  • 중복 정의된 firm 필드 제거: 부모 클래스(User)에서 정의된 firm 필드를 그대로 사용하도록 변경.

수정 후 코드

@Entity
@DiscriminatorValue("layers")
@Table(name = "users_lawyer", schema = "lemon")
public class LawyerUser extends User {

@Column(name = "license_number", length = 100)
private String licenseNumber;

@Column(name = "license_file", length = 255)
private String licenseFile;

// firm 필드 제거 - 이미 User 클래스에 정의되어 있음

@Column(name = "firm_phone", length = 20)
private String firmPhone;
}

경험을 통해 배운 점

  1. JPA 상속 관계 설계 시 주의점
    • 부모 클래스의 필드를 자식 클래스에서 중복 정의하지 않도록 주의해야 한다.
    • 엔티티 구조를 설계할 때, 상속 계층 구조를 꼼꼼히 검토하는 것이 중요하다.
  2. 디버깅의 중요성
    • 처음에는 JPA 프록시나 Lombok 관련 문제로 오해했지만, 디버깅을 통해 문제의 본질을 파악할 수 있었다.
    • Reflection API를 활용해 필드 값과 실제 타입을 확인한 것이 문제 해결에 큰 도움이 되었다.
  3. JPA와 객체 지향 설계 원칙의 조화
    • 상속 구조를 설계할 때, 객체 지향 원칙뿐만 아니라 JPA의 동작 방식도 함께 고려해야 한다.

마무리

이번 문제는 단순히 코드 수정에 그치지 않고, 설계의 중요성을 다시 돌아보는 계기가 되었다.
처음에는 원인을 찾는 데 어려움을 겪었고, 문제의 본질을 파악하기 위해 디버깅과 가설 검증을 반복하며 꽤 오랜시간이 걸렸다.
JPA 프록시 문제나 설정 오류로 오인하며 여러 단계를 거쳤지만, 결국 중복 정의된 필드가 원인임을 발견했다.

문제 해결 후에는 "생각보다 간단하네"라는 허무함이 들었다. 그러나 이 과정을 통해 설계의 작은 실수가 데이터 접근과 동작에 얼마나 큰 영향을 미칠 수 있는지 실감했다.


추가 설명

Shadowing 문제는 상위 클래스(부모)와 하위 클래스(자식) 간에 같은 이름의 필드나 변수가 정의될 때 발생하는 문제.
하위 클래스에서 같은 이름의 필드를 다시 정의하면, 이 필드가 상위 클래스의 필드를 가리게(덮어씌우게) 된다.
결과적으로, 상위 클래스의 필드에 접근하려고 해도 하위 클래스의 필드가 대신 사용된다.

이 현상은 JPA뿐만 아니라 일반적인 객체 지향 프로그래밍에서도 발생할 수 있으며,
다음과 같은 부작용을 초래한다

  • 혼란: 코드가 의도한 대로 동작하지 않거나, 어떤 필드가 참조되고 있는지 명확하지 않음.
  • 디버깅 어려움: 문제의 원인이 shadowing인지, 설정 문제인지 파악하기 어려움.
  • 데이터 불일치: 읽기와 쓰기가 서로 다른 필드를 참조하면서 데이터가 일관되지 않게 됨.

해결 방법은 중복 정의를 피하고, 명확한 설계를 통해 하나의 필드만 사용하도록 하는 것

profile
Onward, Always Upward - 기록은 성장의 증거

0개의 댓글