728x90

 

 

  1. 서브타입 테이블로 구현 (구현 클래스마다 테이블을 만드는 전략) - @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) 각각의 클래스마다 별도의 데이터베이스 테이블을 생성하여 클래스에 정의된 속성을 저장하는 방법입니다. 각 클래스에 대한 테이블은 해당 클래스의 속성을 직접 매핑하여 저장하며, 부모 클래스와 자식 클래스 간의 관계를 데이터베이스 수준에서 처리합니다.

장점 

  1. 클래스별 정규화: 각 클래스에 대한 별도의 테이블을 사용하므로 데이터를 정규화하여 중복을 줄일 수 있습니다. 클래스에 특화된 속성만 저장하여 데이터 일관성을 유지하기 쉽습니다.
  2. 각 테이블에 특화된 제약 조건: 각 테이블에 클래스에 특화된 제약 조건을 선언적으로 추가할 수 있습니다. 이로써 데이터 무결성과 검증이 강화됩니다.
  3. 선언적인 관계: 관계를 선언적으로 정의하여 객체 간의 연결성을 유지할 수 있습니다.

단점

  1. 복잡성: 각 클래스마다 테이블이 생성되므로 데이터베이스 스키마가 복잡해질 수 있습니다. 쿼리 시 조인을 사용해야 하며, 복잡한 쿼리 작성이 필요할 수 있습니다.
  2. 조회 성능: 데이터가 여러 테이블에 분산되어 있기 때문에 쿼리 시 조인을 사용해야 하며, 이로 인해 성능이 저하될 수 있습니다.
  3. 변경 관리: 클래스의 변경이 발생하면 해당 클래스에 대응하는 테이블도 변경해야 합니다. 클래스 구조의 변경이 데이터베이스 스키마 변경으로 이어지므로 유지 관리에 주의가 필요합니다.
  4. 비효율적인 공간 사용: 테이블 간 중복 데이터가 발생할 수 있고, 인덱스 및 제약 조건도 중복될 수 있어 공간 사용이 비효율적일 수 있습니다.

 

2. 조인 테이블로 구현 (조인 전략) - @Inheritance(strategy = InheritanceType.JOINED) 각각의 클래스에 대해 별도의 데이터베이스 테이블을 생성하되, 부모 클래스와 자식 클래스 간의 관계를 조인을 통해 처리하는 방법을 의미합니다. 이로써 부모 클래스와 각 하위 클래스 간의 데이터 일관성을 유지하면서도 각 클래스에 특화된 속성을 저장할 수 있습니다.

장점

  1. 정규화와 일관성: 각 클래스에 특화된 테이블을 사용하므로 데이터를 정규화하여 중복을 줄일 수 있습니다. 데이터 일관성을 유지하면서 각 클래스의 특성을 저장할 수 있습니다.
  2. 유연성: 새로운 하위 클래스를 추가하거나 기존 클래스를 변경해도 데이터베이스 스키마를 변경하지 않아도 됩니다. 클래스 구조의 변경이 데이터베이스 스키마 변경으로 이어지지 않습니다.
  3. 쿼리 성능: 각 클래스의 특화된 테이블을 사용하기 때문에 필요한 속성만 조회하면 됩니다. 조인이 필요하지 않으므로 쿼리 성능이 향상될 수 있습니다.
  4. 클래스 간 분리: 각 클래스에 해당하는 테이블을 별도로 유지하므로 클래스 간의 결합도가 낮아집니다. 각 클래스를 독립적으로 관리하고 확장할 수 있습니다.

단점

  1. 복잡한 쿼리: 필요한 속성을 조회하려면 조인을 사용해야 하므로 쿼리가 복잡해질 수 있습니다. 특히 클래스 계층이 복잡한 경우에는 조인이 많이 발생할 수 있습니다.
  2. 조인의 영향: 조인은 데이터베이스 성능에 영향을 미칠 수 있습니다. 조인된 테이블의 크기와 인덱스 사용 등을 고려하여 성능을 최적화해야 합니다.
  3. 데이터 일관성 유지: 여러 테이블에 데이터가 분산되어 있기 때문에 데이터 일관성을 유지하는 데 주의가 필요합니다. 부모 클래스와 하위 클래스 간의 관계를 올바로 설정해야 합니다.
  4. 스키마 복잡성: 각 클래스에 특화된 테이블을 유지하면서도 클래스 간의 관계를 조인으로 처리하기 때문에 스키마가 복잡해질 수 있습니다.

 

Join 전략에서는 @DiscriminatorColumn어노테이션을 사용하는데, 상속 계층이 관계형 데이터베이스에 저장되는 방법의 세부 정보를 지정하는 데 사용됩니다. 일반적으로 단일 테이블 상속, 조인 테이블 상속 또는 클래스별 테이블 상속과 같은 상속 전략과 함께 사용됩니다.

 

	
    @Entity
		@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
		@DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
		public class Vehicle {
		    // 모든 차량에 공통적인 속성 및 메서드
		}
		
		@Entity
		@DiscriminatorValue("CAR")
		public class Car extends Vehicle {
		    // 자동차에만 해당하는 속성 및 메서드
		}
		
		@Entity
		@DiscriminatorValue("BIKE")
		public class Bike extends Vehicle {
		    // 자전거에만 해당하는 속성 및 메서드
		}

 

이 예시에서 Vehicle 클래스는 두 개의 하위 클래스 Car와 Bike를 가진 부모 클래스입니다. @DiscriminatorColumn 주석은 디스크리미네이터 컬럼의 이름이 "dtype"이고 데이터 유형이 문자열임을 지정합니다. 하위 클래스는 @DiscriminatorValue 주석을 사용하여 디스크리미네이터 컬럼에서 해당 유형을 나타내는 값을 지정합니다.

@DiscriminatorColumn이 작동하는 방식은 다음과 같습니다.

상속 전략 : 관계형 데이터베이스에 매핑하려는 클래스의 계층 구조(부모 및 자식 클래스)가 있는 경우 이를 어떻게 저장할지 결정해야 합니다. JPA는 이를 위한 다양한 전략을 제공하며, 그 중 하나가 디스크리미네이터 기반 전략입니다.

DiscriminatorColumn : 데이터베이스 테이블에서 각 행에 저장된 객체 유형에 대한 정보를 보유하는 열입니다. 이는 어떤 하위 클래스가 해당 행에 해당하는지를 나타냅니다. 이를 통해 JPA 공급자는 데이터베이스를 쿼리할 때 올바른 유형의 객체를 검색할 수 있습니다.

@DiscriminatorColumn : 디스크리미네이터 컬럼을 구성하려면 @DiscriminatorColumn 을 사용합니다. 이 주석은 일반적으로 계층 구조의 부모 클래스에 배치됩니다. 이를 사용하여 디스크리미네이터 컬럼의 이름, 데이터 유형 및 필요한 경우 각 하위 클래스에 해당하는 값을 지정할 수 있습니다.

 

DB입장에서는 어떤 전략을 사용하여 구현을 하더라도 JPA는 매핑이 가능하도록 지원을 해줍니다. 일반적으로 많은 서브타입이 존재하고, 모든 서브타입 데이터에 접근해야 하는 경우 조인 전략을 선택합니다. 서브타입 데이터에 대한 접근이 빠른 것이 중요한 경우 단일 테이블 전략을 선택합니다. 테이블이 작고 효율적인 것이 중요한 경우 테이블 당 클래스 전략을 선택합니다.

각 방법은 장단점을 가지고 있으며, 데이터베이스의 크기와 복잡성, 애플리케이션의 요구사항에 따라 선택되어야 합니다. 가장 적합한 방법을 결정할 때는 데이터베이스 설계 원칙과 성능 최적화를 함께 고려해야 합니다.

728x90
반응형
728x90

 

JPA(Java Persistence API)에서 상속 관계를 매핑하는 것은 객체 지향 프로그래밍에서 자주 발생하는 상속 관계를 데이터베이스로 매핑하는 작업을 의미합니다.
이러한 상속 관계를 매핑하는 과정에서 "고급 매핑(Advanced Mapping)"을 사용할 수 있습니다. 고급 매핑은 다양한 상황에서 발생하는 복잡한 상속 구조를 처리하기 위한 JPA의 기능들을 말합니다.

 

상속관계 매핑

관계형 데이터 베이스는 상속관계가 아닙니다.

슈퍼타입 서브타입 관계라는 모델링 기법은 객체 상속과 유사합니다. 슈퍼타입 서브타입 관계는 데이터베이스에서 공통된 속성들을 슈퍼타입 테이블에 정의하고, 특정 속성들은 서브타입 테이블에 정의하는 기법입니다.

객체 상속은 객체 지향 프로그래밍에서 공통된 속성들을 슈퍼클래스에 정의하고, 특정 속성들은 서브클래스에 정의하는 기법입니다. 두 기법 모두 공통된 속성들을 정의하고, 특정 속성들은 특정 객체에만 정의하는 것이라는 점에서 유사합니다.

상속관계 매핑이라함은 객체의 상속 구조와 DB의 슈퍼타입, 서브타입 관계를 매핑하는 것입니다.

 


 

💡 DB에서의 논리적 모델과 물리적 모델

관계형 데이터베이스에서의 논리적 모델과 물리적 모델은 데이터베이스 설계의 두 가지 중요한 측면을 나타내는 개념입니다.

  • 논리적 모델 논리적 모델은 데이터베이스의 구조와 관계, 테이블의 스키마, 속성들의 정의 등을 나타내는 모델입니다. 이 모델은 데이터베이스 설계자가 비즈니스 요구사항을 분석하고, 데이터베이스의 구조를 추상적으로 설계할 때 사용됩니다. 논리적 모델은 데이터의 논리적 구성을 중심으로 설계되며, 엔터티, 속성, 관계 등의 개념을 포함합니다. 대표적인 논리적 모델로는 E-R 다이어그램(Entity-Relationship Diagram)이 있습니다.

논리적 모델은 데이터베이스 사용자와 관련이 깊은 측면으로, 데이터베이스의 구조를 어떻게 구성할지에 대한 개념적 설계를 의미합니다. 논리적 모델은 데이터베이스의 구조를 결정하고, 어떤 테이블이 어떤 속성을 가지며, 이들 간의 관계를 어떻게 설정할지를 정의합니다.

  • 물리적 모델 물리적 모델은 논리적 모델을 실제 데이터베이스 관리 시스템(DBMS)에 맞게 변환한 모델입니다. 이 모델은 데이터를 저장하고 검색하는 방식, 인덱스 사용 여부, 데이터 타입의 선택 등과 같은 물리적인 세부 사항을 나타냅니다. 물리적 모델은 데이터베이스 시스템의 성능, 확장성, 보안 등과 관련이 깊습니다.

물리적 모델은 실제로 데이터베이스 시스템 내에서 어떻게 데이터가 저장되고 관리되는지를 결정합니다. 이 모델은 인덱스의 생성, 파티셔닝 전략, 테이블 간의 조인 방식 등을 정의하며, 데이터베이스 성능과 최적화를 위한 중요한 요소입니다.

요약하자면, 논리적 모델은 데이터의 구조를 비즈니스 요구사항에 맞게 추상적으로 설계하는 것이고, 물리적 모델은 그 논리적 구조를 실제 DBMS에 맞게 변환하여 데이터의 저장과 관리 방식을 결정하는 것입니다.

 


 

연관관계 매핑 시 주로 사용되는 어노테이션

 

연관관계 매핑에서 주로 사용되는 어노테이션은 JPA(Java Persistence API)의 일부분으로, 객체 간의 관계를 매핑하는 데 사용됩니다. 아래에 언급된 어노테이션들은 주로 상속 관계와 관련된 매핑에서 사용되며, 엔터티 클래스 간의 상속 구조를 정의하고 데이터베이스 테이블 간의 매핑을 지원합니다.

  1. @Inheritance : 상속 관계 매핑을 위한 어노테이션입니다. 부모 엔터티 클래스에 이 어노테이션을 사용하며, 하위 엔터티 클래스들과의 관계를 정의할 때 사용합니다. EX ) @Inheritance(strategy = InheritanceType.JOINED)

  2. @DiscriminatorColumn : 상속 관계 매핑 시, 부모 클래스와 자식 클래스를 식별하는 데 사용할 컬럼을 지정하는 어노테이션입니다. 주로 부모 클래스에 사용되며, 자식 엔터티 클래스의 타입 정보를 저장하는 컬럼을 의미합니다. 연관관계를 맺은 테이블이 어떤 테이블인지 명시해주는 어노테이션입니다.

  3. @DiscriminatorValue : 하위 엔터티 클래스에서 사용되며, 데이터베이스 테이블의 discriminator 컬럼 값으로 사용됩니다. 부모 클래스의 @DiscriminatorColumn과 함께 사용하여 어떤 하위 클래스를 의미하는지 표시합니다.

  4. @MappedSuperclass : 부모 클래스가 테이블과 매핑되지 않고, 자식 클래스에게 매핑 정보만을 제공하기 위해 사용되는 어노테이션입니다. 상속 관계에서 부모 클래스의 공통 매핑 정보를 정의할 때 사용합니다.

 


✍️ 슈퍼타입 서브타입 논리 모델을 실제 물리모델로 구현하는 3가지 방법

 

 

  1. 한 테이블로 구현 (단일 테이블 전략) - @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 이 방법은 슈퍼타입과 서브타입을 하나의 테이블로 구현하는 방식입니다.
    테이블 내에 슈퍼타입과 모든 서브타입의 속성을 함께 저장합니다. 서브타입 특성에 대한 컬럼은 NULL을 허용하며, 슈퍼타입을 나타내는 컬럼을 통해 어떤 서브타입인지 구분합니다.

    단점 : NULL 값을 많이 가지게 되므로 저장 공간의 낭비가 발생할 수 있습니다. 서브타입마다 다른 속성을 가지기 때문에 데이터 중복이 발생할 수 있습니다.

    장점 : 하나의 테이블에 모든 데이터를 저장하여 JOIN 연산을 최소화하므로 조회 성능이 좋을 수 있습니다. 테이블 간의 관계가 없어서 데이터 정합성에 대한 걱정이 줄어듭니다.

 

🎈 단일 테이블 전략으로 관계 매핑 예제

 

 

           
           
           Item Entity 클래스

            @Entity
            public class Item {

                @Id @GeneratedValue
                private Long id;

                private String name; //상품명
                private int price; //상품 가격
            }

            Album, Book, Movie Entity 클래스
            1. @Entity
            public class Book extends Item {

                private String author;
                private String isbn;
            }

            2. @Entity
            public class Album extends Item {
                private String artist;
            }

            3. @Entity
            public class Movie extends Item {

                private String director;
                private String actor;
            }

 

 

 

728x90
반응형
728x90

 

객체 참조는 객체 지향 프로그래밍에서 한 객체가 다른 객체를 참조하거나 사용하는 것을 의미합니다. 객체 참조를 사용하면 객체 간의 관계를 형성하고 데이터 및 기능을 공유할 수 있습니다. 아래 예제를 통해 객체 참조를 사용하는 방법을 살펴보겠습니다.

 

가정: 주문(Order) 객체가 상품(Product) 객체를 참조한다고 가정해봅시다.

            public class Order {
                private Long orderId;
                private List<Product> products; // 상품 목록을 참조하는 필드

                // 생성자, 메서드 등

                public void addProduct(Product product) {
                    products.add(product);
                }

                public List<Product> getProducts() {
                    return products;
                }
            }

            public class Product {
                private Long productId;
                private String name;
                private int price;

                // 생성자, 메서드 등

                public String getName() {
                    return name;
                }

                public int getPrice() {
                    return price;
                }
            }

 

위 코드에서 Order 클래스는 Product 클래스를 참조하고 있습니다. products 필드는 주문 객체가 여러 상품을 참조할 수 있게 합니다. addProduct 메서드를 사용하여 상품을 주문에 추가하고, getProducts 메서드를 사용하여 주문에 포함된 상품 목록을 가져올 수 있습니다.

이제 객체 참조를 활용해 위의 클래스들을 사용하는 예제를 살펴보겠습니다

 

        public class Main {
            public static void main(String[] args) {
                Product product1 = new Product(1L, "사과", 1000);
                Product product2 = new Product(2L, "바나나", 1500);

                Order order = new Order(1L);
                order.addProduct(product1);
                order.addProduct(product2);

                List<Product> productsInOrder = order.getProducts();
                for (Product product : productsInOrder) {
                    System.out.println("상품명: " + product.getName() + ", 가격: " + product.getPrice());
                }
            }
        }

        ====== 출력 =====
        상품명: 사과, 가격: 1000
        상품명: 바나나, 가격: 1500

 

위 예제에서 Order 객체가 Product 객체를 참조하여 상품 목록을 유지하고 출력하는 것을 볼 수 있습니다. 이처럼 객체 참조를 사용하면 객체 간의 관계를 표현하고 상호 작용을 구현할 수 있습니다.

 


 

@OneToMany / @ManyToOne 예제

 

@OneToMany는 JPA(Java Persistence API)에서 사용되는 어노테이션으로, 엔티티 간의 일대다(1:N) 관계를 매핑하기 위해 사용됩니다. 
이 관계는 한 엔티티가 다른 엔티티 여러 개와 관계를 가지는 것을 의미합니다. 예를 들어, 한 회원(Member)이 여러 개의 주문(Order)을 가지는 경우가 일대다 관계입니다.

아래는 @OneToMany 어노테이션을 사용하여 일대다 관계를 매핑하는 예제입니다

        @Entity
        public class Member {
            @Id
            @GeneratedValue
            private Long id;
            private String name;

            @OneToMany(mappedBy = "member") // 회원과의 관계를 매핑하기 위한 필드를 지정
            private List<Order> orders = new ArrayList<>();

            // 생성자, 메서드 등

            // Getter, Setter
        }

        @Entity
        public class Order {
            @Id
            @GeneratedValue
            private Long id;
            private LocalDateTime orderDate;

            @ManyToOne // 다대일 관계 매핑
            @JoinColumn(name = "member_id") // 외래키를 가지는 컬럼 이름
            private Member member; // 주문을 한 회원

            // 생성자, 메서드 등

            // Getter, Setter
        }

 

위 코드에서 Member 엔티티는 orders 필드를 통해 여러 개의 주문(Order)을 가질 수 있습니다. @OneToMany 어노테이션을 사용하여 회원과 주문 간의 관계를 설정합니다. mappedBy 속성은 일대다 관계에서 다 쪽(여기서는 주문)이 관계의 주인임을 나타내며, 매핑을 위한 필드명을 지정합니다.

Order 엔티티는 다대일(@ManyToOne) 관계로 회원과의 관계를 매핑하고, @JoinColumn 어노테이션을 통해 외래 키 컬럼의 이름을 지정합니다.

이렇게 설정하면 Member 엔티티에서 해당 회원과 관련된 주문 목록을 조회할 수 있습니다. 이는 한 회원이 여러 개의 주문을 가지는 일대다 관계를 나타내는 예제입니다.

 

다음은 JPA에서 단방향 연관 관계를 구현하는 예제입니다. 주문(Order) 객체와 회원(Member) 객체 간의 단방향 연관 관계를 가정합니다.

 

        @Entity
        public class Order {
            @Id
            @GeneratedValue
            private Long id;

            @ManyToOne
            @JoinColumn(name = "member_id") // 외래키를 가지는 컬럼 이름
            private Member member; // 주문을 한 회원
            // ...
        }

        @Entity
        public class Member {
            @Id
            @GeneratedValue
            private Long id;

            // 회원 정보
            // ...
        }

 

위 예제에서 @ManyToOne 어노테이션은 다대일 단방향 연관 관계를 표현하며, @JoinColumn 어노테이션을 통해 외래키(Foreign Key) 컬럼의 이름을 지정합니다. 이렇게 설정하면 주문(Order) 객체가 회원(Member) 객체를 참조할 수 있게 됩니다. 회원 객체는 주문을 참조하지 않으므로 이것이 단방향 연관 관계입니다.

이런 단방향 연관 관계를 사용하면 주문 객체에서 주문한 회원 정보를 조회할 수 있습니다.

 

        Order order = entityManager.find(Order.class, orderId);
        Member member = order.getMember(); // 주문을 한 회원 정보를 가져옴

 

단방향 연관 관계는 한쪽 객체에서만 관련된 객체를 참조하므로 객체 간의 의존성을 관리하기 용이하고 간단한 구조를 갖게 됩니다.

728x90
반응형
728x90

JPA에서는 다양한 연관관계를 매핑하는 방법을 제공합니다. 각각의 연관관계는 엔티티 간의 관계를 표현하고 데이터베이스 스키마에 매핑됩니다. 다음은 다양한 연관관계와 그에 따른 매핑 방법에 대한 간략한 설명입니다.

 

연관관계 매핑 시 고려사항 😰

 

연관관계를 매핑할 때 고려해야 하는 주요한 사항은 다음과 같습니다

  1. 다중성(Multiplicity)
    • 다중성은 한 엔티티가 다른 엔티티와의 관계를 얼마나 가질 수 있는지를 나타냅니다.
    • 다중성에는 일대일(1:1), 다대일(N:1), 일대다(1:N), 다대다(N:N)(다대다는 실무에서 절대 사용X) 등이 있습니다.
    • 어떤 엔티티가 다른 엔티티와의 관계를 어떤 방식으로 가질지를 결정해야 합니다.
  2. 단방향 또는 양방향 관계
    • 테이블은 외래 키 하나로 양쪽으로 조인이 가능하기때문에 방향이라는 개념은 없습니다. 그런데 객체는 참조용 필드가 있어야지 필드가 있는 쪽으로 참조가 가능합니다. ex)예를 들어, Member엔티티와 Team엔티티가 존재하면 Member에서 Team으로 이동하고 싶다면 Member안의 Team참조가 있어야지 이동이 가능합니다.
    • 단방향 관계는 한 엔티티가 다른 엔티티를 참조하지만, 그 역은 성립하지 않는 관계입니다.
    • 양방향 관계는 두 엔티티가 서로를 참조하는 관계로, 양쪽으로 조회가 가능합니다.
    • 단방향과 양방향 중 어떤 관계를 선택할지, 양방향 관계일 경우 두 엔티티 중 어떤 엔티티가 연관관계의 주인인지 결정해야 합니다.
  3. 연관관계의 주인(Owner)
    • 양방향 연관관계에서 연관관계의 주인을 정해야 합니다.
    • 연관관계의 주인은 외래키를 관리하는 역할을 합니다.
    • 주인이 아닌 쪽은 읽기만 가능하며, 외래키를 수정할 수 없습니다.
    • 연관관계의 주인데이터베이스 테이블의 외래키 컬럼을 가지고 있는 엔티티입니다.

연관관계를 매핑할 때는 데이터베이스의 스키마와 객체 모델 간의 일관성을 유지하면서 어플리케이션의 요구사항을 충족시키도록 설계해야 합니다. 또한 연관관계의 유형에 따라 필요한 매핑 어노테이션을 사용하여 매핑을 정확하게 설정해야 합니다.

 


 

✍️ 다대일(N:1) 관계

다대일(N:1) 관계여러 개의 엔티티가 하나의 엔티티를 참조하는 관계를 말합니다. 가장 많이 사용되는 연관관계입니다. 이러한 관계는 주로 부모-자식 관계를 표현할 때 사용되며, 다른 말로 "다(N) 쪽이 연관관계의 주인"이라고도 표현합니다.

		@Entity
		public class Member {
		    @Id
		    @GeneratedValue
		    private Long id;
		
		    private String username;
		
		    // 다대일(N:1) 관계 설정
		    **@ManyToOne //Member가 N , Team은 1 (1개의 팀에 여러 member가 소속)**
		    **@JoinColumn(name = "team_id") //어떤 컬럼과 조인할 지 (Team과 Member 테이블의 TEAM_ID(FK)를 매핑)**
		    private Team team;
		
		    // Getter, Setter, Constructors...
		}
		
		@Entity
		public class Team {
		    @Id
		    @GeneratedValue
		    private Long id;
		
		    private String name;
		
		    // Getter, Setter, Constructors...
		}

위의 코드에서 @ManyToOne 어노테이션은 다대일(N:1) 관계를 설정하기 위해 사용되었습니다. @JoinColumn 어노테이션은 외래키 컬럼을 지정하는데 사용되며, 이 컬럼은 Member 테이블에 있는 team_id 컬럼과 연결됩니다.

 

✅ 다대일(N:1) 관계의 특징

여러 개의 다(N) 엔티티가 하나의 일(1) 엔티티를 참조합니다. 다(N) 쪽 엔티티에 외래키가 저장됩니다. JPA에서 다(N) 쪽 엔티티에 @ManyToOne 어노테이션을 사용하여 매핑합니다. 주로 부모-자식 관계를 표현하는데 사용됩니다.
이러한 다대일(N:1) 관계를 활용하면 부모-자식 관계를 효과적으로 모델링하고, 관련된 데이터를 쉽게 조회할 수 있습니다.

 

 

✍️ 일대다(1:N) 관계

일대다(1:N) 관계한 엔티티가 여러 개의 다른 엔티티를 참조하는 관계입니다. 이 관계는 "하나의 엔티티가 여러 개의 다른 엔티티를 가질 수 있다"는 개념을 나타냅니다. 대표적인 예시로 한 회원이 여러 개의 주문을 가질 수 있는 경우를 들 수 있습니다.

 

 

✍️ 일대일(1:1) 관계

일대일(1:1) 관계는 두 개의 엔티티 사이의 관계 중 하나의 엔티티가 다른 엔티티 하나와 1:1로 관계를 가지는 것을 말합니다. 이러한 관계는 주로 두 엔티티 사이에 강한 관계가 있을 때 사용됩니다. 예를 들어, 회원(Member)과 회원의 프로필(Profile) 엔티티가 1:1 관계를 가질 수 있습니다.

일대일(1:1) 관계의 주요 특징은 다음과 같습니다:

  1. 하나의 엔티티가 다른 엔티티와 1:1 관계를 가짐
    • 일대일 관계에서는 한 엔티티가 다른 엔티티와 1:1 관계를 가지게 됩니다. 이는 두 엔티티 사이에 강한 관계를 나타내는 경우에 사용됩니다. 외래 키에 데이터 베이스 유니크(UNI) 제약조건을 추가하여 일대일 매핑이 가능합니다.
  2. 두 엔티티 중 하나만 외래키를 가짐:
    • 일대일 관계에서는 두 엔티티 중 하나만 외래키를 가지며, 다른 엔티티는 mappedBy 속성을 사용하여 매핑합니다. 이로써 한 쪽 엔티티의 외래키가 다른 쪽 엔티티의 주요 식별자가 되는 구조를 가집니다. 주 테이블이나 대상 테이블 중에 외래 키 선택이 가능합니다.
  3. 강한 관계를 표현:
    • 일대일 관계는 두 엔티티 사이에 강한 관계를 나타내는데 사용됩니다. 예를 들어, 회원과 회원의 프로필, 주문과 주문의 배송 정보 등이 일대일 관계로 표현될 수 있습니다.
  4. 일반적으로 양방향 관계로 설정됨:
    • 일대일 관계는 양방향으로 설정하는 것이 일반적입니다. 이때 연관관계의 주인은 한 쪽 엔티티가 됩니다. 두 엔티티 사이의 양방향 관계를 설정하면 두 엔티티 모두 상호 참조할 수 있게 됩니다.
  5. 대칭성 없음:
    • 일대일 관계는 보통 한 쪽 엔티티가 다른 쪽 엔티티를 소유하는 경우가 많습니다. 이러한 구조로 인해 대칭성이 없으며, 두 엔티티 간의 역방향 조회는 불가능합니다.

일대일 관계는 주로 강한 관계를 표현하는데 사용되며, 두 엔티티 사이에 어떤 엔티티가 주인이 되고 어떤 엔티티가 mappedBy를 사용하여 매핑할지 결정하는 것이 중요합니다.

다대일 단방향 매핑과 유사하며, 다대일 양팡향 매핑처럼 외래 키가 있는 곳이 연관관계의 주인이 됩니다.

728x90
반응형
728x90

 

 양방향 연관관계 (외래키가 있는 테이블이 연관관계의 주인)

 

양방향 연관관계(객체의 연관관계 , 테이블의 연관관계)는 ****두 개의 객체 간의 상호 참조를 나타내며, 한 객체에서 다른 객체를 참조하는 것뿐만 아니라 그 반대 방향의 참조도 가능한 연관관계를 의미합니다. 이는 객체 지향 모델링의 관점에서 보다 더 현실적이고 완전한 관계를 표현하는 방식입니다.

객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개입니다. 객체를 양방향 관계를 참조하기 위해서는 단방향 연관관계를 2개를 생성해야하기 때문입니다.

 

                EX ) 
                A -> B (a.getB())

                class A {
                        B b;
                }

                ======= A <=> B의 양방향 관계를 위해서는 이렇게 두개의 방향이 필요합니다 =======

                B -> A(b.getA())

                class B {
                        A a;
                }

 

테이블의 양방향 연관관계하나의 외래 키로 데이터베이스 테이블 간에 서로를 참조하는 관계를 의미합니다. 객체 지향 프로그래밍에서의 양방향 연관관계와 유사한 개념으로, 데이터베이스 테이블 간의 상호 관계를 나타내는 것입니다.

일반적으로 데이터베이스 테이블 간의 연관관계는 외래 키(Foreign Key)를 통해 구현됩니다. 양방향 연관관계에서는 두 테이블이 서로를 참조하므로, 각 테이블이 다른 테이블의 외래 키를 가지고 있게 됩니다.

예를 들어, 팀(Team)과 팀원(Member)라는 두 개의 엔티티가 있다고 가정해보겠습니다. 양방향 연관관계에서는 팀이 팀원을 참조하는 것뿐만 아니라 작성자 역시 팀을 참조할 수 있게 됩니다.
TEAM_ID(FK) 해당 외래 키로 TEAM의 PK값과 조인하면 해당 MEMBER의 소속 팀을 알 수 있고, MEMBER의 PK값과 조인하면 TEAM에 소속되어 있는 MEMBER 데이터를 알 수 있습니다.

 

 

둘 중 하나로 외래키를 관리해야 합니다. 객체를 살펴보면 참조가 MEMBER → TEAM / TEAM에서 MEMBERS로 가는 2개의 참조값이 생겼는데,
이 때, 어떤 값과 매핑을 해야하는 지를 정하는 것이 중요한 사항입니다. 데이터의 변경이 필요할 때, (예를 들면 새로운 팀으로 or 회원 가입, 탈퇴 등) 어떤 객체를 참조해서 변경을 해야하는 지가 어렵습니다.

이를 정하기 위해 양방향 매핑 규칙이 존재하는데, 아래와 같습니다.

  1. 객체 간의 참조 설정
    • 각 엔티티 클래스에서 상대방 엔티티를 참조하는 필드를 추가합니다.
    • 이 필드는 연관된 엔티티를 참조하는 역할을 합니다.
  2. @ManyToOne, @OneToMany 어노테이션 사용
    • 양방향 연관관계의 주인 쪽에 @ManyToOne 어노테이션을 사용하여 다대일 관계를 매핑합니다.
    • 연관관계의 반대편에는 @OneToMany 어노테이션을 사용하여 일대다 관계를 매핑합니다.
    • @ManyToOne 어노테이션의 mappedBy 속성에는 연관관계의 주인 엔티티 필드명을 지정합니다.
  3. Cascade 설정
    • 양방향 연관관계에서는 연관된 엔티티의 상태 변경을 한 곳에서 관리할 수 있도록 cascade 옵션을 설정하는 것이 좋습니다.
    • cascade 옵션을 사용하면 연관된 엔티티의 저장, 수정, 삭제 등의 변경 작업을 한 곳에서 처리할 수 있습니다.
  4. Fetch 설정
    • 양방향 연관관계에서는 엔티티를 조회할 때 지연 로딩(LAZY)을 사용하는 것이 일반적입니다.
    • 필요한 경우 특정 상황에서 즉시 로딩(EAGER)을 사용할 수도 있습니다.
  5. 무한루프 방지를 위한 equals와 hashCode 오버라이딩
    • 양방향 연관관계에서는 객체가 서로를 무한히 참조하는 상황을 방지하기 위해 equals와 hashCode메서드를 적절하게 오버라이딩해야 합니다.
  6. 연관관계의 주인 설정
    • 연관관계의 주인은 외래 키 관리를 담당하는 엔티티 쪽입니다.
    • 주인 엔티티의 참조 필드에 @JoinColumn 어노테이션을 사용하여 외래 키를 설정합니다.
  7. 반대편 엔티티 필드에mappedBy 설정
    • 양방향 연관관계에서 주인이 아닌 엔티티 쪽에서는 mappedBy 속성을 사용하여 연관관계의 주인 엔티티 필드를 지정합니다.
    • 이를 통해 데이터베이스 테이블 간의 외래 키 관계를 설정합니다.
  8. 주인 엔티티에 연관관계 설정 및 관리 메서드 추가
    • 주인 엔티티에 양방향 연관관계를 설정하고 관리하는 메서드를 추가하여 두 엔티티 간의 연관관계를 효과적으로 관리할 수 있습니다.

 


 

✍️ 연관관계에서 주인(Owner)

 

연관관계에서 주인(Owner)이란, 두 엔티티 중 하나에서 외래 키를 관리하고 데이터베이스 연산을 수행하는 역할을 말합니다. 주인 엔티티에서 외래 키를 설정하고 관리하면, 이에 따라 데이터베이스에 적절한 SQL이 생성되어 관련 데이터가 올바르게 저장되고 관리됩니다.

보통 외래 키가 있는곳 주인 엔티티가 외래 키를 관리함으로써 두 엔티티 간의 관계를 유지하고 일관성을 유지할 수 있습니다. 주인이 아닌 쪽은 읽기만 가능하며 , 주인이 아니라면 mappedBy 속성으로 주인을 지정해야 합니다.

외래 키가 있는 곳이 보통 주인이 되며, 1 : N 중에 N쪽이 연관관계의 주인이 됩니다. @ManyToOne 등의 어노테이션이 작성되어야 합니다. DB 테이블의 N쪽이 연관관계의 주인이 되면 프로젝트 구성 시 원할한 실행이 가능합니다.

예를 들어, 팀(Team) 엔티티와 팀원(Member) 엔티티 간의 양방향 연관관계를 가정해보겠습니다. 이 때 팀원이 팀원을 참조하는 @ManyToOne 관계가 있다면, 이 관계에서 팀 엔티티가 주인 역할을 할 수 있습니다. 따라서 팀 엔티티의 외래 키 설정과 관리를 담당하게 됩니다.

💡 Team과 Member ⇒ @OneToMany (Team 기준)

 

        
        
        	============== Team 클래스 필드 ===================

            @OneToMany(mappedBy = "team") //mappedBy : 일대다 매핑에서 어던 객체와 연결되어 있는지 (반대편 방향을 의미) 알려주는 어노테이션
            //team으로 매핑이 되어있음을 의미
            private List<Member> members = new ArrayList<>();			



            ============== main 메서드 ===================

                    //OneToMany 연관 관계 매핑 후 Team 저장 코드
                    Team team = new Team();
                    team.setName("TeamA");
                    em.persist(team);

                    //OneToMany 연관 관계 매핑 후 Team 저장 코드
                    Member member = new Member();
                    member.setUsername("회원 1");
                    member.setTeam(team);
                    em.persist(member);

                    em.flush();
                    em.clear();

                    Member findMember = em.find(Member.class, member.getId());

                    List<Member> members = findMember.getTeam().getMembers();

                    for (Member m : members) {

                        System.out.println("m = " + m.getUsername());
                    }

                    tx.commit();

            ============== 쿼리문 출력 결과=======================

                            Hibernate: 
                                select
                                    m1_0.MEMBER_ID,
                                    t1_0.TEAM_id,
                                    t1_0.name,
                                    m1_0.USERNAME 
                                from
                                    Member m1_0 
                                left join
                                    Team t1_0 
                                        on t1_0.TEAM_id=m1_0.TEAM_ID 
                                where
                                    m1_0.MEMBER_ID=?

                            Hibernate: 
                                select
                                    m1_0.TEAM_ID,
                                    m1_0.MEMBER_ID,
                                    m1_0.USERNAME 
                                from
                                    Member m1_0 
                                where
                                    m1_0.TEAM_ID=?

                            m = 회원 1

 

👉 em.flush()

영속성 컨텍스트에 쌓여 있는 변경 내용(예: 엔티티의 추가, 수정, 삭제)을 데이터베이스에 즉시 동기화합니다. 이 때, 데이터베이스에 대한 쿼리가 실행되어 변경 내용이 반영됩니다.
flush()를 호출하면 데이터베이스와의 실제 동기화가 이루어지며, 트랜잭션이 커밋되기 전에 실행됩니다.

👉 em.clear();:

영속성 컨텍스트를 초기화합니다. 영속성 컨텍스트에 캐시된 엔티티들은 분리(detached) 상태가 되며, 영속성 컨텍스트의 상태가 초기화됩니다.
이는 영속성 컨텍스트의 메모리 누수를 방지하고, 잠재적으로 영속성 컨텍스트에 쌓인 엔티티들을 비워주는 역할을 합니다. 보통은 트랜잭션이 종료될 때나 큰 작업이 끝날 때 호출하여 사용합니다.

 


 

💥 양방향 매핑 시 가장 많이 하는 실수

 

양방향 매핑 시 무한루프

 

양방향 연관관계에서 무한 루프가 발생하는 문제는 주로 객체 그래프를 JSON으로 변환하거나 로깅 등에서 발생하는 문제입니다. 이는 객체가 서로를 계속 참조하면서 상호 참조에 의해 무한한 루프가 형성되기 때문입니다.

예를 들어, Parent 엔티티와 Child 엔티티가 양방향으로 연관되어 있다고 가정해보겠습니다. 그리고 양쪽 엔티티에는 서로를 참조하는 필드가 있습니다.

                @Entity
                public class Parent {

                    @OneToMany(mappedBy = "parent")
                    private List<Child> children;
                }

                @Entity
                public class Child {

                    @ManyToOne
                    private Parent parent;
                }

 

이 상태에서 Parent 엔티티를 JSON으로 변환하려고 하면, Parent 엔티티 안의 children 필드가 Child 엔티티를 가리키고, Child 엔티티 안의 parent 필드가 다시 Parent 엔티티를 가리키게 되어
서로가 계속 참조하는 상황이 발생합니다. 이렇게 서로가 계속 참조하는 상황에서 JSON 변환을 시도하면 무한한 깊이로 객체가 변환되기 때문에 스택 오버플로우나 성능 문제 등이 발생할 수 있습니다.

이를 해결하기 위해서는 다음과 같은 방법들을 사용할 수 있습니다.

@JsonIgnore 어노테이션 사용: Jackson 라이브러리를 사용해 JSON 변환 시 무한 루프가 발생하는 것을 방지할 수 있습니다. @JsonIgnore 어노테이션을 특정 필드에 사용하여 해당 필드가 JSON 변환에서 제외되도록 설정합니다. 하지만 이 방법은 필드 자체가 무시되므로 정보 손실이 발생할 수 있습니다.

 

                @Entity
                public class Parent {
                    // ...

                    @JsonIgnore
                    @OneToMany(mappedBy = "parent")
                    private List<Child> children;
                }

 

  1. DTO 사용: 엔티티를 그대로 반환하는 대신, DTO(Data Transfer Object)를 사용하여 필요한 정보만 전달하도록 합니다. DTO를 사용하면 필요한 정보를 선택적으로 전달할 수 있고, 무한 루프 문제도 방지할 수 있습니다.

  2. @JsonManagedReference와 @JsonBackReference 사용 : 이 어노테이션을 사용하면 순환 참조를 해결할 수 있습니다. @JsonManagedReference는 직렬화 시 참조 엔티티를 처리하고, @JsonBackReference는 역방향 연관 엔티티를 처리합니다.

 

            @Entity
            public class Parent {
                // ...

                @JsonManagedReference
                @OneToMany(mappedBy = "parent")
                private List<Child> children;
            }

            @Entity
            public class Child {
                // ...

                @JsonBackReference
                @ManyToOne
                private Parent parent;
            }

 

 

728x90
반응형
728x90

 

 

 

연관 관계 매핑이란 ❓

연관 관계 매핑객체 간의 관계를 데이터베이스의 관계로 어떻게 매핑할지를 정의하는 것입니다. 객체 지향 프로그래밍에서는 객체 간의 관계를 사용하여 현실 세계를 모델링하고, 이를 데이터베이스에 저장하려면 효과적인 방법이 필요합니다. JPA(Java Persistence API)는 이러한 연관 관계 매핑을 지원하며, 이를 통해 객체 모델과 데이터베이스 스키마를 매핑할 수 있습니다.

객체와 테이블 연관관계의 차이를 이해하고 객체의 참조와 테이블의 외래키를 매핑할 수 있습니다.

✍️ 연관 관계 매핑에서 사용되는 주요 용어

  1. 방향(Direction)
    • 연관 관계가 양방향인지 단방향인지를 나타냅니다.
    • 양방향 연관 관계는 서로를 참조하는 관계로, 한 객체가 다른 객체를 참조하고 동시에 다른 객체도 그 객체를 참조하는 관계를 말합니다. (예: 주문과 회원 간의 관계)
    • 단방향 연관 관계는 한 객체에서만 다른 객체를 참조하는 관계로, 한 쪽 방향으로만 참조가 가능합니다. (예: 주문과 배송 간의 관계)
  2. 다중성(Multiplicity)
    • 객체 간의 관계가 1:1, 1:N, N:1, N:N 등 얼마나 많은 객체가 연결되어 있는지를 나타냅니다.
    • 1:1은 한 객체가 다른 객체와 하나의 관계만 가지는 경우를 말합니다.
    • 1:N은 한 객체가 여러 개의 다른 객체와 관계를 가지는 경우를 말합니다.
    • N:1은 여러 개의 객체가 하나의 객체와 관계를 가지는 경우를 말합니다.
    • N:N은 여러 개의 객체가 여러 개의 다른 객체와 관계를 가지는 경우를 말합니다. (예: 주문과 상품 간의 관계)
  3. 연관 관계의 주인(Owner)
    • 양방향 연관 관계에서 관계를 주도하는 쪽을 연관 관계의 주인이라고 합니다.
    • 연관 관계의 주인은 데이터베이스에서 외래 키(Foreign Key)를 관리하며, 데이터베이스에 데이터를 저장하거나 수정할 때 주인 쪽의 변화가 반영됩니다.
    • 주인이 아닌 쪽은 읽기만 가능하고 데이터베이스에 영향을 주지 않습니다.

주의해야 할 점은 연관 관계의 주인은 데이터베이스의 관점에서 중요한 역할을 하며, 객체지향의 관점에서 중요하지 않을 수 있습니다. 때문에 연관 관계의 주인을 선택할 때는 데이터베이스의 특성과 운영을 고려하여 결정하는 것이 좋습니다.

이러한 방향, 다중성, 연관 관계의 주인 개념은 객체 간의 관계를 데이터베이스에 매핑할 때 혼란을 방지하고 일관된 관계를 구성하기 위해 중요한 역할을 합니다.

 

✅ 예제 시나리오

회원과 팀으로 구분지어 연관관계를 지어보도록 하겠습니다.

⭐ 회원은 하나의 팀에만 소속될 수 있습니다.
⭐ 회원과 팀은 다대일 (N:1)의 관계로 맺어집니다.

객체를 테이블에 맞춰 모델링을 한 경우 아래와 같은 ER 다이어그램이 그려집니다.

 

💥 테이블에 객체를 맞춘 데이터 중심 모델링

 

 
       public static void main(String[] args) {

                EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

                EntityManager em = emf.createEntityManager();
                EntityTransaction tx = em.getTransaction();
                tx.begin();

                //외래 키 식별자를 직접 다루는 코드
                try {

                    //Team 저장 코드
                    Team team = new Team();
                    team.setName("TeamA");
                    em.persist(team);

                    //Member 저장코드
                    Member member = new Member();
                    member.setUsername("회원 1");
                    member.setTeamId(team.getId()); //회원 1을 TeamA에 소속시키고자 함
                    em.persist(member);


                    tx.commit();

                } catch (Exception e) {
                    e.printStackTrace();//추가
                    tx.rollback();
                }finally {
                    em.close();
                }

                emf.close();

                }

 

객체를 테이블에 맞추어 데이터 중심으로 모델링하면 협력 관계를 만들 수 엇습니다. 테이블은 외래 키로 조인을 사용해서 연관 테이블을 찾는 반면, 객체는 참조를 사용해서 연관된 객체를 찾는 큰 차이점이 존재합니다.

객체지향 프로그래밍에서는 객체 간의 관계가 더 복잡한 경우가 많습니다. 이를 데이터베이스에 매핑할 때에는 단순한 테이블 간의 관계만으로 표현하기 어려울 수 있습니다. 단방향 / 양방향 연관관계는 이러한 객체지향 모델을 데이터베이스 모델로 효과적으로 매핑하기 위해 사용됩니다.

 

단방향 연관관계

Team 객체를 Getter나 Setter로 호출하여 사용하는 것이 아닌 Team Entity 그 자체를 Member Entity와 연관 관계를 주어 사용하고자 합니다. JPA에게 관계를 지정해주어야 하며, 일 대 일 or 일 대 다인지가 중요한 부분입니다. Member의 관점에서는 Member가 N , Team이 1의 입장이기 때문에 @ManyToOne 어노테이션이 Member 테이블에 선언된 Team 클래스에 작성해야 합니다.

단방향 연관관계두 개의 객체 간의 관계를 나타내는데, 한 객체가 다른 객체를 참조하는 것입니다. 즉, 한 객체에서 다른 객체로의 관계를 설정하고 사용하는 것이지만, 반대 방향의 참조는 없는 상태를 의미합니다.

예를 들어, 팀(Team)과 팀원(Member)라는 두 개의 엔티티가 있다고 가정해보겠습니다. 여기서 팀원은 팀원을 참조하므로, 팀원 엔티티에 팀원의 정보를 저장하는 멤버 변수를 추가합니다. 이 때 팀원의 정보를 저장하기 위해 외래 키를 사용할 수 있습니다.

단방향 연관관계의 특징은 다음과 같습니다:

  1. 한 객체에서 다른 객체로의 참조만 존재하며, 그 반대는 성립하지 않습니다. 예를 들어, 팀에서 팀원을 참조할 수 있지만 작성자 객체에서 해당 작성글을 바로 참조하는 것은 어렵습니다.

  2. 객체의 참조를 통해 다른 객체를 조회하거나 조작할 수 있습니다. 위의 예에서 팀 객체에서 팀원을 조회하거나 수정할 수 있습니다.

  3. 데이터베이스 테이블 간의 관계로는 해당 연관관계를 한쪽 방향으로만 설정하면 됩니다. 즉, 팀의 테이블에 팀원 정보를 저장하는 컬럼을 추가하면 됩니다.

단방향 연관관계는 간단하고 명확한 구조를 가지고 있어 일반적인 상황에서 유용하게 활용됩니다. 하지만 객체지향 모델링의 관점에서는 한쪽 방향으로만 접근할 수 있는 구조이므로, 양방향 연관관계가 필요한 경우도 있을 수 있습니다.

 

💡 Team과 Member ⇒ @ManyToOne (member기준)

 

                public static void main(String[] args) {

                        생략..

                        //외래 키 식별자를 직접 다루는 코드
                        try {

                            //연관 관계 매핑 후 Team 저장 코드
                            Team team = new Team();
                            team.setName("TeamA");
                            em.persist(team);

                            //연관 관계 매핑 후 Member 저장코드
                            Member member = new Member();
                            member.setUsername("회원 1");
                            member.setTeam(team);
                            em.persist(member);

                            Member findMember = em.find(Member.class, member.getId());
                            Team findTeam = findMember.getTeam();

                            System.out.println("findTeam.getName = " + findTeam.getName());

                        생략..


                ============== 쿼리문 출력 결과=======================
                Hibernate: 
                    select
                        m1_0.MEMBER_ID,
                        t1_0.TEAM_id,
                        t1_0.name,
                        m1_0.USERNAME 
                    from
                        Member m1_0 
                    left join
                        Team t1_0 
                            on t1_0.TEAM_id=m1_0.TEAM_ID 
                    where
                        m1_0.MEMBER_ID=?

                System.out.println("findTeam.getName = " + findTeam.getName());
                findTeam.getName = TeamA
728x90
반응형

'[ BACKEND] > Spring' 카테고리의 다른 글

[SPRING] 다양한 연관관계의 종류  (0) 2023.08.11
[SPRING] 양방향 연관관계  (0) 2023.08.09
[SPRING] Entity Mapping (엔티티 매핑)  (0) 2023.08.07
[SPRING] Entity와 영속성  (0) 2023.08.06
[SPRING] ORM 그리고 JPA  (0) 2023.08.05

+ Recent posts