Weekly TIL - Day 29
⭐️ Today's summary
오늘은 JPA라이브러리와 영속성 관리에 대한 이론과 현재 Vo 객체를 entity화 해보았다.
이론에 대해서는 내일 더 자세하게 할 것이기 때문에 오늘은 객체를 entity로 바꾸고,
테이블 처럼 PK , FK , 필드명 등을 줘보겠다.
⭐️ Problem
생각보다 FK를 설정하는것과 어노테이션이 생소하기때문에 코드를 설명해보겠다.
⭐️ Try
[ Member.java ]
package com.kh.board.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity // 데이터베이스랑 1:1 매칭이될 객체
@Table(name = "MEMBER") // 테이블 이름 설정 생략시 클래스 이름으로 들어감
public class Member {
@Id
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false, length = 100) // 최대길이 설정. 생략시 255
private String nickname;
@CreationTimestamp // 엔티티가 처음 저장될때 자동으로 현재 시간 저장
@Column(name = "created_at", updatable = false) // updatable = false -> 변경 불가
private LocalDateTime createAt;
@UpdateTimestamp // 엔티티가 수정될때마다 자동으로 업데이트
@Column(name = "update_at")
private LocalDateTime updateAt;
@OneToMany(mappedBy = "member",cascade = CascadeType.ALL)
// Member : Board = 1 : N 관계 (한명의 회원은 여러 게시글을 작성할 수 있음)
// mappedBy = "member" -> Board 엔티티의 "member" 필드가 외래키 주인임을 의미
// cascade = CascadeType.All -> 회원 삭제시 관련 게시글 모두 삭제
List<Board> boards = new ArrayList<>();
}
우선 Member 클래스의 코드를 보겠다.
예전에는 이 클래스를 Vo(Value Object)로 사용하였지만, 이제는 @Entity 어노테이션을 붙여줘서
데이터베이스랑 1대1 매칭이될 객체로 만들었다.
@Table 어노테이션은 테이블명을 붙여주는 것인데, 이 어노테이션이 생략되면 클래스의 이름으로 생성된다.
@Id 어노테이션은 PK를 부여하는 어노테이션이다.
@Column(nullable = false) -> Not Null 제약조건이다.
@Column(length = ~~~) -> VCHAR의 최대 길이를 정해주는 것이다. 생략시 255 들어감.
@Column(name = ~~~ ) -> 테이블에 들어갈 컬럼명을 지정해준다. 생략시 멤버변수 명으로 들어간다.
@CreationTimestamp -> 엔티티가 처음 저장될때 자동으로 현재 시간을 저장해준다.
@UpdateTimestamp -> 엔티티가 수정될때마다 자동으로 업데이트 된다.
@OneToMany(mappedBy = "member" , casecade = CascadeType.ALL)
-> OneToMany는 Member Entity 기준으로 관계를 결정하는것이다. 현재 한명의 회원은
여러개의 게시글을 작성할 수 있기때문에 1 : N 관계이기때문에 OneToMany라고 관계를 형성했다.
mappedBy에서 핵심은 mappedBy = "member" 이부분이다.
의미는 이 양방향 관계의 주인은 Board 엔티티 쪽이라는 뜻이다. -> 양방향 관계란 양쪽에서 join을 할 수 있다.
이말은 아래와 같은 코드가 있다는 걸 전제로 한다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_email", nullable = false)
private Member member;
mappedBy = "member"는 "이 연관관계는 Board 클래스에 있는 member 필드가 외래키 주인이다." 라고 JPA한테
알려주는 것이다.
이렇게 선언해줘야 JPA가 외래키 컬럼을 중복으로 생성하지 않는다 ‼️
그다음 List<Board> boards = new ArrayList<>(); 에 대해서 알아 보겠다.
이 필드는 한명의 회원이 작성한 게시글들을 저장하는 리스트이다.
즉, Board와의 1 : N 관계를 자바 객체로 표현한 것이다.
Member member = new Member();
List<Board> myPosts = member.getBoards(); // 해당 회원이 작성한 모든 게시글 가져오기
위 코드와 같이 SQL에서 아래와 같은 쿼리를 자바 객체로 대체하는 느낌이다.
SELECT * FROM board WHERE member_email = 'abc@example.com';
[ Board.java ]
package com.kh.board.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@Entity // 이 클래스가 JPA 엔티티다(DB테이블이랑 맵핑됨)
public class Board {
@Id // PK
@GeneratedValue(strategy = GenerationType.IDENTITY) // PK값 자동생성 (AUTO_INCREMENT 방식과 같음)
@Column(name = "board_id") // DB컬럼명 지정시에 스네이크 케이스로 저장하기 위함
private long boardId;
@Column(nullable = false) // NOT NULL
private String title; // String -> VCHAR로들어감
@Column(nullable = false, columnDefinition = "TEXT") // 데이터타입을 TEXT로 변경
private String contents;
@Column(name = "file_name")
private String fileName;
// private String memberEmail;
@ManyToOne(fetch = FetchType.LAZY)
// Board : Member = N : 1 관계 (다수의 게시글은 하나의 회원에 속함)
// LAZY : 실제 member 정보가 필요할때까지 조회를 지연(지연로딩) -> 성능 최적화를 위함
@JoinColumn(name = "member_email" , nullable = false)
// Board테이블에 member_email이라는 컬럼을 만들어서 해당 값으로 Member테이블의 PK컬럼을 참조하겠다.
private Member member;
@CreationTimestamp // 엔티티가 처음 저장될때 자동으로 현재 시간 저장
@Column(name = "created_at", updatable = false) // updatable = false -> 변경 불가
private LocalDateTime createAt;
@UpdateTimestamp // 엔티티가 수정될때마다 자동으로 업데이트
@Column(name = "update_at")
private LocalDateTime updateAt;
// 전체 setter를 사용하지 않고 변경이 필요한 부분만 setter 사용
public void changeFileName(String fileName) {
this.fileName = fileName;
}
}
위에서 정리한 중복되는 내용을 제외하고 Board Entity를 설명해보겠다.
@GeneratedValue(strategy = GenerationType.IDENTITY) -> PK값을 자동으로 생성해준다.
이 코드는 Oracle의 시퀀스와 같은 역할을한다.
하지만 GenerationType 전략은 SQL마다 다르다. MySql, MariaDB, H2DB는 IDENTITY
Oracle, PostgreSQL등은 SEQUENCE , 나머지는 DB 벤더에 따라 자동 선택된다.
이제 여기서 Board 엔티티는 Member 엔티티의 컬럼을 가지고 있기떄문에 FK키가 존재하는데,
이 FK를 설정해주는 코드를 보자.
@ManyToOne(fetech = FetchType.LAZY)
여기서 ManyToOne은 위에 정리한 OneToMany의 반대이다. 여러 게시글은 한명의 회원에 속하기 때문이다.
현재 엔티티 기준으로 생각하는것을 잊지 말자 ‼️
fetch = FetchType.LAZY 는 board.getMember()를 호출할 때까지 DB에서 Member를 조회 못하게 막게하는
지연 로딩기능이다. 이를 통해 성능 최적화를 기대할 수 있다.
@JoinColumn(name = "member_email" ) -> 이부분은 Board 테이블에 member_email이라는 컬럼을 만들고,
해당 값으로 Member테이블의 PK 컬럼은 참조 하겠다는 뜻이다. 이말은 FK를 설정한것이다.
마지막으로 private Member member는 Board에서 작성자를 객체 형태로 직접 접근 가능하도록 만든것이다.
매번 새로운 프레임워크를 배우면 처음에는 정말 헷갈리지만 적응만되면 정말 SQL 위주 설계보다 객체 지향에 맞게
서비스를 만들 수 있어서 좋을것이다.