Back End/Spring Data JPA

[Spring JPA] @MappedSuperClass 사용시 주의할 점

DevPing9_ 2022. 3. 4. 22:03

필자는 처음 JPA를 패스트캠퍼스에서 간단하게 배웠다.

 

그 중 @MappedSuperClass 가 있었는데 누가봐도 상속을 이용하여

생산성 좋은 코드를 만들 수 있는 매우매우 좋은 기능이었다.

 

강의의 예제에서 @MappedSuperClass 가 붙은 추상클래스에 엔티티의 ID 필드를 작성하셔서

사이드프로젝트 초기에 당연히 추상클래스 필드에 ID필드를 넣었다.

 

이로 인해 매우 귀찮았던 썰을 풀어본다.

 

* 참고로 개인적으로 JPA 스터디에 참여하면서 영한님 강의를 수강하게 되었는데,

영한님은 내가 겪은 사항을 미리 경고하시고 ID필드를 넣지 말기를 권고 하셨다. (호엥...)

 

 

한국인은 본론 먼저!

@MappedSuperClass 의 추상클래스에 @Id 필드를 절대 넣지말자.

 

@Id를 가진 필드를 넣게 되면 @Id에 의존적이게 되므로 코드 확장성이 매우 떨어진다.

 

항상 상위클래스를 만들 때, 내부에 선언할 요소들이 어디까지 영향을 미칠 수 있을지 생각하자.

 

다다익선은 코딩에 적용되지 않는다.

 

무조건 상속이 좋은 것은 아니다.

 

[디자인패턴] 상속보다는 컴포지션을 사용하자!!!

현업자들이 모여있는 오픈톡방에서 상속과 컴포지션에 대한 이야기가 흘러나왔다. ㅎㅎ ...;;; 정말 난 모르는게 많구나....  이제라도 알아가면 되지 뭐!!! 아래의 블로그포스팅을 쓰신분이 여러

developer-ping9.tistory.com

 

 

 

 

 

일기장

 

아래와 같이 BaseEntity 라는 엔티티에 상속시킬 추상클래스를 만들었다고 하자.

 

BaseEntity.class

@MappedSuperclass
@Getter
@Setter
@ToString
public abstract class BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /*...*/
}

 

 

문제점이 무엇일까?

 

첫번째로 id의 자료형이 고정된다.

두번째로 @GeneratedValue 까지 섞여있어 id를 DB에 위임하고 싶지 않아도 방법이 없다.

 

 

필자가 이 문제를 마주한건 업무키(비지니스키)를 구성할 때 였는데, 대충 이러하다.

 

 

Item.class (복합키)

@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name="dtype")
@Getter @Entity
public class Item extends BaseEntity implements Persistable<String>{
    
    /* Table-Field Area */
    @Id
    private String pk;
    
    @Column(name = "user_id")
    private Long userId; // FK
    private Long itemId; 

    public static String makePK(Long itemId, Long userId){
        StringBuilder sb = new StringBuilder();
        sb.append(String.valueOf(itemId));
        sb.append(", ");
        sb.append(String.valueOf(userId));
        return sb.toString();
    }
    
    public Item(
        Long itemId, Long userId
    ){
        this.pk=makePK(itemId, userId);
        this.itemId=itemId;
        this.userId=userId;
    }

}

 

 

Item의 PK를 itemId+userId의 조합으로 unique하게 구성하고 싶었다.

 

사실 데이터 벌크 CRUD 연산을 구현하면서 생긴 문제를 해결하려고 인조키를 만든거긴 한데

 

BaseEntity 안에 @Id 필드가 있으므로, Item.class는 런타임 때 오류를 발생시킬 것이다.

(아마 @Id가 두개니 복합키 설정하라고 오류메세지가 나올듯?)

 

이 때 BaseEntity 를 다른사람이 만들었거나 오래전에 만들어 잊어버린 경우 오류메세지를 보고 잘못된 방향으로 길을 걸어 시간을 허비하게 될 수도 있다.

 

나중에 근본적인 원인을 알게 되더라도 BaseEntity 를 수정해야하며, 이미 BaseEntity를 상속받아서 사용하고 있는 다른 도메인(엔티티)에 까지 영향을 미치게 된다. 

(만약에 BaseTime -> BaseCount -> ... -> BaseEntity 로 계속 상속받아 구성했을 경우, 엔티티마다 다른 레벨의 BaseEntity를 상속받아서 구현되어있을 경우 리팩토링해야 할 코드가 기하급수적으로 많아진다.)

 

 

그 당시 필자는 BaseEntity를 수정하기 위해 거의 15개가 넘어가는 클래스 파일들을 수정하고 생성했던 것으로 기억한다.

 

그래도 이때의 경험으로 상속의 위험성도 알게 되었고, 특히 상위클래스 내부에 생각없이 아무거나 막 넣지 말자라는 다짐도 하게 되었다.

 

덕분에 클래스 파일 하나를 정의할 때, 이 파일 하나가 어디까지 영향을 끼칠 수 있는지 생각해보는 좋은 습관도 생겼다. 

 

다다익선은 코딩에 적용되지 않는다.

 

 

 

 

728x90