Post

연관 관계 매핑 (2)

🎬 Intro

연관 관계의 종류를 알아봅시다.

✅ @ManyToOne

JPA 연관관계에서 가장 많이 사용하고 한 객체가 여러 객체와 연관될 수 있는 관계입니다. 보통 부모-자식 관계에서 부모가 여러 자식을 가질수 있는 경우를 나타냅니다. 예로 하나의 BookStore는 여러 Book을 포함할 수 있고, 각 Book은 하나의 BookStore에만 속할 수 있는 경우가 @ManyToOne 관계입니다. DB 상에는 Many 쪽인 Book에 BookStore의 외래키가 존재하며, 객체 상에서 연관관계의 주인은 Many쪽인 Book이 됩니다.

또한 @ManyToOne의 경우 @JoinColumn으로 외래키를 선언하지 않아도 JPA는 기본적으로 자식 엔티티에 부모 엔티티를 참조하는 외래 키 컬럼을 자동으로 생성합니다.

✅ @OneToMany

보통 @ManyToOne 연관관계 시 양방향을 위해 사용하는 애노테이션입니다. @OneToMany를 단방향으로 사용한다면 외래키를 One쪽에서 관리하게 됩니다.(주인은 여전히 Many쪽) 예를 들어 Book 과 BookStore가 N:1이라면 Book 쪽에 BookStore의 외래키가 존재하게 됩니다. 하지만 이를 객체상에서 @OneToMany를 이용하여 BookStore에서 이 외래키를 관리하게 할 수 있습니다. 하지만 이렇게 되면 BookStore에 Book을 추가할때마다 Book쪽의 외래키를 업데이트해줘야하는 쿼리가 필요하게 됩니다. 이러한 이유로 @OneToMany 단방향 관계는 잘사용하지 않습니다.

@OneToMany의 경우 @JoinColumn으로 외래키를 선언하지 않으면 JPA는 둘 사이의 관계를 관리할 수 없기 때문에 조인 테이블을 자동으로 생성하게 됩니다.

✅ @OneToOne

두 객체의 1:1 괸계입니다. 예를들어 1명의 고객은 1개의 카드만 소지할 수 있는 경우입니다. 단방향, 양방향 모두 가능합니다. @OneToOne은 외래키가 없는 쪽의 객체가 연관관계의 주인이 될 수 없습니다. 따라서 만약 카드쪽에 고객의 외래키가 있는데 객체 상에서 고객을 이용해서 카드를 처리해야할 로직이 많다면 연관관계의 주인은 카드로 두고 고객쪽을 양방향으로 설정하여 처리해야 됩니다.

하지만 이 경우에는 고객을 조회할때 지연로딩이라 하더라도 고객이 카드를 소지하고 있지 않으면 프록시 객체에 null을 넣어야 함으로 카드 테이블을 필수로 조회하게 됩니다. 즉, 즉시로딩이 강제가 됩니다.

✅ @ManyToMany

DB는 다대다 개념이 없기 때문에 @ManyToMany를 사용할 경우 JPA는 조인테이블을 자동으로 생성합니다. 따라서 다대다 관계가 필요할경우 @ManyToMany를 사용하기 보다는 중간 테이블을 두어서 1:N, N:1 관계로 풀어내야합니다. 예를 들어 여러 상품을 주문을 할 수 있을경우 주문은 상품을 여러개 가질 수 있고, 상품 역시 주문이 여러번 되기 때문에 주문을 여러개 가질 수 있습니다. 이경우 주문상품이라는 중간 테이블을 두어서 이 관계를 풀어내야합니다.

📝 예제

@ManyToOne

Book (Many)이 외래키를 가지고 있으므로 연관관계 주인이다.

img.png

단방향
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
32
33
34
35
36
37
38
39
40
41
@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class Book {

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

  private String title;

  private String author;

  @ManyToOne
  @JoinColumn(name = "book_store_id")
  private BookStore bookStore;

  @Builder
  public Book(final Long id, final String title, final String author) {
    this.id = id;
    this.title = title;
    this.author = author;
  }

}


@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class BookStore {

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

  private String name;

}
  • 단방향이므로 BookStore객체에서는 Book을 읽지 못합니다.
양방향
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
@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class BookStore {

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

  private String name;
  
  @OneToMany(mappedBy = "bookStore", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<Book> books = new HashSet<>();


  @Builder
  public BookStore(final Long id, final String name) {
    this.id = id;
    this.name = name;
  }

  public void addBook(final Book book) {
    this.books.add(book);
  }

}

  • 양방향이므로 BookStore에서 Book객체를 읽을 수 있습니다.
  • Book, BookStore 생성시 연관관계 편의 메서드를 사용하여 DB와 객체 사이의 데이터 구조를 일치 시켜 줍니다.

@OneToMany

연관관계 주인은 Book(Many) 이지만 외래키를 BookStore가 관리

단방향
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class Book {

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

  private String title;

  private String author;

  @Builder
  public Book(final Long id, final String title, final String author) {
    this.id = id;
    this.title = title;
    this.author = author;
  }

}


@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class BookStore {

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

  private String name;

  @OneToMany
  @JoinColumn(name = "book_store_id")
  private Set<Book> books = new HashSet<>();


  @Builder
  public BookStore(final Long id, final String name) {
    this.id = id;
    this.name = name;
  }

  public void addBook(final Book book) {
    this.books.add(book);
  }

}
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
32
33
34
35
36
37
38
39
40
41
@DataJpaTest
public class JpaTest3 {

  @Autowired
  private BookRepository bookRepository;

  @Autowired
  private BookStoreRepository bookStoreRepository;

  @Autowired
  private EntityManager em;

  @DisplayName("@OneToMany 단방향 테스트")
  @Test
  void oneToManyTest() throws Exception {
    //given

    Book book = Book.builder()
      .title("테스트북")
      .author("테스트저자")
      .build();

    BookStore bookStore = BookStore.builder()
      .name("테스트북스토어")
      .build();

    bookStore.addBook(book);

    bookRepository.save(book);
    bookStoreRepository.save(bookStore);

    System.out.println("======= 쓰기 지연 ========");
    em.flush();

    //when

    //then
  }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Hibernate: 
    insert 
    into
        book
        (author, title, id) 
    values
        (?, ?, default)
Hibernate: 
    insert 
    into
        book_store
        (name, id) 
    values
        (?, default)
======= 쓰기 지연 ========
Hibernate: 
    update
        book 
    set
        book_store_id=? 
    where
        id=?


  • id 생성 전략이 IDENTITY 이므로 쓰기 지연은 지원되지 않습니다. 따라서 save시점에 바로 INSERT쿼리가 실행됩니다.
  • BookStore에 Book을 추가하였고 BookStore가 연관관계 주인이기 때문에 둘의 관계가 DB에 반영됩니다.
  • Book테이블에 외래키인 BookStore id를 업데이트하기 위해 업데이트 쿼리가 실행됩니다.
양방향

@OneToMany 양방향시에 @ManyToOne 쪽은 읽기, 쓰기를 false 처리해야합니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class Book {

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

	private String title;

	private String author;

	@ManyToOne
	@JoinColumn(name = "book_store_id", insertable = false, updatable = false) // 원투매니 양방향시에 매니투원쪽은 읽기, 쓰기를 false로해야함
	private BookStore bookStore;

	@Builder
	public Book(final Long id, final String title, final String author) {
		this.id = id;
		this.title = title;
		this.author = author;
	}

}


@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class BookStore {

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

  private String name;

  @OneToMany
  @JoinColumn(name = "book_store_id")
  private Set<Book> books = new HashSet<>();


  @Builder
  public BookStore(final Long id, final String name) {
    this.id = id;
    this.name = name;
  }

  public void addBook(final Book book) {
    this.books.add(book);
  }

}
  • 연관관계의 주인은 여전히 Many쪽에 존재합니다. 단지 외래키를 One쪽에서 관리할뿐입니다.
  • 테스트 결과는 위에 단방향과 동일합니다.

@OneToOne

연관관계의 주인은 DB상 외래키가 있는 쪽이 됩니다. 아래 예제는 고객이 카드의 외래키를 갖고 있을때입니다. 따라서 연관관계의 주인은 고객이 됩니다. 단방향, 양방향 모두 @ManyToOne과 유사합니다. 하지만 JPA는 @OneToOne의 경우 연관관계 주인이 아닌 쪽이 외래키를 관리하는 경우를 지원하지 않습니다.

단방향

img.png

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class Customer {

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

  private String name;

  @OneToOne(mappedBy = "customer_id")
  private Card card;

  @Builder
  public Customer(final Long id, final String name, final Card card) {
    this.id = id;
    this.name = name;
    this.card = card;
  }


}

@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class Card {

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

  private String number;

  @OneToOne
  @JoinColumn(name = "customer_id")
  private Customer customer;

  @Builder
  public Card(final Long id, final String number) {
    this.id = id;
    this.number = number;
  }
}
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
32
33
34
35
@SpringBootTest
public class JpaTest4 {


	@Autowired
	private CardRepository cardRepository;

	@Autowired
	private CustomerRepository customerRepository;

	@Autowired
	private EntityManager em;


	@DisplayName("@OneToOne 단방향 관계 테스트")
	@Test
	void oneToManyTest() throws Exception {
	    //given
		Customer customer = Customer.builder()
			.name("테스트고객")
			.build();

		Card card = Card.builder()
			.number("1234")
			.customer(customer)
			.build();
		//when

		customerRepository.save(customer);
		cardRepository.save(card);


	    //then
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Hibernate: 
    insert 
    into
        customer
        (name, id) 
    values
        (?, default)
Hibernate: 
    insert 
    into
        card
        (card_id, number, id) 
    values
        (?, ?, default)

img_8.png img_9.png

  • @OneToMany는 외래키를 주인이 아닌 쪽에서 관리하는 경우, 즉 BookStore에서 Book을 추가하고 둘 다 save를 할때 둘 관계를 DB에 반영하기 하고 Book 쪽 외래키가 업데이트 되었습니다.
  • 하지만 @OneToOne은 주인이 아닌쪽에서 관리하게 되면, 주인이 아닌쪽에도 외래키가 생기고 주인쪽 외래키는 업데이트되지 않아 null이 들어가게 됩니다.
  • Jpa는 @OneToOne에서 주인이 아닌쪽이 외래키를 관리하는것을 지원하지 않는다는걸 알 수 있습니다.
  • 따라서 @OneToOne의 외래키는 무조건 해당 외래키를 가지고 있는 객체에서 관리를 해야합니다.
양방향
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class Customer {

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

  private String name;

  @OneToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "card_id")
  private Card card;

  @Builder
  public Customer(final Long id, final String name, final Card card) {
    this.card = card;
    this.id = id;
    this.name = name;
  }
}


@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class Card {

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

  private String number;

  @OneToOne(mappedBy = "card", fetch = FetchType.LAZY)
  private Customer customer;

  @Builder
  public Card(final Long id, final String number, final Customer customer) {
    this.id = id;
    this.number = number;
    this.customer = customer;
  }


  // 연관관계 편의 메서드
  public void updateCustomer(final Customer customer) {
    customer.setCard(this);
    this.customer = customer;
  }
}
  • 고객 테이블에 카드 외래키가 존재하고 있습니다. 서로를 참조하는 양방향 입니다.
  • Fetch는 Lazy로 사용했지만 외래키가 없는 Card에서 Card를 가져올때는 EAGER 전략과 마찬가지로 Customer의 정보도 같이 가져오게 됩니다.
  • 그 이유는 해당 카드 프록시 객체에 고객이 없으면 null을 넣어야하기 때문에 어쩔수없이 고객 테이블을 조회해야 합니다.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@DataJpaTest
public class JpaTest5 {

  @Autowired
  private CardRepository cardRepository;

  @Autowired
  private CustomerRepository customerRepository;

  @Autowired
  private EntityManager em;

  @DisplayName("@OneToOne 양방향 테스트")
  @Test
  void oneToOneTest() throws Exception {
    //given

    Card card = Card.builder()
      .number("1234")
      .build();

    Customer customer = Customer.builder()
      .card(card)
      .name("테스트고객")
      .build();

    Card card1 = Card.builder()
      .number("4321")
      .build();

    Customer customer1 = Customer.builder()
      .card(card1)
      .name("테스트고객1")
      .build();

    //when
    card.updateCustomer(customer);

    cardRepository.save(card);
    customerRepository.save(customer);
    cardRepository.save(card1);
    customerRepository.save(customer1);

    /**
     * DB에 영속성 컨텍스트에 쌓인 쿼리반영, 1차 캐시 지우기
     */
    em.flush();
    em.clear();

    System.out.println("=== 모든 카드 조회시 고객도 함께 가져온다 ===");
    List<Card> cards = cardRepository.findAll();
    System.out.println("====================================");

    System.out.println("=========== 추가적인 SELECT 쿼리가 실행안된다 ============");
    for (Card c : cards) {
      System.out.println(c.getCustomer().getName());
    }
    System.out.println("====================================================");

    /**
     * 위에서 카드를 가져온것이 1차 캐시에 저장이 되어있다.
     * 따라서 1차 캐시를 비우지 않으면 고객에서 카드를 조회할때 1차 캐시에서 가져오기때문에 SELECT쿼리가 발생하지 않는다.
     */
    em.clear();

    System.out.println("=== 모든 고객 조회시 고객만 가져온다 ===");
    List<Customer> customers = customerRepository.findAll();
    System.out.println("================================");

    System.out.println("=========== 추가적인 SELECT 쿼리가 실행된다. =============");
    for (Customer cus : customers) {
      System.out.println(cus.getCard().getNumber());
    }
    System.out.println("====================================================");

    //then
  }
}


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
[Hibernate] 
    insert 
    into
        card
        (number, id) 
    values
        (?, default)
[Hibernate] 
    insert 
    into
        customer
        (card_id, name, id) 
    values
        (?, ?, default)
[Hibernate] 
    insert 
    into
        card
        (number, id) 
    values
        (?, default)
[Hibernate] 
    insert 
    into
        customer
        (card_id, name, id) 
    values
        (?, ?, default)
=== 모든 카드 조회시 고객도 함께 가져온다 ===
[Hibernate] 
    select
        c1_0.id,
        c1_0.number 
    from
        card c1_0
[Hibernate] 
    select
        c1_0.id,
        c1_0.card_id,
        c1_0.name 
    from
        customer c1_0 
    where
        c1_0.card_id=?
[Hibernate] 
    select
        c1_0.id,
        c1_0.card_id,
        c1_0.name 
    from
        customer c1_0 
    where
        c1_0.card_id=?
====================================
=========== 추가적인 SELECT 쿼리가 실행안된다 ============
테스트고객
테스트고객1
====================================================
=== 모든 고객 조회시 고객만 가져온다 ===
[Hibernate] 
    select
        c1_0.id,
        c1_0.card_id,
        c1_0.name 
    from
        customer c1_0
================================
=========== 추가적인 SELECT 쿼리가 실행된다. =============
[Hibernate] 
    select
        c1_0.id,
        c1_0.number 
    from
        card c1_0 
    where
        c1_0.id=?
[Hibernate] 
    select
        c1_0.id,
        c1_0.card_id,
        c1_0.name 
    from
        customer c1_0 
    where
        c1_0.card_id=?
1234
[Hibernate] 
    select
        c1_0.id,
        c1_0.number 
    from
        card c1_0 
    where
        c1_0.id=?
[Hibernate] 
    select
        c1_0.id,
        c1_0.card_id,
        c1_0.name 
    from
        customer c1_0 
    where
        c1_0.card_id=?
4321
====================================================
  • INSERT문은 무시하시고 아래 SELECT문을 보시면 모든 Card를 조회할때 추가로 Customer에 SELECT 쿼리가 발생하는것을 알 수 있습니다.
  • 앞전에 말씀드린것 처럼 Card 프록시 객체에 있는 Customer의 null 유무를 판단하기 위해서 SELECT 쿼리가 실행된것입니다.
  • Customer의 null 유무만 판단하면 되므로 EAGER 처럼 left join으로 가져오지 않습니다.
  • Card는 findAll()에 의해 1차 캐시에 저장되어 있는 상태입니다.
  • 따라서 em.clear()로 1차 캐시를 비우지 않으면 Customer 객체에서 Card 정보를 조회할때 1차 캐시에 있는 Card를 사용하게 됩니다.
  • Customer는 외래키를 가진 주체이기 때문에 LAZY전략이 잘반영되었고, Customer에서 card 정보를 꺼내오는 시점에 Customer 객체 수 만큼 추가 쿼리가 발생하였습니다(N + 1)
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
[Hibernate] 
    insert 
    into
        card
        (number, id) 
    values
        (?, default)
[Hibernate] 
    insert 
    into
        customer
        (card_id, name, id) 
    values
        (?, ?, default)
[Hibernate] 
    insert 
    into
        card
        (number, id) 
    values
        (?, default)
[Hibernate] 
    insert 
    into
        customer
        (card_id, name, id) 
    values
        (?, ?, default)
=== 모든 카드 조회시 고객도 함께 가져온다 ===
[Hibernate] 
    select
        c1_0.id,
        c1_0.number 
    from
        card c1_0
[Hibernate] 
    select
        c1_0.id,
        c2_0.id,
        c2_0.number,
        c1_0.name 
    from
        customer c1_0 
    left join
        card c2_0 
            on c2_0.id=c1_0.card_id 
    where
        c1_0.card_id=?
[Hibernate] 
    select
        c1_0.id,
        c2_0.id,
        c2_0.number,
        c1_0.name 
    from
        customer c1_0 
    left join
        card c2_0 
            on c2_0.id=c1_0.card_id 
    where
        c1_0.card_id=?
====================================
=========== 추가적인 SELECT 쿼리가 실행안된다 ============
테스트고객
테스트고객1
====================================================
=== 모든 고객 조회시 고객만 가져온다 ===
[Hibernate] 
    select
        c1_0.id,
        c1_0.card_id,
        c1_0.name 
    from
        customer c1_0
[Hibernate] 
    select
        c1_0.id,
        c2_0.id,
        c2_0.name,
        c1_0.number 
    from
        card c1_0 
    left join
        customer c2_0 
            on c1_0.id=c2_0.card_id 
    where
        c1_0.id=?
[Hibernate] 
    select
        c1_0.id,
        c2_0.id,
        c2_0.name,
        c1_0.number 
    from
        card c1_0 
    left join
        customer c2_0 
            on c1_0.id=c2_0.card_id 
    where
        c1_0.id=?
================================
=========== 추가적인 SELECT 쿼리가 실행된다. =============
1234
4321
====================================================

  • 전략을 EAGER로 바꾸면 Card, Customer 모두 조회시 left join으로 연관된 객체를 전부 가져오게 됩니다.
  • 따라서 Customer에서도 N+1 문제가 사라지게 됩니다.
  • 하지만 EAGER는 말씀드린대로 연관된 모든 객체를 다가져오기 때문에 전부 사용하는것이 아니라면 메모리 측면에서 비효율적입니다.
  • 따라서 연관된 일부 객체만 필요하다면 LAZY 전략을 기반으로 fetch join, entityGraph 등 방법을 사용해야합니다. 해당 내용은 추후 다른 포스팅에서 다루도록 하겠습니다.

참고

@ManyToMany는 연관 관계 매핑 (3)에서 다루도록 하겠습니다.

✨ Summary

@ManyToOne

  • 가장 많이 사용
  • Many쪽에 외래키가 생성되고, 연관 관계의 주인은 Many

@OneToMany

  • @ManyToOne과 양방향 매핑시 주로 사용됨
  • Many쪽에 외래키가 생성되고, 연관 관계의 주인은 Many
  • 외래키가 없는 One쪽에서 외래키를 관리하게끔 할 수 있음(권장 X)

@OneToOne

  • 두 엔티티가 1:1로 매핑
  • @OneToMany와 다르게 외래키가 없는 쪽에서 외래키를 관리할 수 없음
  • 무조건 외래키를 가진쪽이 외래키를 관리해야함
  • 이미 DB에 외래키가 잡혀있고, 외래키가 없는 쪽에서 대상 테이블에 조회가 많을 시 양방향 매핑을 고려
  • 양방향 매핑이 되어있을때 외래키가 A에 있다면 외래키가 없는 B가 findAll로 B객체를 조회시, 프록시 객체에 A의 null 유무를 알아야하므로 EAGER처럼 A의 정보도 모두 가져옴

LAZY vs EAGER

  • 상황에 따라 적절히 사용해야함
  • 주로 LAZY 기반으로 fetch join, EntityGraph등을 사용하여 연관된 객체중 필요한 놈들만 골라서 함께 가져옴
  • 추후 다른 포스팅에서 자세히 다룰 예정
This post is licensed under CC BY 4.0 by the author.