@ManyToOne
어노테이션은 JPA에서 엔티티 간의 관계를 매핑할 때 사용하는 애너테이션 중 하나이다. 이 애너테이션은 "다대일"(Many-to-One) 관계를 나타내는데 사용된다. 즉, 하나의 엔티티가 다른 엔티티와 다대일 관계에 있다는 것을 선언할 때 @ManyToOne
애너테이션을 사용한다.User Entity
@NoArgsConstructor // 빈생성자가 필요
@Entity
@Data
@Table(name = "user_tb")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id ;
@Column(unique = true)
private String username ;
private String password;
private String email;
@CreationTimestamp // persistance centext 에 전달될 때 자동으로 주입됨.
private Timestamp createdAt;
@Builder //빌더 패턴,엔티티에는 다 걸기
public User(int id, String username, String password, String email, Timestamp createdAt) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.createdAt = createdAt;
}
}
Board Entity
@NoArgsConstructor // 빈생성자가 필요
@Entity
@Data
@Table(name = "board_tb")
public class Board {
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
private Integer id;
private String title;
private String content;
//@JoinColumn(name="user_id") 변수명을 직접 지정 가능
@ManyToOne(fetch = FetchType.LAZY)
private User user ; // 변수명이 user. user_id 를 만들어줌
@CreationTimestamp // persistance centext 에 전달될 때 자동으로 주입됨.
private Timestamp createdAt;
@Builder //빌더 패턴,엔티티에는 다 걸기
public Board(Integer id, String title, String content, User user, Timestamp createdAt) {
this.id = id;
this.title = title;
this.content = content;
this.user = user;
this.createdAt = createdAt;
}
}
User 엔티티와 Board 엔티티가 있다. Board 엔티티 필드에 User 객체가 포함되어 있다.
이 두 엔티티의 매핑을 도와주는게
@ManyToOne
어노테이션이다.1. Fetch 속성
@ManyToOne
애너테이션에서 fetch 속성은 연관된 엔티티를 어떻게 로딩할지 결정하는 옵션이다.- Eager
- Lazy
- Lazy Loarding
- default_batch_fetch_size
2. Eager 속성

fetch 의 기본 속성은 Eager 로 정해져있다. Eager는 연관된 엔티티의 데이터를 처음 엔티티를 로딩할 때 함께 로딩하
BoardRepository
public Board findById(int id){
Board board = em.find(Board.class,id);
return board;
}
JUnit 테스트
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
@Import(BoardReposiroty.class)
@DataJpaTest
public class BoardRepositoryTest {
@Autowired
private BoardReposiroty boardReposiroty;
@Test
public void findById_test(){
int id = 1 ;
boardReposiroty.findById(1);
}
}

실행시 Board 엔티티와 User 엔티티가 자동으로 조인된다. 매우 편리한 방식이지만 불필요한 데이터까지 로딩되어 성능 저하를 일으킬 수 있다.
2. Lazy 속성

fetch 속성을 Lazy 로 변경 후 동일한 JUnit 테스트를 실행한다.
JUnit 테스트
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
@Import(BoardReposiroty.class)
@DataJpaTest
public class BoardRepositoryTest {
@Autowired
private BoardReposiroty boardReposiroty;
@Test
public void findById_test(){
int id = 1 ;
boardReposiroty.findById(1);
}
}

JOIN이 발생하지 않고 Board 엔티티만 조회가 된다.
3.Lazy Loading
Lazy 속성에선 Board 엔티티와 User 엔티티가 자동으로 조인되지 않기 때문에 만약 User 엔티티의 id를 제외한 다른 데이터는 null 값이 예상된다.
@Test
public void findById_test(){
int id = 1 ;
System.out.println("start-1");
Board board = boardReposiroty.findById(1);
System.out.println("start-2");
System.out.println(board.getUser().getId());
}

Lazy 속성을 사용하면 Board 엔티티를 조회했을 때 User 엔티티의 id를 조회할 수 있다.
이번에는 User 엔티티의 PK(id) 를 제외한 다른 데이터를 조회해본다.
@Test
public void findById_test(){
int id = 1 ;
Board board = boardReposiroty.findById(id);
System.out.println("start-2");
System.out.println(board.getUser().getId());
System.out.println("start-3");
System.out.println(board.getUser().getUsername());
}
}

User 엔티티의 username을 조회했을 때 null 값을 예상했지만, User 엔티티의 필터가 조회되는 시점에 쿼리가 한 번 더 전송되면서 데이터가 조회가 된다.
연관된 객체가 Lazy 상태일 때, 연관된 객체의 pk가 아닌 필드에 접근할 때 한번 더 조회 쿼리가 늦게 발동된다. Lazy 속성 역시 불필요한 데이터를 조회해 성능 저하가 발생할 수 있다.
4. 어떤 전략을 사용해야 할까?
fetch 의 속성은 Lazy 를 사용, 조인이 필요하다면 직접 JPQL 을 사용하자.
BoardRepository
public Board findByIdJoinUser(int id){
//join fetch 를 하면 b 를 조회했을 때 user의 객체도 같이 조회된다.
Query query = em.createQuery("select b from Board b join fetch b.user u where b.id =:id",Board.class);
query.setParameter("id",id);
Board board = (Board) query.getSingleResult();
return board;
}
Board 엔티티와 User 엔티티가 조인이 필요할 때 조인 쿼리를 직접 작성한다.
JPQL 을 사용해 쿼리를 작성할 때 join fetch 를 사용해야 조인된 전체 필드가 조회된다. 만약 fetch 가 없다면 드라이빙 테이블의 필드만 select 절에 조회된다.
JUnit 테스트/ fetch 미사용

JUnit 테스트/ fetch 사용

BoardController
@GetMapping("/board/{id}")
public String detail(@PathVariable Integer id,HttpServletRequest request) { // int 를 쓰면 값이 없으면 0, Integer 를 넣으면 값이 없을 때 null 값이 들어옴.
Board board = boardReposiroty.findByIdJoinUser(id);
request.setAttribute("board",board);
return "board/detail";
}
Board/detail.mustache
<div class="container p-5">
<!-- 수정삭제버튼 -->
<div class="d-flex justify-content-end">
<a href="/board/{{board.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/board/{{board.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
<div class="d-flex justify-content-end">
<b>작성자</b> : {{board.user.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{board.title}}</b></h2>
<hr />
<div class="m-4 p-2">
{{board.content}}
</div>
</div>
Board 엔티티의 User 오브젝트에 username이 있기 때문에 {{board.user.username}} 를 적는다.

Share article