테이블을 설계하다 보면 자기 자신을 참조하는 경우가 있다. 이를 JPA로 나타내자면 아래와 같다.

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Category {

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

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Category parent;

    @Builder.Default
    @OneToMany(mappedBy = "parent")
    private Set<Category> children = new HashSet<>();
}

 

 

이와 다른 형태로는 아래와 같은데 이와 다르게 자식 테이블이 별도로 생성되어 이 경우 불필요한 join query가 불필요하게 발생되어 아래와 같은 방식 보단 첫번째 방식으로 계층형 테이블을 설계해야 한다

 

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Category {

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

    private String name;

    @OneToMany
    private Set<Category> children = new HashSet<>();
}

 

반응형

SQL과 객체지향언어가 갖는 큰 차이점 중 하나가 바로 상속의 유무이다.

객체 지향언어는 상속을 지원하지만, 데이터베이스는 상속을 지원하지 않는다. 

 

스프링에서는 JPA를 이용하여 ORM 기술을 사용하는데, 클래스 간 상속 관계에 대해서는 어떻게 정의를 하는지 알아보자.

 

물리 모델을 데이터 베이스 테이블로 구현하는 방법에는 크게 4가지 방법이 존재한다.

 

- MappedSupperclass

- Single Table 전략

- Joined Table 전략

- Table per Class 전략

 

MappedSuperclass 

 

MappedSuperclass 어노테이션은 부모클래스를 데이터베이스의 테이블로 매핑하지 않고 자식클래스에게 매핑 정보를 제공하고 싶을 때만 사용한다.

 

특징

- MappedSuperclass 어노테이션을 사용한 클래스는 엔티티가 아니다. 따라서 해당 클래스를 가지고 영속성 컨텍스트 또는 JPQL 등에서 사용할 수 없다.

- MappedSuperclass 어노테이션을 사용한 클래스는 자체적으로 의미가 없기에 추상 클래스로 만드는 것이 좋다.

 

Single Table 전략

 

Single Table 전략은 각 클래스 계층에 대해 하나의 테이블을 만들며 명시적으로 지정하지 않는 경우 JPA에서는 Default로 해당 전략을 사용한다. 

discriminatorType으로 식별자를 integer로 할 것 인지 혹은 다른 타입으로 지정할 것인지 선택할 수 있다. name을 지정하게 되면 해당 식별자의 이름이 지정되어 테이블로 들어간다. 

 

특징

- 싱글 테이블이기 때문에 조인 쿼리를 사용하지 않고 한 번에 조회가 가능하다.

- 싱글 테이블이기 때문에 null 값이 들어가는 자료가 생긴다.

- 단일 테이블로 유지하기 때문에 테이블의 크기가 커진다.

 

Joined Table 전략

 

조인 전략은 계층 구조를 띄는 각 클래스가 해당 테이블로 매핑된다. 이 때, 테이블마다 반복되는 열이 하나가 있는 데, 이는 조인할 때 반드시 필요한 식별자 이다. 

 

특징

- 테이블을 정규화 할 수 있다.

- 저장공간을 효율적으로 사용한다 이는 정규화를 실시했기 때문이다.

- 조회할 때 외래키를 이용하여 조인 쿼리를 사용하기 때문에 성능이 좋지 않다.

- 데이터 등록 시 쿼리가 한 번 더 발생한다. 

 

Table per Class

 

Table per Class 는 각각의 테이블을 모두 생성하는 전략이다. 결과적으로 MappedSuperclass를 사용한 것돠 유사하다. 그러나 Table per Class 전략에서는 상위 클래스에 대한 엔티티를 정의하기 때문에 상위 클래스를 연결 및 쿼리 사용이 가능하다.

 

특징

- 서브 타입과 구분해서 처리할 때 효과적이다.

- 상속 없이 각 엔티티를 매핑하는 것과 다르지 않아, Union을 사용하여 쿼리를 작성한다.

- Union 쿼리로 인해 성능이 저하될 수 있다.

반응형

Springboot2로 운영되는 서비스를 Springboot3 로 버전 업을 하게 되었을 시에 javax 대부분의 패키지가 jakarta 패키지로 이관되어 기존 QueryDSL의 JPAQueryFactory는 javax.persistence 패키지를 참조하고 있어 호환이 불가능 하기에 아래처럼 수정하면 에러가 해결된다.

// build.gradle

implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

 

 

반응형

'SpringBoot > JPA' 카테고리의 다른 글

계층형 테이블 구현  (1) 2024.10.09
Inheritance 전략  (0) 2024.10.08
값 타입과 값 타입 컬렉션에 관한 사용법  (0) 2023.04.07

값 타입인 경우

@Entity
public class DeliveryJpaEntity {
    ...
    
    @Embedded	// 생략 가능
    private AddressJpaEntity address;
    
    ...
}

@Embeddable
public class AddressJpaEntity {

    private String address;

    private String ji;

    private String bun;
}

 

값 타입 컬렉션인 경우

- 값 타입을 컬렉션에 담아서 쓰는 걸 값 타입 컬렉션이라 한다.

- 값 타입 컬렉션은 영속성 전이(Casecade)와 고아 객체 제거(Orphan remove)가 Default 이다.

@Entity
public class DeliveryJpaEntity {

    ...
    
    @ElementCollection	// 기본 fetch 전략은 lazy 이다.
    @CollectionTable(name = "delivery_item", joinColumns = @JoinColumn(name = "delivery_id"))
    private List<DeliveryItemJpaEntity> deliveryItems = new ArrayList<>();
    
    ...
}

@Embeddable
public class DeliveryItemJpaEntity {

    private String name;

    private Long quantity;

    private BigDecimal amount;
}

 

값 타입 컬렉션의 한계

엔티티는 ID로 DB에서 찾을 수 있기 때문에 쉽게 CRUD가 가능하나, 값 타입은 식별자라는 개념이 없기에 원본데이터를 찾기 어렵다, 값 타입 컬렉션은 별도의 테이블에 저장되는데 이 테이블 안에 있는 값타입의 값이 변경 되면 기존 값 타입 컬렉션을 제거하고 컬렉션 전체를 새롭게 저장하는 방식으로 이뤄진다. 때문에 컬렉션의 양이 많아지면 쿼리 수가 많아지기 때문에 컬렉션의 데이터가 많으면 값 타입 컬렉션 대신 일대다 관계 엔티티로 수정하는 것을 고려해야 한다.

반응형

'SpringBoot > JPA' 카테고리의 다른 글

계층형 테이블 구현  (1) 2024.10.09
Inheritance 전략  (0) 2024.10.08
Springboo3 으로 변경 했을시에 QueryDSL 변경 점  (0) 2023.06.09

+ Recent posts