[스프링 부트] 31. Entity 연관 관계 설정 - 웹 게시판 v6

KangHo Lee's avatar
Nov 29, 2024
[스프링 부트] 31. Entity 연관 관계 설정 - 웹 게시판 v6

Hibernate

  • Hibernate는 JPA 명세를 구현한 객체-관계 매핑(Object-Relational Mapping, ORM) 프레임워크 중 하나입니다.
  • 엔티티 간의 연관 관계 설정을 도와줍니다.

1. User Entity

@Table(name = "user_tb") @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(unique = true, nullable = false) private String username; @Column(nullable = false) private String password; @Column(nullable = false) private String email; @CreationTimestamp private Timestamp createdAt; @Builder public User(Integer id, String username, String password, String email, Timestamp createdAt) { this.id = id; this.username = username; this.password = password; this.email = email; this.createdAt = createdAt; } }

2. Board Entity

@Table(name = "board_tb") @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String title; private String content; // private Integer userId; -> 외래키 @ManyToOne(fetch = FetchType.LAZY) private User user; @CreationTimestamp 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; } }

1) 필드에 객체가 있을 경우 빌더 패턴 작성 방법

  • @NoArgsConstructor(access = AccessLevel.PROTECTED)
    • 빌더 외의 방법으로 객체를 생성하지 못하게 합니다.
  • all arguments constructor (풀 생성자)에 @Builder 어노테이션을 작성합니다.
    • 필드에 컬렉션이 있는 경우 컬렉션은 제외한 생성자를 만들어야 합니다.

2) @ManyToOne 으로 연관 관계 설정

  • JPA에서 두 테이블 간의 다대일(Many-to-One) 관계를 정의할 때 사용됩니다.
  • 여기서는 Board 엔티티가 User 엔티티와 n : 1 관계에 있음을 나타냅니다.
  • DB에는 객체(User)가 들어가지 못하기 때문에 해당 객체의 기본키를 외래키로 설정합니다.
    • 이 외래키는 user_id로 생성됩니다.

3) FetchType.LAZY (지연 로딩)

  • JPA에서 제공하는 성능 최적화 기법 중 하나로, 연관된 엔티티를 실제로 필요할 때까지 로드하지 않는 전략입니다.
public class Main { public static void main(String[] args) { // 엔티티 매니저 설정 및 트랜잭션 시작 (생략) // Board 엔티티 조회 Board board = entityManager.find(Board.class, 1); // 이 시점에서는 User 엔티티가 로드되지 않음 System.out.println("Lazy Loading 직전"); // User 엔티티가 필요할 때 로드됨 String username = board.getUser().getUsername(); System.out.println("Lazy Loading 직후"); // 트랜잭션 종료 및 엔티티 매니저 종료 (생략) } }
  • 동작 방식
    • 프록시 객체 생성
      • Board 엔티티가 로드될 때, User 엔티티는 프록시 객체로 대체됩니다.
      • 이는 실제로 User 엔티티의 데이터를 가지지 않지만, 해당 엔티티에 접근할 때 데이터베이스 조회를 트리거합니다.
    • 실제 데이터 로드
      • board.getUser()와 같이 User 엔티티에 접근하는 시점에서 데이터베이스 조회가 발생하여 실제 데이터가 로드됩니다.
      • 이 시점에서 프록시 객체는 실제 User 객체로 대체됩니다.
  • 지연 로딩의 장점
    • 성능 최적화
      • 초기 로딩 시 불필요한 데이터를 로드하지 않음으로써 성능을 향상시킵니다.
      • 특히, 연관된 엔티티가 많거나 대용량 데이터베이스를 다룰 때 유용합니다.
    • 메모리 사용 절약
      • 필요한 시점에만 데이터를 로드함으로써 메모리 사용을 절약할 수 있습니다.
  • 주의 사항
    • 트랜잭션 관리
      • 지연 로딩은 일반적으로 트랜잭션 내에서 사용됩니다.
      • 트랜잭션이 종료된 후에 지연 로딩된 엔티티에 접근하면 예외가 발생할 수 있습니다.
    • 즉시 로딩(Eager Loading)과의 차이
      • 즉시 로딩(FetchType.EAGER)은 연관된 엔티티를 즉시 로드합니다.
      • 이는 초기 조회 시점에서 모든 연관된 엔티티를 로드하기 때문에 지연 로딩보다 성능에 영향을 미칠 수 있습니다.

3. 연관 관계에 맞춰 Response DTO 수정하기

BoardResponse.DetailDTO
public class BoardResponse { @Data public static class DetailDTO { private int id; private String title; private String content; private String createdAt; private Integer userId; private String username; private boolean isOwner = false; public DetailDTO(Board board, User sessionUser) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); this.createdAt = MyDate.formatToStr(board.getCreatedAt()); this.userId = board.getUser().getId(); this.username = board.getUser().getUsername(); if (sessionUser != null) { this.isOwner = sessionUser.getUsername().equals(board.getUser().getUsername()); } } } }
  • Service에서 Controller로 보낼 때 DTO를 만듭니다.
  • DTO의 필드는 객체가 아니라 실제 컬럼값으로 사용되는 외래키 등을 넣어줘야 합니다.
Share article

devleekangho