유미의 기록들

[최종 프로젝트] Entity 계층 구조에 따른 상속 관계 매핑 본문

대외활동 기록/내일배움캠프

[최종 프로젝트] Entity 계층 구조에 따른 상속 관계 매핑

지유미 2024. 11. 7. 23:55
728x90
반응형

💡  배경

최종 프로젝트를 하면서 스터디 파티 모집 게시글, 개발 커뮤니티 게시글, 프로젝트 게시글에 공통적으로 이미지 첨부 파일을 추가해야 하는 요구사항이 있었습니다. 

 

초기에는 하나의 이미지 첨부 파일 테이블에 모든 데이터를 넣는 단일 테이블 전략으로 설계했습니다. 하지만 테이블의 크기가 커지게 되고, NULL값을 가지는 속성이 많아지는 단점이 있었습니다.

 

따라서 다른 테이블 전략이 없는 지 찾아보았습니다

 

📝  상속 관계 매핑

관계형 데이터베이스에는 객체에서의 상속 관계가 없고, 객체의 상속과 유사한 슈퍼타입 / 서브타입 관계를 매핑하는 상속 관계 매핑이 존재한다

슈퍼타입과 서브타입의 논리모델을 실제 물리 모델로 구현하는 방법은 3가지가 있다. 각 구현 방법의 장단점을 비교해 보려고 한다.

 

1. 단일 테이블 전략 (SINGLE_TABLE)

데이터베이스의 논리모델을 한 테이블로 합친 것을 말한다. 초기에 설계했던 이미지 첨부파일 테이블과 유사하다

 

장점

- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다

- 조회 쿼리가 단순하다

 

단점

- 자식 엔티티가 매핑한 컬럼은 모두 NULL 값이 허용되어야 한다

- 하나의 테이블에 모든 것을 저장하기 때문에 테이블의 크기가 커질 수 있다 따라서 상황에 따라 조회 성능이 오히려 느려질 수 있다.

 

 

2. 조인 전략 (JOINED)

슈퍼타입과 서브 타입으로 나누어 슈퍼타입에 공통적인 부분을 두고 조인하는 방식으로 객체의 상속 구조와 가장 유사하다

 

장점

- 테이블구조를 정규화할 수 있다

- 불필요한 컬럼이 포함되지 않아 데이터 중복을 줄일 수 있어서 저장공간이 효율적이다

- 외래키 참조 무결성 제약조건 활용이 가능하다

 

단점

- 조회 시 조인을 해야 해서 성능이 저하될 수 있다

- 조회 쿼리가 다른 전략에 비해 복잡하다

- 데이터 저장 시에 INSERT문이 2번 호출된다

 

3. 구현 클래스 마다 테이블 전략 

공통된 슈퍼타입 없이 각 테이블을 만든다

이 전략은 여러 자식 테이블을 함께 조회할 때 성능이 느리고, 자식 테이블을 통합해서 쿼리하기 어렵기 때문에 가급적이면 사용하지 않는 것이 좋다고 한다

데이터 베이스 설계자와 ORM 전문가 둘 다 추천하지 않는다고 한다

 

 

🚀  의사결정

우리는 조인 전략을 사용하여 이미지 첨부 파일을 부모 엔티티로 가지고, 각 게시글 테이블 간의 관계를 분리하여 설계하였습니다.

조인 성능은 더 안좋아 질수 도 있지만 불필요한 컬럼이 포함되지 않아 데이터 중복을 줄일 수 있고, 테이블 구조를 정규화할 수 있다는 장점과 새로운 게시글 유형이 추가 될 때 기존 구조에 영향을 주지 않고, 새로운 테이블을 추가하여 유연성과 확장성 면에서 효율적이라고 판단하였습니다.

 

 

 

슈퍼타입 (Attachment)

@Getter
@NoArgsConstructor
@Entity
@DiscriminatorColumn
@Inheritance(strategy = InheritanceType.JOINED) //조인 전략 사용
public abstract class Attachment extends Timestamped {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    private URL imageURL;

    @NotNull
    @Enumerated(EnumType.STRING)
    private FileFormat fileFormat;

    @NotNull
    private String fileName;

    public Attachment(URL imageURL, FileFormat fileFormat,String fileName) {
        this.imageURL = imageURL;
        this.fileFormat = fileFormat;
        this.fileName = fileName;
    }

    public void updateAttachment(URL imageURL, FileFormat fileFormat,String fileName){
        this.imageURL = imageURL;
        this.fileFormat = fileFormat;
        this.fileName = fileName;
    }
}

 

서브타입 (PartyAttachment)

@Getter
@NoArgsConstructor
@Entity
@DiscriminatorValue("PARTY")

public class PartyAttachment extends Attachment {

    @NotNull
    private Long partyId;

    private PartyAttachment(Long partyId, URL imageURL, FileFormat fileFormat , String fileName){
        super(imageURL, fileFormat, fileName);
        this.partyId = partyId;
    }
    public static PartyAttachment of(Long partyId, URL imageURL, FileFormat fileFormat ,String fileName){
        return new PartyAttachment(partyId, imageURL, fileFormat,fileName);
    }
}
728x90
반응형
Comments