관계형 데이터베이스에서는 일대일, 일대다, 다대일, 다대다의 관계를 이용해서 데이터가 서로 간에 어떻게 구성되었는지를 표현한다. 이 표현에서 가장 중요한 것이 PK와 FK를 어떻게 설정해서 사용하는가이다.
JPA를 이용해서 연관관계를 해석할 때는 PK를 기준으로 잡고, 데이터베이스를 모델링하는 방식으로 구성한다. 예를 들어, '회원, 게시글, 댓글'의 관계를 PK의 기준으로 설계하면 다음과 같다.

회원이 있어야만 게시글을 작성할 수 있다
댓글은 게시글이 있어야만 작성할 수 있다
FK를 기준으로 위 관계를 해석해보자.
💡 게시물은 회원과 다대일(N:1) 관계이다.
💡 댓글은 게시물과 다대일(N:1) 관계이다.
JPA는 객체지향의 입장에서 이들의 관계를 보기 때문에 데이터베이스와 달리 여러 선택이 가능하다.
관계형 데이터베이스에서는 PK와 FK만으로 표현되었던 관계가 객체지향으로 옮겨지면 다양한 선택지가 존재하게 된다. 흔히 단방향(unidirectional) 참조, 양방향(bidirectional) 참조 라고 표현하기도 하는데, 실제 데이터베이스에서는 양방향이라는 말이 존재하지 않기 때문에 객체지향에서만 겪는 문제라고 볼 수 있다.
위의 세 가지 선택지에서 가장 간단한 시작은 관계형 데이터베이스 모델링을 위주로해서 구성하는 것이다. FK를 사용하는 엔티티가 PK를 가진 엔티티를 참조하는 구조로 설계하면 데이터베이스와 동일한 구조가 되기 때문에 관계를 이해하는 것도 편하고 자동으로 테이블이 생성될 때도 유용하다.

board와 member 관계는 N:1(다대일)의 관계가 되므로 JPA에서는 이를 의미하는 @ManyToOne을 적용하면 된다. 해당 어노테이션을 데이터베이스상에서 외래키의 관계로 연결된 엔티티 클래스에 설정한다.
package org.zerock.board.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class Member extends BaseEntity {
@Id
private String email;
private String password;
private String name;
}
package org.zerock.board.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString(exclude = "writer")
public class Board extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long bno;
private String title;
private String content;
@ManyToOne
private Member writer;
}

이와 동일하게 Reply 쪽에서는 Board 쪽의 PK를 참고해서 구성되어야하므로, 수정해준다.
package org.zerock.board.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class Reply extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long rno;
private String text;
private String replyer;
@ManyToOne
private Board board;
}
두 개 이상의 엔티티 간의 연관관계를 맺고 나면 쿼리를 실행하는 데이터베이스 입장에서는 한 가지 고민이 생긴다. 엔티티 클래스들이 실제 데이터베이스상에서는 두 개 혹은 두 개 이상의 테이블로 생성되기 때문에 연관관계를 맺고 있다는 것은 데이터베이스의 입장으로 보면 조인이 필요하다는 것이다.
예를 들어, 위에서 작성한 엔티티에서 Reply를 가져오려면 Board와 Member까지 매번 조인하게 된다.
특정한 엔티티를 조회할 때 연관관계를 가진 모든 엔티티를 같이 로딩하는 것을 Eager loading이라고 한다. Eager은 일반적으로 즉시 로딩이라는 용어로 표현한다.
💡 fetch?
JPA에서 연관관계를 어떻게 가져올 것인가를 의미하는 용어로, 연관관계의 어노테이션 속성으로fetch모드를 지정한다.
Lazy loading은 지연 로딩이라고 표현한다.
장점 : 조인을 하지 않기 때문에 테이블이 하나일 경우 빠른 속도의 처리가 가능하다.
단점 : 필요한 순간 쿼리를 실행하기 때문에 연관관계가 복잡하다면 여러 번 쿼리가 실행된다.
따라서, 보편적인 코딩 가이드는 '지연 로딩을 기본으로 사용하고, 상황에 맞게 필요한 방법을 찾는다'이다.
지연 로딩을 사용하기 위해선 @Transactional을 추가로 작성해주어야한다.
해당 메서드를 하나의 트랜잭션으로 처리하라는 의미이다. 트랜잭션으로 처리하면 속성에 따라 다르게 동작하지만, 기본적으로는 필요할 때 다시 데이터베이스와 연결이 생성된다.

실행되는 메서드 위에 작성해주면된다.
✅ 연관관계에서의
@ToString()
@ToString()은 해당 클래스의 모든 멤버 변수를 출력하게 된다.- 예를 들어, Board 객체를
@ToString()으로 출력하고자 하면 writer 변수로 선언된 Member 객체 역시 출력해야 한다. 이를 출력하기 위해선 Member 객체의@ToString()이 호출되어야 하고, 이때 데이터베이스의 연결이 필요해진다.
이러한 문제로 연관관계가 있는 엔티티 클래스의 경우@ToString()을 할때는 습관적으로 exclude 속성을 사용하는 게 좋다.
exclude: 해당 속성값으로 지정된 변수를ToString()에서 제외한다.
목록 화면에서 게시글의 정보와 함께 댓글의 수를 같이 가져오기 위해서는 단순히 하나의 엔티티 타입을 이용할 수 없다. 이를 해결하는 방법을 다음 포스트에서 알아보자.