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 |