@Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
따라서 @Entity가 붙지 않으면 JPA가 관리하지 않는, 내가 그냥 마음대로 사용하는 클래스이다.
JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수
주의
중요하지 않다.
JPA에서 사용할 엔티티 이름을 지정한다.
• 기본값: 클래스 이름을 그대로 사용(예: Member)
• 같은 클래스 이름이 없으면 가급적 기본값을 사용한다.
@Entity(name=" ")으로 이름 지정이 가능하다. 하지만 쓰지 않는다.
근데 다른 패키지에 같은 이름의 클래스가 있을 땐, 쓰기도 한다. jpa가 구분하는 이름이다.
@Table은 엔티티와 매핑할 테이블 지정
@Table 테이블을 다른 이름으로 매핑하고 싶어 축약으로 하고 싶을 때 @Table(name="MBR")
이런식이면 클래스 이름이 MEMBER여도 쿼리가 INSERT INTO MBR 같은 식으로 쿼리가 나간다.
JPA는 매핑정보(@Id,@Entity등)만 보면 어떤 쿼리를 만들어야할지 어떤 테이블인지 다 알 수 있다.
따라서 애플리케이션 로딩 시점에 DB테이블을 생성하는 기능도 제공한다. 물론 이 기능은 운영서버에선 사용하면 안되고, 혼자 로컬에서 개발할 때만 사용하도록 하자.
따라서 이 기능을 사용하면 애플리케이션 실행시점에서 테이블이 생성되게 해준다.
애플리케이션 로딩 시점에 CREATE문으로 일단 DB를 생성하고 시작할 수 있게 해준다.
장점 -> 원래는 테이블을 다 만들어놓고 객체개발 근데 JPA는 그럴 필요가 없다.
매핑을 다 해놓으면 애플리케이션 뜰 때 테이블을 다 만들어준다.
데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
오라클로 설정하면 varchar2로 생성
creat : 기존 테이블 삭제후 다시 생성 항상 생성전 drop table Member if exists
DB에서 따로 테이블 만들지 않아도 됨 그리고 테이블의 변경사항이 생겨도(필드 추가) 그냥 Member만 고치면 알아서 변경사항을 반영해 (필드가 추가됨) 생성된다.
create-drop : 보통 테스트 페이지에서 깔끔하게 다 날려버리고 싶을 때 사용
update : 테이블 만들 때 drop 안함 그래서 Member 테이블을 만들었다가 age필드를 추가하고 싶을 때, 기존 테이블을 유지한 상태로 테이블을 변경하고 싶다면 update 사용
대신 삭제가 안됨 age필드를 Member클래스에서 삭제해도 그대로 남아있는다.
validate : 엔티티와 테이블이 정상 매핑되었는지만 확인
만약 필드 이름 다르게 하고 돌리면 생성된 테이블과 다르기 때문에 오류난다.
운영장비에는 절대 사용 하지말아야한다.
개발 초기 -> create update 로컬과 개발 서버
테스트 서버 -> update validate -> 개발서버 개발자 여러명이 같이 쓰는 중간 서버
절대 create금지다. 다른 개발자가 쓰던 테이블의 데이터들 다 날아가기 때문.
스테이징과 운영 서버는 validate 또는 none :
사실 그냥 안쓰는게 제일 best다.
validate까진 괜찮다.
왜 쓰지말아야할까
개발서버는 update정도는 편하겠지만 결국 운영같은경우에는 몇 천만건씩 있는데 Alter같은 걸 잘못치게 되면 시스템이 중단상태가 되버린다.
애플리케이션 로딩 시점에 시스템이 알아서 alter를 쳐준다는것자체가 위험하다.
테스트 서버나 개발서버 전부 모두 본인이 직접싸서 쓰는 것을 추천한다.
@Table에서 이름을 name=""으로 바꾸는 것은 runtime에 영향을 줌 insert쿼리나 update쿼리등
DDL 생성 기능(유니크 제약 조건, 길이 제한 등)은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다
제약조건 추가: 회원 이름은 필수, 10자 초과X
@Column(nullable = false, length = 10)
유니크 제약조건 추가
@Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )})
DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.
insertable, updatable : 등록, 변경 가능 여부/ 변경이 불가능하게 하려면 기본이 true이기 때문에 false로 지정한다.
nullable : 기본이 true 따라서 false로 하면 notnull제약조건이 붙는다. 자주 사용한다.
unique : 잘 쓰지 않는다. alter로 unique제약조건을 걸어주긴 하는데 이름을 랜덤값으로 붙여주기 때문에 이름반영이 어렵다.
따라서 랜덤값으로 이름이 생성되는게 싫다면 @Table 어노테이션에 unique 제약조건을 걸어주면된다.
columnDefinition : 데이터베이스 컬럼 정보를 직접 줄 수 있음
precision : 아주 큰 숫자나 소수점을 사용할 때사용한다.
기본이 ORDINARY임 사용하지 않으므로 STRING으로 꼭 명시해줘야함
public enum RoleType {
USER, ADMIN
}
의 ENUM이 있을 때 ORDINARY를 사용하거나 명시하지 않는다면,
member.setRoleType(RoleType.ADMIN)을 하면 1이 저장된다. 순서가 저장되기 때문이다
마찬가지로 user로 set하고 persist, commit으로 db에 저장하면 0이 저장된다
그럼 왜 쓰면 안되나
위의 RoleType에 GUEST를 추가해다고 해보자.
public enum RoleType {
GUEST,USER, ADMIN
}
그리고 GUEST로 저장하면 이젠 GUEST가 0이다 .
하지만 DB를 확인해보면 예전에 저장되어있던 ADMIN-1, USER-0은 그대로이다. 이는 굉장한 문제를 야기한다.
날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용
참고: LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)
데이터베이스 BLOB, CLOB 타입과 매핑
• @Lob에는 지정할 수 있는 속성이 없다.
• 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
• CLOB: String, char[], java.sql.CLOB
• BLOB: byte[], java.sql. BLOB
@Lob
private String
같은 경우 데이터 베이스 생성 쿼리를 보면
description clob으로 clob타입으로 매핑된 것을 볼 수 있다.
필드 매핑X
• 데이터베이스에 저장X, 조회X
• 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용
• @Transient
private Integer temp;
@Id와 @GeneratedValue가 있다.
@id : 코드성 데이터를 조합해서 직접 id를 만들어서 할당할때 사용, 내가 직접 setting
RDB를 사용할 경우 보통 ORACLE의 경우 sequence , DB에서 자동으로 GENERATE 해주는 값
MYSQL 계열의 경우는 AUTO INCREMENT , 값을 NULL로 집어넣으면 값을 알아서 순차적으로 올라가도록 세팅해주는것이다.
이와 같이 값을 자동으로 할당해주는 방법 -> @GeneratedValue
GenerationType을 auto로 해놓으면 데이터베이스 방언을 알아서 처리해줌
auto는 방언에 맞춰 아래의 세가지 중에 한가지를 선택해줌
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한
데이터베이스 오브젝트(예: 오라클 시퀀스)
• 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
sequence : db에 있는 sequence 오브젝트를 통해서 값을 generating 하는 것
@Entity
@SequenceGenerator(
name = “MEMBER_SEQ_GENERATOR",
sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
increment든 sequence든 다 적용가능하지만, 성능이 좀 떨어진다.
운영에서 쓰기엔 좀 부담임
create table MY_SEQUENCES (
sequence_name varchar(255) not null,
next_val bigint,
primary key ( sequence_name )
)
@Entity
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = “MEMBER_SEQ", allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
initalValue와 allocationSize가 중요
기본 키 제약 조건에 대해 생각해보자. null 아니면서 유일하고 변하면 안된다.
변하면 안되는게 어려움 전체 수명 동안 변화하지 않는게 어렵고 이런것을 찾는 것 또한 어렵다.
자연키 : 비즈니스적으로 의미있는 키 - 주민번호 등
대리키를 사용하자. generatevalue
주민 등록 번호도 기본 키로 적절하지 않음 - 나라에서 주민 번호를 관리 하지 말라해서 봤더니 pk가 다 주민 번호 - pk를 주민번호로 쓰니 이 테이블을 사용하는 다른 모든 테이블에 영향이 감 왜냐면 거기 fk가 주민번호이기 때문.
따라서 변경하는데 어마무시한 비용이 든다
권장 : Long + 대체키 (uuid등) + 키 생성전략 사용
id에 값을 넣지 않고 db에 insert를 해야한다. db에서 null로 날아오면 값을 세팅한다.
뭐가 문제일까 ? id값을 알 수있는 시점이 db에 값이 세팅될때인 것이 문제가 된다.
영속성 컨텍스트에서 관리되려면 pk값이 있어야한다. 근데 db에 들어가야 pk를 알 수 있다.
영속 상태가 됐다는것은 1차 캐시에 존재한다는 뜻이다. 하지만 1차 캐시에 넣으려면 pk알아되는데 알 방법이 없는 것이다.
그래서 identity 전략에서만 예외적으로, persist를 호출한 시점에 바로 db에 insert쿼리를 날린다. 커밋 시점 아님 !
그래서 db에 값이 들어가면 jpa가 내부적으로 select해서 pk가져온다.
select 쿼리가 나가는게 보여야하는 거 아닌가 ? 내부적으로 짜져있기 때문에 insert하는 시점에 id를 바로 알 수 있다.
한 트랜잭션안에서 insert 쿼리가 네트워크를 여러번 탄다고 해서 성능에 그렇게 큰 영향을 끼치진 않는다.
sequence는 1부터 시작하고 1씩 올라간다.
create sequence MEMBER_SEQ start with 1 increment by 1
em.persist를 하면 영속 컨텍스트에 올라가야되는데 영속 컨텍스트에 가려면 pk가 있어야한다. 따라서 먼저 시퀀스를 가져와야한다.
그래서 MEMBER_SEQ에서 먼저 가져온다.
그래서 em.persist하면 call next value for MEMBER_SEQ 로그가 찍히는 걸 볼 수 있다.
MEMBER_SEQ 의 다음 값을 가져와 지금 가져와야할 PK값을 가져오는 것이다.
따라서 가져와서 영속성 컨텍스트에 넣는다.
쿼리는 안날아가고 SEQ값만 가져오는것이다.
따라서 SEQUENCE는 버퍼링 방법이 가능하다.
그러면 네트워크를 계속 왔다갔다 해야되지 않냐 성능상 좋지 않지 않냐 그냥 다녀올바엔 INSERT로 날리는게 낫지 않냐 라는 고민을 하게 될 수있다.
이때 우리가 사용할 수 있는것이 allocationSize=50
이다.
em.persist(memberA)
em.persist(memberB)
em.persist(memberC)
를 SEQUENCE 방식으로 한다고 가정하자.
그럼 persist할 때 마다 call next value를 해야하니 네트워크를 타야된다.
그래서 allocationSize=50를 이용해 50개를 미리 땡겨오는 것이다.
next value를 할 때 미리 50개 사이즈를 올려놓고, db에 미리 50개를 올려놓고 메모리에서 1씩 사용하고, 다쓰면 next call을 한번 호출 한다.
그럼 또 50개를 미리 올려놓는다. 이러면 이미 db엔 100번대가 되어있다. 그러면 나는 51번 부터 다시 메모리에서 1씩 사용한다.
여러 웹서버를 사용해도 동시성 이슈 없이 사용가능하다.
처음 allocationSize를 설정하고 persist를 호출하면 call next value가 두번 호출된다.
처음에는 50개씩 써야되는데 호출해봤더니 1인거
그래서 문제가 있나 하고 한번 더 호출 한거임 그래서 51이됨
한번은 그냥 더미로 호출한거임
em.persist(memberA)//1, 51 db
em.persist(memberB)//memory
em.persist(memberC)//memory
이렇게 되면 next call 2번 호출
이론적으론 allcationSize를 많이 크게 하면 좋은데 웹서버를 내릴때 이게 다 날아간다.
그럼 구멍이 생긴다. 구멍이 생겨도 괜찮긴 한데 낭비다
Table 전략도 마찬가지다.
미리 값을 올려두는 방식이기 때문에 웹서버 10대가 동시에 호출하면 값이 쭉올라감
자기가 미리 숫자를 확보하기 때문에 상관이 없음
동시성 문제 없음
id가 직접적으로 들어가있다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")//대문자 소문자는 회사의 RULE에 따라
private Long id;
private String name;
private String city;
private String street;
private String zipcode;
/**
* getter setter를 꼭 다 만들 필요는 없음
* getter는 가급적 만드는 게 좋고 setter는 고민을 좀 해봐야함
* setter는 유지보수 측면에서 별로이기 때문에 생성자를 통해 set하는 식으로
* @return
*/
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;
}
}
@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;
}
}
@Entity
@Table(name = "ORDERS") //ORDER로 했을 때 안되는 DB도 있음
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;
}
}
@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;
}
}
public enum OrderStatus {
ORDER, CANCEL
}
잘 보면 이상한게 있음
order에서
Order order = em.find(Order.class, 1L)
Long memberId= order.getMemberId();
Member member=em.find(Member.clas, memberId);
id가 1L인 상품을 주문한 member를 찾고 싶을 땐 위와 같이 해야한다.
하지만 이는 객체지향 스럽지 못하다.
객체 지향스러운 코드가 되려면
Member findMember = order.getMember()
가 되어야할 것이다.
왜냐면 객체지향에선 참조로 값들을 쭉쭉 찾아갈 수 잇어야하기 때문이다.
우리가 한 설계는 관계형 db에 맞춘 설계이다.
따라서 참조값을 가져와야되는데 테이블의 외래키를 객체에 그대로 가져온다.
id만 가지고 있어서 참조가 다 끊기고 있다.
예를 들어 order안에 memberid만 가지고 있는게 아니라 member가 있어야한다.