연관관계 매핑
RDBMS를 사용할 때는 테이블 하나만 사용해서 애플리케이션의 모든 기능을 구현하는 것은 어렵다.
JPA를 사용하는 애플리케이션에서도 테이블의 연관관곌르 엔티티 간의 연관관계로 표현이 가능하다.
그 중에서 일대일(1:1) 매핑 방식에 대해 알아보겠습니다.
연관관계 매핑 종류와 방향
- One To One : 일대일(1:1)
연관관계 이해
[예시] 재고관리시스템
- 재고로 등록되어 있는 상품 엔티티에는 공급업체의 정보 엔티티가 매핑되어있다.
- 공급업체 입장에서는 한 가게에 납품하는 상품이 여러 개가 있을 수 있으므로 상품 엔티티와 일대다 관계가 된다.
- 상품 입장에서 보면 하나의 공급업체에 속하게 되므로 다대일 관계가 된다.
- 데이터베이스에서 두 테이블의 연관관계를 설정하면 외래키를 통해 서로 조인해서 참조하는 구조로 생성
- JPA를 사용하는 객체지향 모델링에서는 엔티티 간의 참조 방향을 설정할 수 있다.
- 단방향 : 두 엔티티의 관계에서 한쪽의 엔티티만 참조하는 형식
- 양방향 : 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조하는 형식이다.
- 연관관계를 설정하면 한 테이블에서 다른 테이블의 기본값을 외래키로 갖게 된다.
- 주인(Owner) : 일반적으로 외래키를 가진 테이블이 그 관계의 주인이 되며, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행할 수 있다.
일대일 단방향 매핑
상품 정보 엔티티
@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Table(name = "product_detail")
public class ProductDetail extends BaseEntity{
@Id
@GeneratedValue
private Long id;
private String description;
@OneToOne
@JoinColumn(name = "product_number")
private Product product;
}
- @OneToOne 어노테이션
- 다른 엔티티 객체를 필드로 정의했을 시에, 일대일 연관관계로 매핑하기 위해 사용된다.
- @JoinColumn 어노테이션
- 매핑할 외래키를 지정한다.
- 기본값이 설정되어 있어 자동으로 이름을 매핑하지만 의도한 이름이 들어가지 않기 때문에, name 속성을 이용하여 원하는 컬럼명을 지정하는 것이 좋다.
- @JoinColumn을 선언하지 않으면 엔티티를 매핑하는 중간 테이블이 생기면서 관리 포인트가 늘어나서 좋지 않다.
- @JoinColumn 어노테이션이 사용할 수 있는 속성
- name : 매핑할 외래키의 이름을 설정
- referencedColumnName : 외래키가 참조할 상대 테이블의 컬럼명을 지정한다.
- foreignKey : 외래키를 생성하면서 지정할 제약 조건을 설정(unique, nullable, insertable, updatable ...)
상품 정보 리포지토리 인터페이스
- 생성한 상품 정보 엔티티를 활용할 수 있도록 리포지토리를 생성한다.
public interface ProductDetailRepository extends JpaRepository<ProductDetail, Long> {
}
연관관계 테스트 코드
@SpringBootTest
class ProductDetailRepositoryTest {
@Autowired
ProductDetailRepository productDetailRepository;
@Autowired
ProductRepository productRepository;
@Test
void saveAndReadTest1() {
Product product = Product.builder()
.name("Springboot Jpa")
.price(5000)
.stock(500)
.build();
productRepository.save(product);
ProductDetail productDetail = new ProductDetail();
productDetail.setProduct(product);
productDetail.setDescription("springboot + jpa");
productDetailRepository.save(productDetail);
//생성 데이터 조회
System.out.println("product: " + productDetailRepository.findById(
productDetail.getId()).get().getProduct());
System.out.println("productDetail: " + productDetailRepository.findById(
productDetail.getId()).get());
}
}
- 테스트 코드를 실행하기 위해서는 상품과 상품정보에 매핑된 리포지토리에 대한 의존성을 주입받아야한다.
- 그리고, 테스트 상에서 조회할 엔티티 객체를 저장한다.
- 일대일 단방향 연관관계를 설정했기 때문에, Repository에서 detail 객체를 조회 후에 연관 매핑된 product 객체를 조회할 수 있다.
Hibernate: select productdet0_.id as id1_1_0_, productdet0_.created_at as created_2_1_0_, productdet0_.updated_at 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=?
- select 구문을 보면 productDetail과 product 객체가 동시 조회된다.
- 즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회하는 것을 뜻한다.
- left outer join : @OneToOne 어노테이션 인터페이스 사용으로 인해 발생
@OneToOne 어노테이션 인터페이스
public @interface OneToOne {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.EAGER;
boolean optional() default true;
String mappedBy() default "";
boolean orphanRemoval() default false;
}
- 기본 fetch 전략으로 즉시 로딩 전략(EAGER) 이 채택된 것을 볼 수 있다.
- optional() 메서드는 기본적으로 true로 설정되어 있다.
- 기본값이 true인 상태는 매핑되는 값이 Nullable이라는 것을 의미한다.
- 반드시 값이 있어야 한다면 엔티티의 속성값을 변경해줘야한다. 👉 @OneToOne(optional = false)
- @OneToOne(optional = false) 지정한 경우에는 left out join 👉 left inner join 으로 바뀌어서 실행된다.
일대일 양방향 매핑
양방향 매핑을 위해 엔티티 추가
- 객체에서의 양방향 개념은 양쪽에서 단방향으로 서로 매핑하는 것을 의미한다.
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;
}
- 양쪽에서 외래키를 가지고 있는 경우에는 join이 2번 발생되는 효율성이 떨어지는 경우가 발생한다.
- 실제 데이터베이스에서도 테이블 간 연관관계를 맺으면 한쪽 테이블이 외래키를 가지는 구조로 이루어진다.
mappedBy 속성을 이용한 엔티티 클래스
- 실제 데이터베이스의 연관관계를 반영해서, 한쪽의 테이블에서만 외래키를 바꿀 수 있도록 정의하는 것이 좋다.
- 엔티티는 양방향으로 매핑하되 한쪽에게만 외래키를 줘야하는데, 이 때 사용하는 것이 mappedBy이다.
- mappedBy는 어떤 객체가 주인인지 표시하는 속성이라고 볼 수 있다.
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")
private ProductDetail productDetail;
}
반응형
'Book > 스프링부트 핵심가이드' 카테고리의 다른 글
연관관계 매핑(of. 다대일(N:1), 일대다(1:N) 매핑) (0) | 2023.11.14 |
---|---|
JPA Auditing 개념과 활용 방법 (0) | 2023.11.09 |
@Query 어노테이션 관련 내용 정리 (2) | 2023.11.09 |
댓글