SpringBoot

연관관계 매핑

똑똑한망치 2024. 2. 22. 20:58
728x90
반응형

1. 연관관계 매핑 종류와 방향


  • One To One : 일대일 (1:1)
  • One To Many : 일대다(1:N)
  • Many To One : 다대일 (N:1)
  • Many To Many : 다대다(N:M)

 

예시 사진

 

데이터베이스에서는 두 테이블의 연관관계를 설정하면 외래키를 통해 서로 조인해서 참조하는 구조로 생성되지만 JPA를 사용하는 객체지향 모델링에서는 엔티티 간 참조 방향을 설정할 수 있다. 데이터베이스와 관계를 일치시키기 위해 양방향으로 설정해도 무관하지만 비즈니스 로직의 관점에서 봤을 때 단방향 관계만 설정해도 해결되는 경우가 많다.

 

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

 

연관관계가 설정되면 한 테이블에서 다른 테이블의 기본값을 외래키로 갖는다. 이런 관계에서는 주인(Owner)이라는 개념이 사용된다. 일반적으로 외래키를 가진 테이블이 그 관계의 주인이 되며, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행할 수 있다.

 

 

 

 

2. 일대일 매핑


일대일 매핑 예시

 

(1) 일대일 단방향 매핑

// 일대일 단방향 매핑 예시
// 상품정보 엔티티

@Entity
@Table(name = "product_detail")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ProductDetail extends BaseEntity {
	
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String description;
    
    @OneToOne
    @JoinColumn(name = "product_number")
    private Product product;
}

 

@OneToOne 어노테이션은 다른 엔티티 객체를 필드로 정의했을 때 일대일 연관관계로 매핑하기 위해 사용된다. 

@JoinColumn 어노테이션을 사용해 매핑할 외래키를 설정한다.

 

 

 

(2) 일대일 양방향 매핑

객체에서의 양방향 개념은 양쪽에서 단방향으로 서로를 매핑하는 것을 의미한다.

// 일대일 양방향 매핑 예시
// 상품 엔티티

@Entity
@Table(name = "product")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
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;
}

 

JPA에서 실제 데이터베이스의 연관관계를 반영하여 한쪽의 테이블에서만 외래키를 바꿀 수 있도록 정하는 것이 좋다.

이 경우 엔티티는 양방향으로 매핑하되 한쪽에게만 외래키를 줘야 하는데, 이때 사용하는 속성 값이 mappedBy 이다.

mappedBy 속성은 어떤 객체가 주인인지 표시하는 속성이라고 볼 수 있다.

양방향으로 연관관계가 설정되면 ToString을 사용할 때 순환참조가 발생한다. 그렇게 때문에 필요한 경우가 아니라면 대체로 단방향으로 연관관계를 설정하거나 양방향으로 설정할 경우 순환참조 제거를 위해 ToString.Exclude를 하여 제외 설정을 해야한다.

 

 

 


 

 

 

3. 다대일, 일대다 매핑


다대일, 일대다 매핑 예시

 

(1) 다대일 단방향 매핑

 

// 공급업체 엔티티 클래스

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
}
// 다대일 단방향 예시
// 상품 엔티티

@Entity
@Table(name = "product")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
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;
}

 

상품 엔티티와 공급업체 엔티티 사이에 다대일 단방향 연관관계를 설정했다.

일반적으로 외래키를 갖는 쪽이 주인의 역할을 수행하기 때문에 이 경우 상품 엔티티가 공급업체 엔티티의 주인이다.

 

 

 

 

(2) 다대일 양방향 매핑

// 공급업체 엔티티 클래스
// 다대일 양방향 매핑 예시

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "provider", fetch = FetchType.EAGER)
    @ToString.Exclude
    private List<Product> productList = new ArrayList<>();
}

 

일대다 연관관계의 경우 여러 상품 엔티티가 포함될 수 있어 컬렉션(Collection, List, Map) 형식으로 필드를 생성한다.

주인이 아닌 엔티티 클래스에서 데이터베이스에 레코드를 저장하게 되면 해당 데이터는 데이터베이스에 반영되지 않는다.

 

 

 

 

(3) 일대다 단방향 매핑

일대다 관계인 상품 분류 테이블과 상품 테이블

 

 

@OneToMany를 사용하는 입장에서 어느 엔티티 클래스도 연관관계의 주인이 될 수 없기 때문에 양방향 매핑은 다루지 않는다.

 

// 일대다 단방향 매핑 예시
// 상품 분류 엔티티 클래스

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true)
    private String code;
    
    private String name;
    
    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "category_id")
    private List<Product> products = new ArrayList<>();
}

 

일대다 단방향 관계의 단점은 매핑의 주체가 아닌 반대 테이블에 외래키가 추가된다는 것이다.

일대다 연관관게에서는 연관관계 설정을 위한 update 쿼리가 발생한다.

 

 

 


 

 

 

4. 다대다 매핑


다대다(M:N) 연관관계는 실무에서 거의 사용되지 않는 구성이다. 

다대다 연관관계에서는 각 엔티티에서 서로를 리스트로 가지는 구조가 만들어진다. 이런 경우에는 교차 엔티티라고 부르는 중간 테이블을 생성해서 다대다 관계를 일대다 또는 다대일 관계로 해소한다.

 

 

다대다 관계인 상품 테이블과 생산업체 테이블

 

 

 

(1) 다대다 단방향 매핑

// 다대다 단방향 매핑 예시
// 생산업체 엔티티

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String code;
    
    private String name;
    
    @ManyToMany
    @ToString.Exclude
    private List<Product> products = new ArrayList<>();
    
    public void addProduct(Product product) {
    	products.add(product);
    }
}

 

다대다 연관관계는 @ManyToMany 어노테이션으로 설정한다. 리스트로 필드를 가지는 객체에서는 외래키를 가지지 않기 때문에 별도의 @JoinColumn은 설정하지 않아도 된다.

 

 

 

(2) 다대다 양방향 매핑

// 공급업체 엔티티 클래스
// 다대일 양방향 매핑 예시

@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@Table(name = "product")
@EqualsAndHashCode(callSuper = true)
public class Provider 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 productDatil;
    
    @ManyToOne
    @JoinColumn(name = "provider_id")
    @ToString.Exclude
    private Provider provider;
    
    @ManyToMany
    @ToString.Exclude
    private List<Producer> producers = new ArrayList<>();
    
    public void addProducer(Producer producer) {
    	this.producers.add(producer);
    }
}

 

@ManyToMany 어노테이션을 이용하여 다대다 연관관계를 설정한다. 필요에 따라 mappedBy 속성을 통해 두 엔티티 간 연관관계의 주인을 설정할 수 있다.

다대다 연관관계를 설정하면 중간 테이블을 통해 연관된 엔티티의 값을 가져올 수 있다. 하지만 중간 테이블이 생성되기 때문에 예기치 못한 쿼리가 생길 수 있다. 즉, 관리하기 힘든 포인트가 발생한다는 문제가 있다.

 

 

 


 

 

 

5. 영속성 전이


영속성 전이(cascade)란 특정 엔티티의 영속성 상태를 변경할 때 그 엔티티와 연관된 엔티티의 영속성에도 영향을 미쳐 영속성 상태를 변경하는 것을 의미한다.

 

< 영속성 전이 타입의 종류 >

종류 설명
ALL 모든 영속 상태 변경에 대해 영속성 전이를 적용
PERSIST 엔티티가 영속화할 때 연관된 엔티티도 함께 영속화
MERGE 엔티티를 영속성 컨텍스트에 병합할 때 연관된 엔티티도 병합
REMOVE 엔티티를 제거할 때 연관된 엔티티도 제거
REFRESH 엔티티를 새로고침할 때 연관된 엔티티도 새로고침
DETACH 엔티티를 영속성 컨텍스트에서 제외하면 연관된 엔티티도 제외

 

영속성 전이에 사용되는 타입은 엔티티 생명주기와 연관이 있다.

 

 

 

 

(1) 고아 객체

JPA에서 고아(orphan)란 부모 엔티티와 연관관계가 끊어진 엔티티를 의미한다. JPA에는 이러한 고아 객체를 자동으로 제거하는 기능이 있다.

 

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

 

예시에서 "orphanRemoval = true" 속성은 고아 객체를 제거하는 기능이다.

반응형

'SpringBoot' 카테고리의 다른 글

예외 처리  (0) 2024.02.28
유효성 검사  (1) 2024.02.28
JPA Auditing  (0) 2024.02.14
Spring Data JPA 활용  (0) 2024.02.13
ORM (Object Relational Mapping)  (1) 2024.02.08