본문 바로가기
🚀 부트캠프 - PLAYDATA/📒 수업 내용 정리

[Springboot]9/6 연관 관계 매핑

by minhe2810 2023. 9. 6.

연관관계 매핑 

- 관계형 데이터 베이스일 경우에만 가능함. 

- 오라클, MySQL

- 신입사원은 일대일, 일대다 정보를 익히는 게 좋음. 

 

데이터베이스에서는 두 테이블의 연관관계를 설정하면 외래키를 통해 서로 조인해서 참조하는 구조로 생성되지만, 

JPA를 사용하는 객체지향모델링에서는 엔티티 간 참조 방향을 설정 가능

 

일반적으로 외래키를 가진 테이블이 그 관계의 주인이 됨.

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) 
@Builder
@Table(name = "product")
public class Product extends BaseTimeEntity {      
                          

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column(name = "product_number")
    private Long number;        
    
    @Column(nullable = false)      
    private String name;

    @Column(nullable = false)       
    private Integer price;

    @Column(nullable = false)           
    private Integer stock;

    @OneToOne(mappedBy = "product")
    @ToString.Exclude
    private ProductDetail productDetail;

    @ManyToOne
    @JoinColumn(name = "provider_id")
    @ToString.Exclude
    private Provider provider;

}
@Entity
@Table(name = "product_detail")
@Getter
@Setter
@ToString(callSuper = true) // BaseEntity에 선언한 것 까지 모두 적용하기 위해
@EqualsAndHashCode(callSuper = true)
public class ProductDetail extends BaseEntity{

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

    private String description;

    @OneToOne
    @JoinColumn(name = "product_number")   // 외래키 이름 데이터 베이스 내에서 PK_라는 상수가 만들어짐. 
    private Product product;			   // 그와같은 이름을 만들어놓은 것임.
}

 

연관관계 매핑 종류와 방향


1. 종류

 

    1. One To One : 일대일 (1:1)
        @One To One, @JoinColumn
        단방향 : 한쪽만 @One To One
        양방향 : @One To One 단, Owner를 지정 -> @One To One(mappedBy= "연관관계 필드명")

@OneToOne(mappedBy = "product")
@ToString.Exclude
private ProductDetail productDetail;


    2. One To Many : 일대다(1:N) : 공급업체(한개) -> 한 가게에 납품하는 상품(여러개)
    @One To One,  @JoinColumn
    단방향만

    3. Many To One : 다대일 (N:1) : 상품 여러개 -> 공급업체 한개
    @Many To One,  @JoinColumn
    단방향 / 양방향

    4. Many To Many : 다대다 (N:N) : 일반 실무에서는 많이 사용되지 않는 개념
    @Many To Many
    한 종류의 상품이 여러 생산 업체를 통해 생산될 수 있고,
    생산 업체 한 곳이 여러상품을 생상할 수도 있다.
    각 엔티티에서 서로를 List로 가지는 구조
    -> 교차 엔티티를 생성해서 N:N, 1:N, N:1 관계로 해소
    별도의 @JoinColumn을 설정하지 않는다.

    @One To One : left outer join(optional=true) / inner join(optional=false)
    교집합으로 조인하고 싶으면 optional값을 true로 변경

     @JoinColumn(name="컬럼명")
        name : 매핑할 외래 키 이름
        referencedColumnName : 외래키가 참조할 상대 테이블의 컬럼명을 지정.
        foreignKey : 외래키를 생성하면서 지정할 제약조건을 설정.
            unique, nullable, insertable, updatable

     # lombok  사용시 ... #
     @ToString(callSuper=true)
        callSuper : 클래스가 다른 클래스를 상속하고 있을 때.
                    부모 클래스의 toString() 출력을 포함하려는 경우, true
        exclude : 제외해야하는 필드명을 지정
        includeFieldNames : 기본값 True (생략가능), ex) [필드명 : ~~~] 라고 출력되는데 이걸 값만 뽑고 싶을 때 설정해주는 것
                           필드명 포함여부, 필드명을 제외시킬 경우 false

     방향
        단방향 : 두 엔티티의 관계에서 한쪽의 엔티티만 참조하는 형식
        양방향 : 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조하는 형식

        데이터베이스와 일치시키기 위해 양방향으로 설정해도 무관하지만.
        비즈니스 로직의 관점에서 보았을 때, 단방향 관계만 설정해도 대부분 해결된다.

     * 중요한 점 :
     1. 어떤 엔티티를 중심으로 연관 엔티티를 보느냐에 따라 연관관계 상태가 달라진다.
     2. 연관관계가 설정되면 한 테이블에서 다른 테이블의 기본값(primary Key)을 외래 키(Foreign Key)로 갖게 된다.
     3. 일반적으로 외래키를 가진 테이블이 그 관계의 주인(Owner)이 되고, Owner는 Foreign key를 사용할 수 있으나,
        상대 엔티티는 읽는 작업만 수행할 수 있다.
     4. ToString을 사용할 때 순환참조가 발생 : StackOverFlowError발생
     => @ToString.Exclude : ToString에서 제외.

     * 지연로딩(fetch="Lazy")과 즉시로딩(fetch="FatchType.EAGER") : JPA에서만...
     @OneToOne, @OneToMany, @ManyToOne, @ManyToMany의 속성 중
     fetch="Lazy" 로 기본 설정 되어있음.

     JPA에서 지연로딩(Lazy Loading)과 자연로딩(Eager Loading)은 중요한 개념
     1. 엔티티라는 객체의 개념으로 데이터 베이스를 구현했기 때문에
        연관관계를 가진 각 엔티티 클래스에서 연관관계가 있는 객체들이 필드에 존재하게 된다.

     2. 연관관계와 상관 없이 즉각 해당 엔티티의 값만 조회하고 싶거나,
        연관관계를 가진 테이블의 기본값도 조회하고 싶을 경우 등...
        여러가지 조건을 만족하기 위해 등장한 개념

 

연관관계 매핑 종류와 방향

종류
    1. One To One : 일대일 (1:1)
        @One To One, @JoinColumn
        단방향 : 한쪽만 @One To One
        양방향 : @One To One 단, Owner를 지정 -> @One To One(mappedBy= "연관관계 필드명")

    2. One To Many : 일대다(1:N) : 공급업체(한개) -> 한 가게에 납품하는 상품(여러개)
    @One To One,  @JoinColumn
    단방향만

    3. Many To One : 다대일 (N:1) : 상품 여러개 -> 공급업체 한개
    @Many To One,  @JoinColumn
    단방향 / 양방향

    4. Many To Many : 다대다 (N:N) : 일반 실무에서는 많이 사용되지 않는 개념
    @Many To Many
    한 종류의 상품이 여러 생산 업체를 통해 생산될 수 있고,
    생산 업체 한 곳이 여러상품을 생상할 수도 있다.
    각 엔티티에서 서로를 List로 가지는 구조
    -> 교차 엔티티를 생성해서 N:N, 1:N, N:1 관계로 해소
    별도의 @JoinColumn을 설정하지 않는다.

    @One To One : left outer join(optional=false) / inner outer join(optional=false)
    교집합으로 조인하고 싶으면 optional값을 true로 변경

     @JoinColumn(name="컬럼명")
        name : 매핑할 외래 키 이름
        referencedColumnName : 외래키가 참조할 상대 테이블의 컬럼명을 지정.
        foreignKey : 외래키를 생성하면서 지정할 제약조건을 설정.
            unique, nullable, insertable, updatable

     # lombok  사용시 ... #
     @ToString(callSuper=true)
        callSuper : 클래스가 다른 클래스를 상속하고 있을 때.
                    부모 클래스의 toString() 출력을 포함하려는 경우, true
        exclude : 제외해야하는 필드명을 지정
        includeFieldNames : 기본값 True (생략가능), ex) [필드명 : ~~~] 라고 출력되는데 이걸 값만 뽑고 싶을 때 설정해주는 것
                           필드명 포함여부, 필드명을 제외시킬 경우 false

     방향
        단방향 : 두 엔티티의 관계에서 한쪽의 엔티티만 참조하는 형식
        양방향 : 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조하는 형식

        데이터베이스와 일치시키기 위해 양방향으로 설정해도 무관하지만.
        비즈니스 로직의 관점에서 보았을 때, 단방향 관계만 설정해도 대부분 해결된다.

     * 중요한 점 :
     1. 어떤 엔티티를 중심으로 연관 엔티티를 보느냐에 따라 연관관계 상태가 달라진다.
     2. 연관관계가 설정되면 한 테이블에서 다른 테이블의 기본값(primary Key)을 외래 키(Foreign Key)로 갖게 된다.
     3. 일반적으로 외래키를 가진 테이블이 그 관계의 주인(Owner)이 되고, Owner는 Foreign key를 사용할 수 있으나,
        상대 엔티티는 읽는 작업만 수행할 수 있다.
     4. ToString을 사용할 때 순환참조가 발생 : StackOverFlowError발생
     => @ToString.Exclude : ToString에서 제외.

     * 지연로딩(fetch="Lazy")과 즉시로딩(fetch="FatchType.EAGER") : JPA에서만...
     @OneToOne, @OneToMany, @ManyToOne, @ManyToMany의 속성 중
     fetch="Lazy" 로 기본 설정 되어있음.

     JPA에서 지연로딩(Lazy Loading)과 자연로딩(Eager Loading)은 중요한 개념
     1. 엔티티라는 객체의 개념으로 데이터 베이스를 구현했기 때문에
        연관관계를 가진 각 엔티티 클래스에서 연관관계가 있는 객체들이 필드에 존재하게 된다.

     2. 연관관계와 상관 없이 즉각 해당 엔티티의 값만 조회하고 싶거나,
        연관관계를 가진 테이블의 기본값도 조회하고 싶을 경우 등...
        여러가지 조건을 만족하기 위해 등장한 개념

 

실습 전 바꿔줘야 할 사항 

create 로 변경 

 

매핑하는 코드 여기까지 (클래스 선언부까지가 매핑하는 것)

 

실제로 사용하는 코드 => 256페이지 

 

Hibernate: 
    
    alter table product_detail 
       drop 
       foreign key FKdp0jtp38vrl8mxrqwge69bjyh
Hibernate: 
    
    drop table if exists product
Hibernate: 
    
    drop table if exists product_detail
Hibernate: 
    
    create table product (
       number bigint not null auto_increment,
        created_at datetime(6),
        updated_at datetime(6),
        name varchar(255) not null,
        price integer not null,
        stock integer not null,
        primary key (number)
    ) engine=InnoDB
Hibernate: 
    
    create table product_detail (
       id bigint not null auto_increment,
        created_by varchar(255),
        updated_by varchar(255),
        description varchar(255),
        product_number bigint,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    
    alter table product_detail 
       add constraint FKdp0jtp38vrl8mxrqwge69bjyh 
       foreign key (product_number) 
       references product (number)
[2023-09-06 10:58:37.405] [INFO ] [main] org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
[2023-09-06 10:58:37.421] [INFO ] [main] org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean Initialized JPA EntityManagerFactory for persistence unit 'default'
[2023-09-06 10:58:39.280] [WARN ] [main] org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration$JpaWebConfiguration spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
[2023-09-06 10:58:40.092] [INFO ] [main] springfox.documentation.spring.web.PropertySourcedRequestMappingHandlerMapping Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
[2023-09-06 10:58:41.686] [INFO ] [main] springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper Context refreshed
[2023-09-06 10:58:41.733] [INFO ] [main] springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper Found 1 custom documentation plugin(s)
[2023-09-06 10:58:41.826] [INFO ] [main] springfox.documentation.spring.web.scanners.ApiListingReferenceScanner Scanning for api listing references
[2023-09-06 10:58:42.045] [INFO ] [main] com.springboot.relationship.data.repository.ProductDetailRepositoryTest Started ProductDetailRepositoryTest in 9.867 seconds (JVM running for 12.8)
Hibernate: 
    insert 
    into
        product
        (created_at, updated_at, name, price, stock) 
    values
        (?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        product_detail
        (created_by, updated_by, description, product_number) 
    values
        (?, ?, ?, ?)
Hibernate: 
    select
        productdet0_.id as id1_1_0_,
        productdet0_.created_by as created_2_1_0_,
        productdet0_.updated_by as updated_3_1_0_,
        productdet0_.description as descript4_1_0_,
        productdet0_.product_number as product_5_1_0_,
        product1_.number as number1_0_1_,
        product1_.created_at as created_2_0_1_,
        product1_.updated_at as updated_3_0_1_,
        product1_.name as name4_0_1_,
        product1_.price as price5_0_1_,
        product1_.stock as stock6_0_1_ 
    from
        product_detail productdet0_ 
    left outer join
        product product1_ 
            on productdet0_.product_number=product1_.number 
    where
        productdet0_.id=?
savedProduct : Product(number=1, price=5000, stock=500)
Hibernate: 
    select
        productdet0_.id as id1_1_0_,
        productdet0_.created_by as created_2_1_0_,
        productdet0_.updated_by as updated_3_1_0_,
        productdet0_.description as descript4_1_0_,
        productdet0_.product_number as product_5_1_0_,
        product1_.number as number1_0_1_,
        product1_.created_at as created_2_0_1_,
        product1_.updated_at as updated_3_0_1_,
        product1_.name as name4_0_1_,
        product1_.price as price5_0_1_,
        product1_.stock as stock6_0_1_ 
    from
        product_detail productdet0_ 
    left outer join
        product product1_ 
            on productdet0_.product_number=product1_.number 
    where
        productdet0_.id=?
savedProductDetail : ProductDetail(super=BaseEntity(createdBy=null, updatedBy=null), id=1, description=스프링 부트와 JPA를 함께 볼 수 있는 책, product=Product(number=1, price=5000, stock=500))
[2023-09-06 10:58:42.545] [INFO ] [SpringApplicationShutdownHook] org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean Closing JPA EntityManagerFactory for persistence unit 'default'
[2023-09-06 10:58:42.545] [INFO ] [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource HikariPool-1 - Shutdown initiated...
[2023-09-06 10:58:42.560] [INFO ] [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource HikariPool-1 - Shutdown completed.

Hibernate: 
    
    alter table product_detail 
       drop 
       foreign key FKdp0jtp38vrl8mxrqwge69bjyh
Hibernate: 
    
    drop table if exists product
Hibernate: 
    
    drop table if exists product_detail
Hibernate: 
    
    create table product (
       number bigint not null auto_increment,
        created_at datetime(6),
        updated_at datetime(6),
        name varchar(255) not null,
        price integer not null,
        stock integer not null,
        primary key (number)
    ) engine=InnoDB
Hibernate: 
    
    create table product_detail (
       id bigint not null auto_increment,
        created_by varchar(255),
        updated_by varchar(255),
        description varchar(255),
        product_number bigint,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    
    alter table product_detail 
       add constraint FKdp0jtp38vrl8mxrqwge69bjyh 
       foreign key (product_number) 
       references product (number)
[2023-09-06 10:58:37.405] [INFO ] [main] org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
[2023-09-06 10:58:37.421] [INFO ] [main] org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean Initialized JPA EntityManagerFactory for persistence unit 'default'
[2023-09-06 10:58:39.280] [WARN ] [main] org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration$JpaWebConfiguration spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
[2023-09-06 10:58:40.092] [INFO ] [main] springfox.documentation.spring.web.PropertySourcedRequestMappingHandlerMapping Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
[2023-09-06 10:58:41.686] [INFO ] [main] springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper Context refreshed
[2023-09-06 10:58:41.733] [INFO ] [main] springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper Found 1 custom documentation plugin(s)
[2023-09-06 10:58:41.826] [INFO ] [main] springfox.documentation.spring.web.scanners.ApiListingReferenceScanner Scanning for api listing references
[2023-09-06 10:58:42.045] [INFO ] [main] com.springboot.relationship.data.repository.ProductDetailRepositoryTest Started ProductDetailRepositoryTest in 9.867 seconds (JVM running for 12.8)
Hibernate: 
    insert 
    into
        product
        (created_at, updated_at, name, price, stock) 
    values
        (?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        product_detail
        (created_by, updated_by, description, product_number) 
    values
        (?, ?, ?, ?)
Hibernate: 
    select
        productdet0_.id as id1_1_0_,
        productdet0_.created_by as created_2_1_0_,
        productdet0_.updated_by as updated_3_1_0_,
        productdet0_.description as descript4_1_0_,
        productdet0_.product_number as product_5_1_0_,
        product1_.number as number1_0_1_,
        product1_.created_at as created_2_0_1_,
        product1_.updated_at as updated_3_0_1_,
        product1_.name as name4_0_1_,
        product1_.price as price5_0_1_,
        product1_.stock as stock6_0_1_ 
    from
        product_detail productdet0_ 
    left outer join
        product product1_ 
            on productdet0_.product_number=product1_.number 
    where
        productdet0_.id=?
savedProduct : Product(number=1, price=5000, stock=500)
Hibernate: 
    select
        productdet0_.id as id1_1_0_,
        productdet0_.created_by as created_2_1_0_,
        productdet0_.updated_by as updated_3_1_0_,
        productdet0_.description as descript4_1_0_,
        productdet0_.product_number as product_5_1_0_,
        product1_.number as number1_0_1_,
        product1_.created_at as created_2_0_1_,
        product1_.updated_at as updated_3_0_1_,
        product1_.name as name4_0_1_,
        product1_.price as price5_0_1_,
        product1_.stock as stock6_0_1_ 
    from
        product_detail productdet0_ 
    left outer join
        product product1_ 
            on productdet0_.product_number=product1_.number 
    where
        productdet0_.id=?
savedProductDetail : ProductDetail(super=BaseEntity(createdBy=null, updatedBy=null), id=1, description=스프링 부트와 JPA를 함께 볼 수 있는 책, product=Product(number=1, price=5000, stock=500))
[2023-09-06 10:58:42.545] [INFO ] [SpringApplicationShutdownHook] org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean Closing JPA EntityManagerFactory for persistence unit 'default'
[2023-09-06 10:58:42.545] [INFO ] [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource HikariPool-1 - Shutdown initiated...
[2023-09-06 10:58:42.560] [INFO ] [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource HikariPool-1 - Shutdown completed.