1. 객체와 테이블 매핑
1) @Entity
- @Entity 가 붙은 클래스는 JPA 가 관리하며 엔티티, 엔티티 클래스 라고 한다.
- JPA 를 사용해서 테이블과 매핑 할 클래스는 @Entity 가 필수이다
- 이때 기본 생성자는 필수!!! : 파라미터가 없는 pulbic 또는 protected 생성자가 필요하다 => JPA 에서 사용하는 객체를 프로싱하거나 하는 등등의 기술들이 있는데 이때문에 기본 생성자가 필수라고한다
- final 클래스, enum, interface, inner 클래스 사용X
- 저장할 필드에 final 사용 X
핵심 Point!!
DB 테이블과 매핑하기 위한 클래스 ( 보통 DTO ) 에 @Entity 어노테이션을 붙인다. 이때 PK 에 해당하는 변수에는 @Id 어노테이션을 붙여야 한다
2) @Table
- DB 테이블과 매핑 후 데이터를 가져와 저장하기 위한 클래스에 붙이는 어노테이션
- 이때 Entity 에 name 파라미터를 사용할 수 있는데 이 파라미터로 엔티티 이름을 지정할 수 있다 => 기본값은 클래스명
속성 | 기능 | 기본값 |
name | 매핑할 테이블 이름 | 엔티티 이름을 사용 |
catalog | DB catalog 매핑 | |
schema | DB schema 매핑 | |
uniqueConstraints | DDL 생성 시 유니크 제약 조건 생성 |
/*
* DB 테이블과 매핑하기 위한 클래스 ( 보통 DTO ) 에 @Entity 어노테이션을 붙인다
* 이때 PK 에 해당하는 변수에는 @Id 어노테이션을 붙여야 한다
*
* */
// DB 테이블과 매핑 후 데이터를 가져와 저장하기 위한 클래스에 붙이는 어노테이션
// 이때 Entity 에 name 파라미터를 사용할 수 있는데 이 파라미터로 엔티티 이름을 지정할 수 있다 => 기본값은 클래스명
@Entity(name="Member")
@Table(name="Member") // @Table 은 엔티티와 매핑할 테이블을 지정한다
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Member {
@Id // PK에 해당하는 변수
private Long id;
private String name;
2. DB 스키마 자동 생성 -> application.properties 혹은 xml 에서 설정
- DDL 을 애플리케이션 실행 시점에 자동 생성!! => 테이블 중심 -> 객체 중심
- 데이터베이스 방언을 활용해서 DB 에 맞는 적절한 DDL 을 생성 가능하다
- 단!! 이렇게 자동으로 생성된 DDL 은 개발 장비에서만 사용하는 것이 적절하다. DDL 정도는 직접 만들자
- DB 스키마 자동 생성은 application properties 또는 xml 에서 설정 가능하다. 이때 value 에 해당하는 속성값들은 create, create-drop, update, vaildate, none 까지 총 5가지가 있다
속성 값 | 설명 | 사용처 |
create | 기존 테이블 삭제 후 다시 생성 drop+create | 주로 개발 초기 단계 |
create-drop | create + 애플리케이션 종료 시 테이블 drop | 주로 개발 초기 단계 |
update | 변경된 부분만 반영 | 테스트 서버 |
vaildate | 엔티티와 테이블이 정상 매핑되었는지만 확인 => 만약 정상 매핑 아니라면 에러!! | 스테이징과 |
none | 사용하지 않음 | 스테이징과 운영서버(운영때는 그냥 얘만 사용하자) |
<!-- 데이터 베이스 스키마 자동생성 : value 속성 -->
<!--
create : 기존 테이블 삭제 후 다시 생성 drop + create
create-drop : create 와 같으나 애플리케이션 종료 시점에 테이블 drop
update : 변경된 부분만 반영 => 특히 운영 DB 에 사용하면 클남
vaildate : 엔티티와 테이블이 정상 매핑되었는지만 확인 => 매핑 안되면 에러남
none : 사용하지 않음
-->
<property name="hibernate.hbm2ddl.auto" value="create" />
3. 필드와 컬럼 매핑
- @Column 등 여러 어노테이션을 사용해서 테이블과 엔티티의 변수를 매핑시킬 수 있다
- 이 부분은 바로 코드로 알아보자. 공부하기 위한 요구사항은 아래와 같다
요구사항
1. 회원은 일반 회원과 관리자로 구분한다
2. 회원 가입일과 수정일이 있어야한다
3. 회원을 설명할 수 있는 필드가 있어야 한다. 해당 필드에는 길이 제한이 없다
@Entity(name="NMember")
@Table(name="Member")
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
@Id
private Long id; // pk
// 컬럼명과 변수명이 다를 경우 name 파라미터로 지정 가능
@Column(name="name", nullable = false)
private String userName; // 이름
private Integer age; // 나이
@Enumerated(EnumType.STRING) // Enum 타입의 값이 들어가는 경우 사용하는 어노테이션
private RoleType roleType; // 역할
// 날짜, 시간, 날짜+시간 의 정보를 매핑할때 사용하는 어노테이션
// 참고로 Enum 타입
@Temporal(TemporalType.TIMESTAMP)
private Date createDate; // 생성 날짜
// @Temporal 을 사용하거나 아래처럼 LocalDate, LocalDateTime 을 사용해도 무방하다
private LocalDateTime updateDate; // 수정 날짜
@Lob
// varchar 를 넘어서는 엄~~청나게 많은 데이터가 들어 갈 수 있는 타입
// String 에 달려있는 경우 CLOB
private String description; // 유저 설명
@Transient // DB 와 매핑하고 싶지 않은 엔티티 변수에 달림 => DB 매핑 X , 컬럼 생성 X
private String tmp;
}
4. 매핑 어노테이션과 column 어노테이션 정리
- 엔티티-DB 매핑에 사용되는 어노테이션과 column 에 사용되는 어노테이션을 정리해보자
1) 매핑 어노테이션
어노테이션 | 설명 |
@Column | 컬럼 매핑 |
@Temporal | 날짜 타입 매핑, 아래 둘 중 하나로 사용 가능하다 -> Date, TimeStamp - TemporalType ; 어노테이션 파라미터로 사용 - LocalDate, LocalDateTime : 변수 타입으로 사용 |
@Enumerated | enum 타입 매핑 - EnumType.ODINAL : enum 순서(int 타입)를 DB 에 저장 => 기본값, 쓰지말자 - EnumType.STRING : enum 이름(String 타입)을 DB 에 저장 |
@Lob | CLOB -> 문자 타입일 때 => String, char[] BLOB -> CLOB 에 해당하지 않는 경우 전체 |
@Transient | 특정 필드를 컬럼 매핑에서 빼고 싶을 때 - 주로 메모리 상에서만 사용하는 변수일때 사용 |
2) Column 어노테이션
속성 | 설명 | 기본값 |
name | 필드와 매핑할 테이블의 컬럼명 | |
insertable, updateable | 등록, 변경 가능 여부 -> false 인 경우 JPA 로는 등록, 변경이 불가능!! | true |
nullable(DDL) | null 값의 허용 여부 설정, false 로 하는 경우 DDL 생성 시 not null 제약 조건이 붙는다 | true |
unique(DDL) | @Table 의 uniqueConstraints 와 같지만 한 컬럼에 간단히 유니크 제약 조건을 걸 때 사용한다 | |
columnDefinition(DDL) | DB 칼럼 정보를 줄 수 있다 | 필드의 자바 타입과 방언 정보 사용!! |
length(DDL) | 문자 길이 제약 조건 -> String 타입에만 사용 | 255 |
precision, scale(DDL) | BigDecimal 타입에 사용!! => precision 은 소수점을 포함한 자리수 전체를, scale 는 소수 자리수를 의미한다. 참고로 double, float 타입에는 적용 XX, 아주 정밀한 소수를 다룰 때만 사용한다 |
5. 기본키 매핑 : Id && GeneratedValue
- 기본키를 숫자형으로 사용하는 경우 int 나 Integer 대신 Long 을 권장한다
- 기본키 매핑 방법 : @Id 만 사용
- @GeneratedValue 자동 생성 : @GeneratedValue 를 사용하면 PK 로 설정된 값을 자동으로 증가시켜야 하는 경우 사용한다 => Mysql 의 AUTO_INCREMENT 나 ORACLE 의 SEQUENCE 같은 느낌
- Auto_Increment 는 DB 에 insert SQL 을 실행한 이후에 ID 값을 알 수 있음
- @GeneratedValue 를 사용할 때 Strategy 파라미터를 사용하고 이에 해당하는 값을 넣어줘야 한다.
Strategy | 설명 |
AUTO | DB 에 맞게 알아서 자동으로 생성 - Oracle 이면 Sequence - mysql : auto_increment |
Indentity | - 기본키 생성을 DB 에 위임 => Auto_Increment 같은 느낌 - initialValue : DDL 생성 시에만 사용됨, 시퀸스 dDL 을 생성할 때 처음 시작하는 수를 지정 - allocationSize : 시퀸스 한 번 호출에 증가하는 수 - em.persist() 시점에 insert sql 을 실행하고 DB에서 식별자를 조회함 ===> sql 을 DB 에 넣고 나서 식별자 즉 PK 를 조회하기 때문에 아주 다른 애들과는 다르게 특이한 부분이 존재하는데 일반적으로 commit() 후 sql 이 DB 에 반영되는 것과는 다르게 얘만 persist 하는 순간 insert sql 을 실행함!!! |
strategy = Sequence 추가적으로 @SequenceGenerator 를 사용해서 시퀸스 이름, 값 등을 지정 가능 |
- name : 식별자 생성 이름 - 필수 - generate [시퀸스명] : 어떤 시퀸스를 사용할 것인지 지정 - sequenceName : DB 에 등록되어 잇는 시퀸스 이름 - initialValue : DDL 생성 시에만 사용됨, 시퀸스 dDL 을 생성할 때 처음 시작하는 수를 지정 - allocationSize : 시퀸스 한 번 호출에 증가하는 수 - catalog, schema : DB catalog, schema 이름 ---------------------------------------------------------------------------------------- - sequence 전략의 경우 em.persist() 하는 시점에 시퀸즈 전략인 것을 확인 한 후 DB 에 만들어진 시퀸스에서 "call next value for 시퀸스명" 해서 다음 시퀸스 값을 가져온 후 영속성 컨텍스트로 저장함 - call next for 시퀸스명 할때마다 네트워크로 요청하기 때문에 성능 상 이슈가 발 생할 수 있다. 이를 해결하기 위해서 allocationSize 의 기본값이 50 으로 지정된다. 이렇게 하면 하나의 엔티티를 persist 했을 때 DB 에서의 next value 값은 51로 되고, 애플리케이션 메모리에는 50개가 쌓여있게 된다. 이후 메모리에 쌓인 50개 안의 숫자를 먼저 PK 로 사용한다. ==> 쉽게 생각하자면 50개를 미리 가불해서 당겨온 후 가불해서 당겨온 값부터 먼저 PK 로 사용한다. 가불해서 가져왔기 때문에 51이 될 때까지 DB 에 next value 하지 않는것은 덤 |
Table | - 키 생성 전용 테이블을 하나 만들어서 사용!! => 마치 DB 시퀸스를 흉내내서 사용하는 전략 - 모든 DB 에 사용가능하나, 성능 이슈가 있다 |
// AUTO
@Entity(name="NMember")
@Table(name="Member")
public class Member {
@Id
// Auto 는 DB 에 맞춰서 자동으로
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; // pk
}
// Sequence
@Entity(name="NMember")
@Table(name="Member")
@SequenceGenerator(name = "member_seq_generator", sequenceName = "member_seq")
public class Member {
// SEQUENCE 를 사용하는 경우 SequenceGenerator 를 추가적으로 사용해서 시퀸스 제너레이터명과 시퀸스명을 지정 가능하다
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_seq_generator")
private Long seqId;
}
// Table
@TableGenerator(name = "member_tableseq_generator", table = "my_seq",
pkColumnName = "member_SEQ", allocationSize = 1)
public class Member {
// Table 전략은 시퀸스 전용 테이블을 만들어서 사용하는 것 -> DB 에서 사용하는 시퀸스 테이블을 흉내내는 전략
// 시퀸스 전략과 마찬가지로 제너레이터 파라미터에 TableGenerator name 을 사용한다
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.TABLE, generator = "member_tableseq_generator")
private Long tableId;
}
권장하는 식별자 전략!!
- 기본 키 제약 조건 : not null, unique, 변하면 안된다
- 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다
--> 권장 : Long 형 + 대체키 + 키 생성전략 사용
6. 실전 예제를 통해코드로 만들어보기
- Member, Orders, Order_tiem, item
1) MEMBER
package com.use.jpabasic.practice.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity(name = "ShopMember")
@Table
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {
@Id
@GeneratedValue // 전략 생략하면 AUTO
@Column(name="MEMBER_ID")
private Long id;
private String name;
private String city;
private String street;
private String zipcode;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}
2) ORDER
package com.use.jpabasic.practice.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "ORDERS")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@Column(name = "MEMBER_ID")
private Long memberId;
private LocalDateTime orderDate;
// 주문 상태는 Enum 으로
@Enumerated(EnumType.STRING)
private OrderStatus status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getMemberId() {
return memberId;
}
public void setMemberId(Long memberId) {
this.memberId = memberId;
}
public LocalDateTime getOrderDate() {
return orderDate;
}
public void setOrderDate(LocalDateTime orderDate) {
this.orderDate = orderDate;
}
}
3) ORDER_STATUS : Enum 클래스
package com.use.jpabasic.practice.domain;
public enum OrderStatus {
ORDER, CANCEL;
}
4) ORDER_ITEM
package com.use.jpabasic.practice.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Table
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class OrderItem {
@Id
@GeneratedValue
@Column(name = "ORDER_ITEM_ID")
private Long id;
@Column(name = "ORDER_ID")
private Long orderId;
@Column(name = "ITEM_ID")
private Long ItemId;
private int orderPrice;
private int count;
}
5) ITEM
package com.use.jpabasic.practice.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Table
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Item {
@Id
@GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
private int stockQuantity;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getStockQuantity() {
return stockQuantity;
}
public void setStockQuantity(int stockQuantity) {
this.stockQuantity = stockQuantity;
}
}
그런데 이렇게 설계하면 문제가 생긴다!!
예를 들어서 order_id 가 1 인 Member 를 가져오기 위해서는 아래와 같이 Order 코드를 고치거나 find 를 2번 실행해서 가져와야 한다.
// Order_id = 1 인 Member 를 가져오기 위해서
// 일단 Order_id = 1 인 Order 객체를 가져오고 나서
Order order = em.find(Order.class, 1L);
// 다시 Member_ID 에 맞는 member 를 꺼내와야 한다
Member member = em.find(Member.class, order.getMemberId());
--------- 혹은 아래처럼 Order 쪽에 코드를 추가하던가 -----------
private Member member;
public Member getMember(){
return this.member;
}
public void setMember(Member member){
this.member = member;
}
DB 데이터 중심 설계의 문제점
- 객체 설계를 테이블 설계에 맞춘 방식
- 테이블의 외래키를 객체에 그대로 가져옴 => 객체 그래프 탐색 불가능
- 참조가 없음으로 UML 도 잘못됨
'Java - SpringJPA' 카테고리의 다른 글
Spring JPA (6) - JPA 개념 잡기 : 엔티티 연관관계 매핑 1:N, N:1, N:M, 1:1 (1) | 2022.10.03 |
---|---|
Spring JPA (5) - JPA 개념 잡기 : 단방향 연관관계, 양방향 연관관계, 연관관계의 주인 (0) | 2022.10.03 |
Spring JPA (3) - JPA 개념 잡기 : 영속성 컨텍스트, 플러시, 준영속 상태 (0) | 2022.10.01 |
Spring JPA (2) - Entity 객체, JPA 로 DB 연결하기, 기본 문법 사용하기 (0) | 2022.09.29 |
Spring JPA (1) - JPA 기본 & JPA 프로젝트 생성 (0) | 2022.09.28 |
댓글