| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- 연관관계
- 자료구조
- actuator
- spring
- JWT
- 백엔드공부
- error
- Java
- queue
- 프로그래머스
- ORM
- 핵심가이드
- 컴퓨터 공학
- SpringSecurity
- JPA
- QueryDSL
- 서버
- spring boot
- 개발자
- LinkedList
- restTemplate
- 컴퓨터 구조
- 제로베이스
- 스프링부트
- array
- 백엔드스쿨
- SpringBoot
- 백엔드
- TestCode
- 백준
- Today
- Total
be_better
[Springboot] 연관관계 매핑 - 종류, 일대일 매핑 본문
목차
RDBMS를 사용할 때는 테이블 하나만 사용해서 애플리케이션의 모든 기능을 구현하기란 불가능하다. 대체로 설계가 복잡해지면 각 도메인에 맞는 테이블을 설계하고 연관관계를 설정해서 조인(Join) 등의 기능을 활용한다. JPA를 사용하는 애플리케이션에서도 테이블의 연관관계를 엔티티 간의 연관관계로 표현할 수 있다.
연관관계 매핑 종류와 방향
두 엔티티 간에 생성할 수 있는 연관관계의 종류
- One To One: 일대일(1:1)
- One To Many: 일대다(1:N)
- Many To One: 다대일(N:1)
- Many To Many: 다대다(N:M)
연관관계를 이해하기 위해 한 가게가 재고관리시스템을 통해 상품을 관리한다고 가정할 때, 공급업체 입장에서 보면 한 가게에 납품하는 상품이 여러 개 있을 수 있으므로 상품 엔티티와는 일대다 관계, 상품 입장에서 보면 하나의 공급업체에 속하게 되므로 다대일 관계가 된다.
즉, 어떤 엔티티를 중심으로 연관 엔티티를 보느냐에 따라 연관관계의 상태가 달라진다.

데이터베이스에서는 두 테이블의 연관관계를 설정하면 외래키를 통해 서로 조인해서 차조하는 구조로 생성되지만 JPA를 사용하는 객체지향 모델링에서는 엔티티 간 참조 방향을 설정할 수 있다. 데이터베이스 관계를 일치시키기 위해 양방향으로 설정해도 무관하지만 비즈니스 로직의 관점에서 봤을 때는 단방향 관계만 설정해도 해결되는 경우가 많다.
- 단방향: 두 엔티티의 관계에서 한쪽의 엔티티만 참조하는 형식
- 양방향: 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조하는 형식
연관관계가 설정되면 한 테이블에서 다른 테이블의 기본값을 외래키로 갖게 된다. 이런 관계에서는 주인(Owner)라는 개념이 사용된다. 일반적으로 외래키를 가진 테이블이 그 관계의 주인이 되며, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행할 수 있다.
일대일 매핑
프로젝트에 일대일로 매핑될 상품정보 테이블을 생성한다.

일대일 단방향 매핑
상품 정보에 대한 도메인은 ProductDetail로 설정해서 진행한다.
@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
@JoinColumn(name = "product_number")
private Product product;
}
상품 정보 테이블을 상품 번호에 매핑하기 위해 @OneToOne 어노테이션을 사용한다. @OneToOne 어노테이션은 다른 엔티티 객체를 필드로 정의했을 때 일대일 연관관계로 매핑하기 위해 사용된다.
@JoinColumn 어노테이션은 매핑할 외래키를 설정한다. @JoinColumn 어노테이션은 기본값이 설정돼 있어 자동으로 이름을 매핑하지만 의도한 이름이 들어가지 않기 때문에 name 속성을 사용해 원하는 칼럼명을 지정하는 것이 좋다.
만약 @JoinColumn을 선언하지 않으면 엔티티를 매핑하는 중간 테이블이 생기면서 관리 포인트가 늘어나 좋지 않다.
@JoinColumn 에서 사용할 수 있는 속성들
- name: 매핑할 외래키의 이름을 설정한다.
- referencedColumnName: 외래키가 참조할 상대 테이블의 칼럼명을 지정한다.
- foreignKey: 외래키를 생성하면서 지정할 제약조건을 설정한다.(unique, nullable, insertable, updatable 등)
애플리케이션을 실행시켜 db를 확인해보면 다음과 같다.

ProductDetailRepository 인터페이스 생성
@Repository
public interface ProductDetailRepository extends JpaRepository<ProductDetail, Long> {
}
ProductDetailRepositoryTest 테스트 코드
@SpringBootTest
class ProductDetailRepositoryTest {
@Autowired
private ProductRepository productRepository;
@Autowired
private ProductDetailRepository productDetailRepository;
@Test
void saveAndReadTest() {
Product product = new Product();
product.setName("스프링 부트 JPA");
product.setPrice(5000);
product.setStock(500);
productRepository.save(product);
ProductDetail productDetail1 = new ProductDetail();
productDetail1.setProduct(product);
productDetail1.setDescription("스프링 부트와 JPA를 함께 볼 수 있는 책");
productDetailRepository.save(productDetail1);
System.out.println("savedProduct : " + productDetailRepository.findById(
productDetail1.getId()).get().getProduct()
);
System.out.println("savedProductDetail : " + productDetailRepository.findById(
productDetail1.getId()).get()
);
}
}
실행한 결과는 다음과 같다.

이와같이 select 구문에 ProductDetail 객체와 Product 객체가 함께 조회되는 것을 볼 수 있다. 이처럼 엔티티를 조회할 때 연관된 엔티티도 함께 조회하는 것을 '즉시 로딩' 이라고 한다.
그리고 @OneToOne 어노테이션 때문에 left outer join이 수행된다.
@OneToOne 어노테이션 인터페이스
public @interface OneToOne {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default EAGER;
boolean optional() default true;
String mappedBy() default "";
boolean orphanRemoval() default false;
}
@OneToOne 어노테이션은 기본 fetch 전략으로 EAGER, 즉 즉시 로딩 전략이 채택된 것을 볼 수 있다.
그리고 optional() 메서드는 기본값으로 true가 설정되어 있다. 기본값이 true인 상태는 매핑되는 값이 nullable이라는 것을 의미한다. 반드시 값이 있어야 한다면 @OneToOne(optional = false)와 같이 속성을 설정해야 한다.
Test코드로 테스트 해본다면 left outer join이 inner join으로 바뀌어 있는 것을 확인해볼 수 있다.

일대일 양방향 매핑
일대일 양방향 매핑을 위해서는 Product 엔티티에 @OneToOne 어노테이션을 설정한 ProductDetail를 선언해준다.
@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
private ProductDetail productDetail;
}
실행 후 db를 확인해보면

Test코드로 테스트해보면

여러 테이블에서 연관관계가 설정되어 있어 여러 left outer join이 설정되는 것은 괜찮으나 위와 같이 양쪽에서 외래키를 가지고 left outer join이 두번이나 수행되는 경우는 효율성이 떨어진다. 실제 데이터베이스에서도 테이블 간 연관관계를 맺으면 한쪽 테이블이 외래키를 가지는 구조로 이뤄지는게 이러한 개념이 바로 '주인' 개념이다.
따라서 한쪽의 테이블에서만 외래키를 바꿀 수 있도록 정해줘야 하는데 이때 사용되는 속성 값이 mappedBy 속성이다. 어떤 객체가 주인인지 표시하는 속성이라고 볼 수 있다.
@OneToOne(mappedBy = "product")
private ProductDetail productDetail;
db를 확인해보면 product 테이블에서 외래키가 사라진 것을 볼 수 있다.

이렇게 양방향으로 연관관계가 설정되면 ToString을 사용할 때 순환참조가 발생하기 때문에 테스트 할때 StackOverflowError가 발생한다. 순환참조 제거를 위해 @ToString.Exclude를 사용해 ToString에서 제외 설정을 하는 것이 좋다.
@OneToOne(mappedBy = "product")
@ToString.Exclude
private ProductDetail productDetail;'Springboot > 이론' 카테고리의 다른 글
| [Springboot] 연관관계 매핑 - 다대다 매핑 (0) | 2023.11.18 |
|---|---|
| [Springboot] 연관관계 매핑 - 다대일, 일대다 매핑 (0) | 2023.11.17 |
| [Springboot] Spring Data JPA 활용 - JPA Auditing (0) | 2023.11.12 |
| [Springboot] QueryDSL 설정 gradle (0) | 2023.11.12 |
| [Springboot] Spring Data JPA 활용 - @Query 어노테이션 (0) | 2023.11.10 |