@Entity
@Entity 가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
- 기본 생성자 필수(파리미터가 없는 public 또는 protected 생성자)
- final 클래스, enum, interface, inner 클래스 사용 X
- 저장할 필드에 final 사용 X
예를 들어서 엔티티를 JPA 구현체가 생성할 때 리플렉션을 사용해서 객체를 먼저 생성하고 나중에 값을 필드에 직접 넣어주기도 하는데 final 이면 그게 불가능해진다.
속성: name
JPA 에서 사용할 엔티티 이름을 지정한다. 같은 클래스의 이름이 없으면 그대로 사용하자.
이러한 관계를 이루고있다.
데이터베이스 스키마 자동 생성
DDL(Data Definition Language)을 애플리케이션 실행 시점에 자동 생성 ,Create Table 등등
테이블 중심 -> 객체 중심
데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
이렇게 생성된 DDL은 개발된 장비에서만 사용해야함. 운영서버에서 사용하면 안된다. 적절히 다듬고 사용해야한다.
개발 초기 단계에는 create 또는 update 에서 쓰면 된다.
테스트 서버는 update 또는 validate 를 쓰면 된다. create 를 쓰면 안된다 데이터가 다 날라가기 때문.
스테이징과 운영 서버는 validate 또는 none
앵간하면 개발 서버 말고는 update를 쓰지 않는것을 권장한다. 시스템이 대신해서 alter를 해주는것은 매우 위험하다.
@Column(nullable=false,length=10) 이처럼 제약조건이 추가 가능
DDL 생성 기능은 DDL을 자동생성 할떄만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다. 예를들어
@Column(unique=true) 했을때 런타임 시점에서 테이블을 알맞게 생성하는것이아니라 그냥 나중에 alter table Member add constraint UK_~~~ unique (name) 이런식으로 쿼리를 날릴 뿐이다.
필드와 컬럼 매핑
package hellojpa;
import javax.persistence.*;
import java.util.Date;
@Entity
public class Member
{
@Id
private Long id;
private String name;
private Integer age;
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
@Transient
private int temp;
public Member()
{
}
}
TemproalType enum 클래스를 들어가보면 크게 3가지 로 구분이되어있다. DATE,TIME,TIMESTAMP 데이터베이스는 이렇게 날짜, 시간, 날짜+시간 으로 구분하기에 매핑을 위해서 java에서도 enum으로 3가지로 구분하고 있다.
Lob은 긴 문장을 넣을 떄 사용한다. @Transient를 쓰면 데비에 반영되지도 않고 메모리에서만 쓰겠다는 의도로 JPA가 알아듣는다.
Hibernate:
create table Member (
id bigint not null,
age integer,
createDate timestamp,
description clob,
lastModifiedDate timestamp,
name varchar(255),
roleType varchar(255),
primary key (id)
)
H2 에 맞는 설정으로 DDL 을 만들어서 날려준다.
unique 의 경우 위에서 봤드시 alter table Member add constraint 하고 알수없는 String이 나오기에 나중에 오류가 나왔을때 어떤 constraint 인지 알아볼수 없으므로 사용안한다. 대신
@Table(uniqueConstraints={@UniqueConstraint(name="Tony" ,columnNames = {"createDate","age"})} )
Hibernate:
alter table Member
add constraint Tony unique (createDate, age)
이런식으로 constraint의 이름까지 정해주고 mutiple column 에 유니크제약조건을 걸 수 있다.
기본값을 쓰면 숫자로 저장되어버린다. STRING을 써줘야 아래처럼 USER로 나오지 안그러면 해당 순서의 index가 나와버린다.
최신 버젼을 쓸때는 그냥
private LocalDate test1;
private LocalDateTime test2;
처럼 @Temporal annotation 이 필요없이 알아서 각각 test1 은 date로 test2 는 timestamp 로 변경해서 쿼리를 쏴준다.
@Lob의 경우 필드 타입이 문자면 CLOB, 나머지는 (byte등등 ) BLOB으로 매핑된다.
기본키 매핑
직접할당 = @Id만 사용
자동생성(@GeneratedValue (strategy =GenerationType.옵션)
- IDENTITIY = 데이터베이스에 위임 mysql
insert 할때 id설정을 해주지말아야 된다. DB에 들어갔을때 id가 결정됨으로 그전에는 알 수 가 없다. 따라서 JPA는 1차캐시에 저장할떄 필요한 ID값을 알 수 가 없다.
그래서 보통 쿼리를 commit 한 후에 날라가는데 IDENTITIY 로 한경우는 persist한 시점에서 쿼리가 날라가버린다.
그 다음 JPA가 내부적으로 DB에 생성된 ID값을 가지고 온다.
이때 select쿼리는 나가지 않고 jdbc 내부적으로 insert문이 나갈때 pk만 return 해주는 api를 사용한다.
이렇게 하면 DDL 에서 id varchar(255) not null auto_increment 이런식으로 쿼리를 쏜다. 기본적으로 DB로 하여금 알아서 하라는 것이다.
- SEQUENCE = 데이터베이스 시퀀스 오브젝트 사용, ORACLE (@SequenceGenerator) 필요
sequence 오브젝트를 만들어 내서 값을 가져와서 세팅해준다. JPA가 sequence에서 PK값을 얻어온후에 persist()를 통해서 영속성 컨텍스트에 딱 저장을 해준다.
@Entity
@SequenceGenerator(name="member_seq_generator", sequenceName = "member_seq")
public class Member
{
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "member_seq_generator")
private Long id;
private String name;
}
oracle,h2 의 경우 sequence table을 만들고 싶을 때 sequenceName을 주어 만들 수 있다.
sequence 도 마찬가지로 db에 들어갔다 오기전까지는 primary key를 알 수 가 없다.
allocationSize는 매번 영속성 컨텍스트에 1차 캐시에 등록을 하기위해 PK값을 조회하러 DB까지 네트워크를 타고 sequence를 조회하는것은 낭비라 생각됨으로 한번에 50개를 DB에 올려놓고 JPA는 야금야금 써먹는것이다. 그러면 네트워크 리소스를 절약할 수 있다.
2번 호출하는것을 확인할 있다. 첫번째는 1을 맞추기위해 2번째는 50까지 가지고 오기위해
너무 크게 하면 나중에 웹서버를 내릴떄 그사이에 빈 숫자구멍이 생긴다. 이것은 나중에 rollback이 일어나도 시퀀스는 rollback이 일어나지 않는다는의미이다. 구멍이 있어야 그만큼 빈 곳을 인지할수 있기 때문이다.
- TABLE =키 생성용 테이블 사용, 모든 DB에서 사용 (@TableGenerator) 필요
- AUTO = 방언에 따라서 자동 지정 , 기본값
권장하는 방법 = Long형 + 데체키 (Seqeuence,UUID) + 키 생성전략 사용 = Auto increment, sequence object, UUID ,회사내의 룰 , 절대 비즈니스를 키로 끌고오는것은 권장하지 않는다.
실전 예제 1 - 요구사항 분석과 기본 매핑
package jpabook.jpashop.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
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;
}
}
package jpabook.jpashop.domain;
import javax.persistence.*;
@Entity
public class Member
{
@Id @GeneratedValue
@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;
}
}
package jpabook.jpashop.domain;
import javax.annotation.processing.Generated;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "ORDERS")
public class Order
{
@Id
@GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@Column(name="MEMBER_ID")
private Long memberId;
private LocalDateTime orderDate;
@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;
}
public OrderStatus getStatus()
{
return status;
}
public void setStatus(OrderStatus status)
{
this.status = status;
}
}
db 마다 order가 예약어로 걸려있는 경우가 있다. 그래서 따로 tablename 을 orders 로 한 것이다.
package jpabook.jpashop.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
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;
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public Long getOrderId()
{
return orderId;
}
public void setOrderId(Long orderId)
{
this.orderId = orderId;
}
public Long getItemId()
{
return itemId;
}
public void setItemId(Long itemId)
{
this.itemId = itemId;
}
public int getOrderPrice()
{
return orderPrice;
}
public void setOrderPrice(int orderPrice)
{
this.orderPrice = orderPrice;
}
public int getCount()
{
return count;
}
public void setCount(int count)
{
this.count = count;
}
}
package jpabook.jpashop.domain;
public enum OrderStatus
{
ORDER, CANCEL
}
@Column 내용을 통해 명시적으로 굳이 DB table을 까보지 않아도 매핑관계를 확인할수있다. 성향에따라 보통 달라진다고 그랬다. 명시적으로 적는것이 편해보인다. Index도 Entity위에 명시함으로서 쉽게 확인가능하다.
하지만 객체지향적이지 않다. 예를들어 Order 엔티티에서 주문한 Member를 조회하려면 id를 조회하고 또다시 그아아디로 Meber 를 조회해야한다. getMember같은것으로 바로 조회하기를 원한다.
spring boot에서는 orderDate 같은 관례를 ORDER_DATE , order_date 로 overrie할수있다. 기본설정이 camle을 underscore 소문자로 설정이 되어있다.
'WEB > JPA' 카테고리의 다른 글
김영한 (ORM 표준 JPA 프로그래밍 6) 다양한 연관관계 매핑 (0) | 2021.03.08 |
---|---|
김영한 (ORM 표준 JPA 프로그래밍 5) 연관관계 매핑 기초 (0) | 2021.03.07 |
김영한 (ORM 표준 JPA 프로그래밍 3) 영속성 관리 - 내부 동작방식 (0) | 2021.02.25 |
김영한 (ORM 표준 JPA 프로그래밍 2) JPA 시작하기 (0) | 2021.02.25 |
김영한 (ORM 표준 JPA 프로그래밍 1) (0) | 2021.02.24 |