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