JPA로 DB의 테이블과 상호작용(데이터 CRUD)하기 위해서는,
엔티티 클래스와 DB의 테이블이 매핑되어 있어야 한다.
엔티티 매핑은 크게 아래와 같이 분류할 수 있다.
- 엔티티 객체 & DB 테이블 간의 매핑
- 기본키 매핑
- 필드(멤버변수) &컬럼 간의 매핑
- 엔티티 간의 연관관계 매핑
1️⃣ 엔티티 & 테이블 매핑
- @Entity: JPA가 관리하는 클래스 = 엔티티 클래스
- @Table: 엔티티와 매핑할 테이블을 지정하는 애너테이션
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table
public class Member {
@Id
private Long memberId;
}
- @Entity & @Id 애너테이션을 통해 엔티티 클래스와 DB 테이블을 매핑한다.
- @Entity 애너테이션이 클래스 레벨에 붙으면 JPA 관리 대상 엔티티가 된다.
@Entity(name ="")
- 엔티티 자체의 이름을 정할 때 사용
- name 애트리뷰트가 정해지지 않으면 기본값으로 클래스 이름 사용
@Table(name ="")
- DB에 생성될 테이블의 이름을 지정할 때 사용
- name 애트리뷰트가 정해지지 않으면 기본값으로 클래스 이름 사용
✔️@Entity(name ="")만 존재할 경우 엔티티와 DB 테이블 모두의 이름을 결정한다.
✔️@Table 애너테이션은 옵션. 주로 테이블 이름이 클래스 이름과 달라야 할 경우 추가한다.
✔️@Entity + @Id 애너테이션은 필수이다.
✔️ Spring data JPA를 적용할 때 기본 생성자가 없으면 에러가 발생할 수도 있기 때문에 @NoArgsConstructor를 습관적으로(?) 사용하는 것을 권장한다.
✔️ 중복되는 엔티티 클래스가 없을 때, 테이블 이름이 클래스 이름과 같을 경우 @Table과 @Entity의 name 애트리뷰트를 지정하지 않는 것이 권장된다.
2️⃣ 기본키 매핑
- JPA에서는 @Id 애너테이션을 추가한 필드가 Primary Key(기본키) 컬럼이 된다.
- JPA에서는 기본키를 생성하는 다양한 방식을 지원한다.
- 기본키 직접 할당
- 기본키 자동 생성
- IDENTITY
- SEQUENCE
- TABLE
- AUTO
✅ 기본키 직접 할당 전략
@NoArgsConstructor
@Getter
@Entity
public class Member {
@Id
private Long memberId;
public Member(Long memberId) {
this.memberId = memberId;
}
}
위와 같이 @Id 애너테이션을 필드에 추가해주면 기본적으로 기본키 직접 할당 전략이 적용된다.
위와 같이 @Id 애너테이션을 통해 기본키를 직접 적용할 경우,
@Configuration
public class JpaIdDirectMappingConfig {
...
@Bean
public CommandLineRunner testSingleMappingRunner(EntityManagerFactory emFactory){
...
return args -> {
tx.begin();
em.persist(new Member(1L)); // (1)
tx.commit();
};
}
}
위의 코드처럼 객체 생성 시 기본키를 직접 할당해서 엔티티를 저장해야 한다.
기본키 없이 엔티티 저장을 시도하면 에러가 발생한다.
✅ IDENTITY 전략
@NoArgsConstructor
@Getter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
public Member(Long memberId) {
this.memberId = memberId;
}
}
기본키 자동생성 전략 - IDENTITY 를 설정하려면 @Id 필드의 @GeneratedValue 애너테이션의 strategy 애트리뷰트의 값을 GenerationType.IDENTITY로 지정해주면 된다.
위의 코드와 같이 IDENTITY 전략을 설정해주면,
@Configuration
public class JpaIdIdentityMappingConfig {
...
@Bean
public CommandLineRunner testJpaSingleMappingRunner(EntityManagerFactory emFactory){
...
return args -> {
tx.begin();
em.persist(new Member());
tx.commit();
Member member = em.find(Member.class, 1L);
System.out.println("# memberId: " + member.getMemberId());
};
}
}
Member 엔티티에 따로 기본키 값을 할당해 주지 않아도 자동으로 키 값이 생성되는 것을 볼 수 있다.
(email 칼럼은.. 실수🥲)
MEMBER 테이블에 데이터를 저장 후, 기본키 값이 자동으로 생성된다.
✅ SEQUENCE 전략
@NoArgsConstructor
@Getter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long memberId;
public Member(Long memberId) {
this.memberId = memberId;
}
}
기본키 자동생성 전략 - SEQUENCE 를 설정하려면 @Id 필드의 @GeneratedValue 애너테이션의 strategy 애트리뷰트의 값을 GenerationType.SEQUENCE로 지정해주면 된다.
위의 코드와 같이 SEQUENCE 전략을 설정해주면,
@Configuration
public class JpaSequenceMappingConfig {
...
@Bean
public CommandLineRunner testJpaSingleMappingRunner(EntityManagerFactory emFactory){
...
return args -> {
tx.begin();
em.persist(new Member());
Member member = em.find(Member.class, 1L);
System.out.println("# memberId: " + member.getMemberId());
tx.commit();
};
}
}
역시 Member 엔티티에 따로 기본키 값을 할당해 주지 않아도 자동으로 키 값이 생성되는 것을 볼 수 있다.
하지만 IDENTITY 전략과는 다르게, DB가 시퀀스에서 기본키에 해당하는 값을 먼저 생성한 후에 엔티티가 Persistence Context에 저장되기 전에 시퀀스 값을 조회한다. 조회된 시퀀스 값은 Member 엔티티의 memberId 필드에 할당된다.
✅ AUTO 전략
기본키 자동생성 전략 - AUTO 를 설정하려면 @Id 필드의 @GeneratedValue 애너테이션의 strategy 애트리뷰트의 값을 GenerationType.AUTO로 지정해주면 된다.
AUTO로 지정될 경우 JPA가 데이터베이스의 Dialect에 따라서 적절한 전략을 자동으로 선택해준다.
Dialect(방언)
- 표준 SQL 등이 아닌 특정 DB에 특화된 고유한 기능
- JPA에서 제공하는 추상화된 클래스. 각 벤더에 맞는 구현체를 제공해준다.
- JPA에서 지원하는 표준 문법이 아닌 특정 DB에 특화된 기능을 사용할 경 Dialect가 처리해준다.
✔️Table 전략
- 모든 데이터베이스에서 사용가능하고, 키 생성 전용 테이블을 생성해서 키 값을 관리한다.
- 최적화되지 않은 테이블에서 키를 생성하기 때문에 문제가 발생할 수 있어 상용 서비스에서는 사용하지 않는 것이 좋다.
3️⃣ 필드(멤버변수) &컬럼 간의 매핑
✅ 엔티티 클래스 필드와 컬럼 간의 매핑
@NoArgsConstructor
@Getter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(nullable = false, updatable = false, unique = true)
private String email;
@Column(nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Transient
private String age;
...
}
@Column 애너테이션
- 클래스 필드와 DB 테이블 컬럼을 매핑해주는 애너테이션
- @Column 애너테이션 없이 필드만 있을 경우
- 기본적으로 JPA는 필드들이 테이블 컬럼과 매핑되는 필드라고 판단
- 기본적으로 애트리뷰트는 모드 디폴트 값 적용
@Column(nullable="", updatable="", unique="", name ="", length ="" , ...)
nullable
- 컬럼에 null값 할당 가능 여부 지정
- 디폴트는 true
- 필수항목인 필드에 false로 지정해주면 될 듯
updatable
- 컬럼 데이터 수정 가능 여부 지정
- 디폴트는 true
- ID같이 수정하면 안되는 컬럼에 false로 지정해주면 될 듯
unique
- 컬럼에 unique 제약 조건 지정
- 디폴트는 false
- 고유한 값이여야 할 경우 true로 지정해주면 될 듯
name
- 생략 시 엔티티 클래스 필드의 이름으로 컬럼 생성
- 지정 시 엔티티 클래스 필드와 다른 이름으로 컬럼 생성
추가적인 애트리뷰트 참고!
https://velog.io/@gillog/JPA-Column-Annotation
@Column 애너테이션이 생략될 경우 nullable=true인데,
필드의 데이터 타입이 int/long 같은 원시타입일 경우, Java는 원시타입에 null값이 들어가는 것을 허용하지 않기 때문에 에러가 발생한다. 따라서 @Column이 없거나 @Column의 애트리뷰트를 생략하려 할 경우 적어도 nullable=false를 설정해 주는 것이 에러방지에 도움이 될 것이다.
엔티티 클래스에서 발생한 예외는 API 계층의 DTO 클래스에서 발생하는 예외와 비슷하게 일종의 유효성 검증으로 인해 발생했다고 볼 수 있다. 엔티티 클래스에서 발생한 예외는 API 계층까지 전파되기 때문에, API 계층의 GlobalExceptionAdvice에서 catch한 후 처리할 수 있다.
@Column(nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
- java.util.Date, java.util.Calendar 타입으로 매핑하기 위해서는 @Temporal 애너테이션이 필요하지만, 타입이 LocalDateTime일 경우에는 생략가능하다.
- LocalDateTime 타입은 테이블의 TIMESTAMP 타입과 매핑된다.
@Transient 애너테이션
- JPA에게 테이블 컬럼과 애너테이션이 붙은 필드를 매핑하지 않겠다고 알리는 애너테이션
=> 데이터베이스에 저장되지 않고 조회할 때 매핑도 되지 않는다.
=> 주로 임시 데이터를 메모리에서 사용하기 위해 사용
@NoArgsConstructor
@Getter
@Setter
@Entity(name = "ORDERS")
public class Order {
...
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus = OrderStatus.ORDER_REQUEST;
...
public enum OrderStatus {
ORDER_REQUEST(1, "주문 요청"),
ORDER_CONFIRM(2, "주문 확정"),
ORDER_COMPLETE(3, "주문 완료"),
ORDER_CANCEL(4, "주문 취소");
@Getter
private int stepNum;
@Getter
private String step;
OrderStatus(int stepNumber, String stepDescription) {
this.stepNum = stepNum;
this.step = ;
}
}
}
@Enumerated 애너테이션
- enum 타입과 매핑할 때 사용되는 애너테이션
- 두 가지 타입으로 설정 가능
- EnumType.STRING: enum의 순서를 나타내는 숫자를 테이블에 저장
- EnumType.ORDINAL: enum의 이름을 테이블에 저장
EnumType.ORDINAL로 지정할 경우, enum의 순서가 바뀌게 된다면 그때부터 테이블의 enum 순서와 클래스에 정의되어 있는 enum 순서가 맞지 않을 수도 있기 때문에 EnumType.STRING 사용을 권장한다.
4️⃣ 엔티티 간의 연관관계 매핑
...
'🌿With Spring > JPA' 카테고리의 다른 글
한 엔티티의 PK를 2개의 FK로 가지는 클래스 (0) | 2023.08.04 |
---|---|
JPA 基本 (0) | 2022.12.03 |