이끌든지 따르든지 비키든지

Framework/JPA

[JPA] 패러다임 불일치

SeongHo5 2024. 1. 4. 23:02

패러다임 불일치는 객체 지향 프로그래밍(OOP)과 관계형 데이터베이스(RDB) 간의 근본적인 차이점에서 발생하는 문제를 말한다.

 

 

객체 지향 프로그래밍은 데이터와 행동을 함께 갖는 객체를 중심으로 하는 반면, 

관계형 데이터베이스는 데이터를 테이블 형태로 구조화한다.

 

이 두 시스템 사이의 차이로 인해 여러 문제가 발생할 수 있는데,

이 글에서는 JPA가 패러다임 불일치 문제를 해결하기 위해 어떠한 방법을 사용하는지 알아본다.

 


패러다임 불일치 문제와 JPA의 해결책

  • 상속과 다형성
    • (문제) 객체 지향 언어에서는 상속을 통해 재사용과 확장이 가능하지만, 관계형 데이터베이스는 이러한 계층적 구조를 직접적으로 지원하지 않는다.
    • (해결) JPA는 클래스 계층을 DB 테이블과 매핑하는 여러 전략을 제공한다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String manufacturer;

    // 생성자, getter, setter 생략
}


@Entity
public class Car extends Vehicle {
    private int numberOfSeats;

    // 생성자, getter, setter 생략
}


@Entity
public class Bike extends Vehicle {
    private boolean hasBasket;

    // 생성자, getter, setter 생략
}

 

@Inheritance 를 통해 매핑 전략을 지정할 수 있고, 이를 통해 상속 구조를 매핑할 수 있다.

이 예시는 단일 테이블(SINGLE_TABLE) 전략으로, 모든 클래스를 단일 테이블에 매핑하는 전략이다.

 

 

  • 연관성 관리
    • (문제)  객체는 참조를 통해 다른 객체와 연결되지만, 관계형 데이터베이스는 테이블 간의 관계를 외래 키를 통해 관리한다.
    • (해결) JPA는 @OneToOne, @OneToMany, @ManyToOne, @ManyToMany 등의 어노테이션으로 객체 간의 관계를 정의하고, 이를 데이터베이스 테이블 관계로 변환한다.
@Entity
public class User {
    @OneToMany(mappedBy = "user") // 1:N 관계
    private List<Order> orders;
}

@Entity
public class Order {
    @ManyToOne // N:1 관계
    @JoinColumn(name = "user_id")
    private User user;
}

 

@ManyToOne, @OneToMany 어노테이션으로 User 엔티티와 Order 엔티티 사이의 관계를 설정했다.

이러한 매핑을 통해 객체 지향적인 방식으로 데이터베이스의 관계를 표현할 수 있다.

 

 

  • 식별성 문제
    • (문제) 객체의 동일성은 인스턴스에 의해 결정되지만, 관계형 데이터베이스에서는 행의 동일성이 주로 기본 키에 의해 결정된다.
    • (해결) JPA는 @Id@EmbeddedId@IdClass 등의 어노테이션으로 객체의 식별자(ID)를 지정한다.
@Entity
public class Product {

    @Id
    private Long id; // 기본 키 필드

    // 나머지 필드 및 메소드
}

 

위 예시에서 @Id 어노테이션은 id 필드가 해당 엔티티의 기본 키(Primary Key) 임을 나타낸다.

 

@Embeddable
public class OrderId implements Serializable {
    private Long userId;
    private Long orderId;

    // 생성자, getter, setter, equals, hashCode 생략
}

@Entity
public class Order {
    @EmbeddedId
    private OrderId id; // 복합 키

    // 나머지 필드 및 메소드
}

 

@EmbeddedId 어노테이션을 사용하면 복합 키를 가지는 엔티티의 식별자를 정의할 수 있다.

복합키를 구성하는 필드를 가지는 별도 클래스를 정의하고, 엔티티는 이 클래스를 복합 키로 사용한다.

 

 

  • 데이터 내비게이션
    • (문제) 객체 지향 언어에서는 객체 참조를 통해 쉽게 다른 객체로 이동할 수 있지만, 데이터베이스에서는 조인 연산을 통해 관련 데이터에 접근해야 한다.
    • (해결) JPA는 지연 로딩과 즉시 로딩 전략을 제공하고, 이를 통해 필요에 따라 관련 객체를 자동으로 로드한다.
@Entity
public class User {
    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private List<Order> orders; // 사용자가 가진 주문들
}

@Entity
public class Order {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user; // 이 주문을 소유한 사용자
}

 

위 코드에서 User 엔티티는 여러 Order 1:N의 관계를 가지고 있는데,

fetch 속성을 통해 해당 관계의 엔티티를 언제 로딩할 것인지를 결정할 수 있다. 

FetchType.LAZY는 지연 로딩을 의미하며, 이는 관련 데이터가 실제로 필요할 때까지 로딩을 지연시킨다.

반대로 FetchType.EAGER는 즉시 로딩을 의미하며, 엔티티가 로드될 때 관련 데이터도 함께 로드된다.

 

이 로딩 전략을 적절히 활용해 애플리케이션의 성능을 최적화하는 것이 바람직하다.

(기본적으로 지연 로딩 전략을 채택하고, 필요한 경우에만 즉시 로딩을 사용한다던지)