Spring Boot, 연관관계 매핑

Jihu Kim·2024년 1월 30일
0

Spring 입문

목록 보기
10/14
post-thumbnail

실무에서 테이블 하나만 사용해서 애플리케이션의 모든 기능을 구현하는 것은 불가능하다.

보통의 경우에는 테이블을 설계하고 연관관계를 설정해서 JOIN을 통해서 기능을 구현하게된다.

JPA를 사용하는 애플리케이션에서도 테이블의 연관관계를 엔티티 간의 연관관계로 표현할 수 있다.

JPA에서는 객체(Entity)를 통해서 연관관계를 설정한다.

연관관계 매핑

  • One To One : 일대일
  • One To Many : 일대다
  • Many To One : 다대일
  • Many To Many : 다대다

JPA를 사용하는 객체지향 모델링에서는 엔티티 간 참조방향을 설정할 수 있다. (데이터베이스에서는 연관관계를 설정하면 외래키를 통해 서로 조인해서 참조하는 구조로 생성된다.)

참조방향 설정

  • 단방향
  • 양방향

연관관계가 설정되면 한 테이블에서 다른 테이블의 기본값을 외래키로 갖고온다.

@OneToOne

일대일로 매핑되는 일대일 관계

예를 들자면, 하나의 상품은 하나의 상품정보를 갖는다.

@OneToOne 어노테이션은 일대일 연관관계로 매핑하기 위해 사용되며, @JoinColumn 어노테이션을 사용해 매핑할 외래키를 설정한다.

다음은 단방향 관계의 일대일 관계 매핑을 한 예시이다.

상품(상품번호, 상품이름, 상품가격, 상품재고, 공급업체번호, 상품분류번호, 상품생성일자, 상품정보변경일자) 테이블과 상품정보(상품정보번호, 상품설명, 상품번호, 상품정보생성일자, 상품정보변경일자) 테이블을 일대일로 매핑시킨다고 가정하고 진행하겠다.

단방향

@Entity
@Table(name = "product_detail")
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ProductDetail extends BaseEntity {

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

    private String description;

    @OneToOne //(optional=false) 예제 9.5
    @JoinColumn(name = "product_number")
    private Product product;

}

다음은 상품정보에 대한 도메인을 ProductDetail로 설정하고 진행한 코드이다.
@OneToOne 어노테이션은 다른 객체를 일대일로 매핑하기 위해 사용된다.
@JoinColumn 어노테이션은 매핑할 외래키를 설정하기 위해 사용된다.

위에서는 상품정보에 대한 도메인을 ProductDetail로 설정한 일대일 단방향 매핑을 했었다.

이러한 경우에는 한 테이블에서 다른 테이블의 기본값을 외래키로 갖고 일반적으로 외래키를 가진 테이블이 그 관계의 주인이 되며, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행할 수 있다.

즉, ProductDetail 테이블에만 칼럼이 생성된다.

상품정보 엔티티 객체를 사용하기 위해 리포지토리 인터페이스를 생성해야한다.

public interface ProductDetailRepository extends JpaRepository<ProductDetail, Long> {

}

양방향

Product 엔티티를 추가하고, @OneToOne 어노테이션을 통해서 양방향 매핑을 해준다.

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    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;
}

위와 같이 객체를 생성해주면, Product 테이블에도 칼럼이 생성된다.

양방향으로 매핑을 하게되면 양쪽에서 외래키를 가지고 left outer join이 두번 수행돼 효율성이 떨어진다.

한쪽의 테이블에서만 외래키를 바꿀 수 있도록 정하는 것이 좋다.

mappedBy를 통해서 어떤 객체가 주인인지 표시할 수 있다.

양방향 매핑을 하고 toString()을 실행하는 시점에서 순환참조가 발생해, StackOverFlowError가 발생할 수 있다. 따라서 순환참조를 제거하기 위해서 exclude를 사용해 ToString에서 제외 설정하는 것이 좋다.

@OneToMany, @ManyToOne

상품(상품번호, 상품이름, 상품가격, 상품재고, 공급업체번호, 상품분류번호, 상품생성일자, 상품정보변경일자) 테이블과 공급업체(공급업체번호, 업체이름, 업체생성일자, 업체정보변경일자) 테이블의 관계를 생각해보면 일대다 관계로 볼 수 있다.

단방향

Product 클래스가 외래키를 갖는 예시를 보고 알아보겠다. 일반적으로 외래키를 갖는 쪽이 주인의 역할을 수행한다는 것을 기억하자.

공급업체에 매핑되는 Provider 엔티티 클래스를 코드로 보면 이렇다.

@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Table(name = "provider")
public class Provider extends BaseEntity {

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

    private String name;
}

상품 엔티티는 다음과 같다.

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    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;
}

리포지토리를 생성하면 엔티티를 활용할 수 있게 된다.

@Repository("productRepositorySupport")
public interface ProductRepository extends JpaRepository<Product, Long>, ProductRepositoryCustom {

}

이 경우에는 ProductRepository를 통한 조회만 가능하다. Provider를 통한 조회도 가능하도록 할려면 양방향 매핑을 해야한다.

양방향

@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Table(name = "provider")
public class Provider extends BaseEntity {

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

    private String name;

    @OneToMany(mappedBy = "provider", cascade = CascadeType.PERSIST, orphanRemoval = true)
    @ToString.Exclude
    private List<Product> productList = new ArrayList<>();

}

Product가 여러개가 될 수도 있으므로 컬렉션(Collection, List, Map) 형식으로 필드를 생성한다.

@ManyToMany

생략..

단방향

양방향

profile
Jihukimme

0개의 댓글