이번에는 바로 객체 관계형 DB 매핑에 대해 알아볼 것!
객체와 관계형 DB 매핑은 설계적인 것, 정적인 내용에 해당된다.
JPA는 사실 매핑 정보만 보면, 어떤 쿼리를 만들어야하는지, 어떤 테이블이 있는지 다 알 수 있다. 따라서 JPA에서는 아예 애플리케이션 로딩 시점에 DB 테이블을 생성하는 기능도 제공해준다.
물론 운영환경에서 절대 사용하면 안되고, 개발 환경이나 로컬 환경에서 도움된다.
운영장비에는 절대 create, create-drop, update 사용하면 안된다!!
결론은 로컬 PC에서는 자유롭게 쓰되, 여러 명이 쓰는 개발 서버나 운영 서버에서는 사용하지 말아라
로컬 PC에서 나온 DDL 스크립트를 다듬어서 운영 서버에 적용하면 된다.
물론 일일이 꼼꼼히 따져봐야한다!
제약조건 추가 : 예) 회원 이름은 필수, 10자 초과 X
@Column(nullable = false, length = 10)
유니크 제약조건 추가
@Table(uniqueConstraints = {@UniqueConstraint(name ="NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"})})
DDL 생성기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.
왜냐하면 제약조건 추가하는 것은 DB에 영향주는 것이지 애플리케이션 실행에는 영향주는 것이 아니기때문
테이블 명, 컬럼명 바꾸는 것은 INSERT 쿼리 날리는 것이 바뀌기 때문에 JPA 실행 로직에 영향을 주지만, 제약 조건 추가는 그냥 alter 문 하나 날리는게 끝이고 애플리케이션 실행에는 영향을 주지 않는다.
자바 enum 타입 매핑시 사용 (DB에는 enum 타입이 없으므로 매핑해줘야함)
주의! ORDINAL 사용하지 않는다!
날짜 타입 매핑에 사용 (java.util.Date, java.util.Calendar)
LocalDate, LocalDateTime 사용시에는 생략가능 (최신 하이버네이트 지원)
데이터베이스의 BLOB과 CLOB 타입 매핑
기본키 자동 생성 전략은 결과적으로 3가지 전략이 있다.
create sequence로 데이터베이스에 sequence 오브젝트 생성한다. 시퀀스 오브젝트 1부터 시작하고 1씩 증가.
sequence object를 통해 값을 가져온다음 insert쿼리에 해당 값을 세팅해서 날린다.
into Member(name, id) values (?,?)
에서 value(?,여기)
에 시퀀스 오브젝트에서 가져온 값이 세팅되서 날라간다
위 사진에서는 따로 이름을 주지 않았기 때문에, 하이버네이트가 만드는 기본 시퀀스를 사용한다.
그런데 테이블 마다 시퀀스 따로 관리하고 싶다. 그렇다면 @SequenceGenerator라는 것을 통해 시퀀스 오브젝트랑 매핑하면 된다.
@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;
}
위의 결과로 DB에 MEMBER_SEQ라는 시퀀스 오브젝트가 생성된다.
name은 Id필드에 어떤 시퀀스 오브젝트를 시퀀스 제네레이터로 사용할지 매핑하기 위해 필요한 속성
아래 사진으로 확인
Sequence 전략의 경우 DB에 엔티티 insert 하려면 키 값이 필요함
따라서 시퀀스 오브젝트에서 먼저 키 값을 조회한 후, 해당 키 값으로 세팅해서 영속성 컨텍스트에 저장한다. (따라서 em.persist() 시점에는 아직 쿼리 안날라감)
트랜잭션이 커밋되는 시점에 쓰기지연SQL 저장소에 저장되있는 insert 쿼리 날린다. (버퍼링 가능)
주의 : allocationSize 기본값 = 50
Sequence 전략 사용하면, 매번 키 값 세팅하기 위해서 DB에서 Select 쿼리 날려야하는데 (next call로 하나씩 가져오면), 그러면 네트워크를 계속 타야하니 성능상 문제있지 않나?
그래서 JPA에서는 allocationSize를 미리 50개 올려놓고, 메모리에서 1씩 사용한다. (매번 1씩 안올려줘도 된다.) 그러다가 50개 올려놓은 것에 도착하면, 또 next call 한번 호출해준다.
이게 기가막힌다! 여러 웹서버 올려놔도 동시성 문제없이 잘 동작한다.
▶ 시작은 1 증가는 50씩
저렇게 세팅하고 확인해보면 현재 시퀀스 값이 -49이다. 왜 저렇게 되있냐면 다음 번 호출때 50증가하면 1 가지도록 하기 위해서
call next value 해주면 50 증가해서 현재값 1가진다.
해당 코드 실행하면 call next value가 2번 호출된다.
성능 최적화 하기 위해서 allocationSize = 50으로 하는 건데, 50개씩 메모리에 쓰려고 했는데 처음 호출하면 값이 1이니까 한번 더 호출해서 51까지 늘려준다.
DB에는 51개까지 늘려주고, 애플리케이션 메모리에서는 1부터 차례대로 증가시킨다.
맨 처음에만 DB 호출해주고, 이후에는 51되기 전까지 메모리에서 호출한다.
1,2,3 다 1차 캐시에 등록됐는데 DB에서 call next value는 똑같이 2번 호출됐다.
거의 시퀀스와 비슷하게 쓴다.
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) //PK컬럼 이름
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
}
위의 쿼리 날리고 종료 (시퀀스 테이블에서 next value select해오고, 시퀀스 테이블 업데이트하고, 가져온 키값으로 member insert)
위에서 생성된 테이블 보면 이해된다.
MEMBER_SEQ의 경우 다음 키 값이 1인 상태이다.
또 다른 테이블의 SEQ 생성하고 싶다면 시퀀스 네임을 USER_SEQ 이런식으로 줘서 사용하면 된다.
하지만 운영에서 TABLE 전략 사용하기 부담스러움
기존 DB에서 관례로 사용하던게 있기 때문에, 기존 DB 전략 사용을 권장
SEQUENCE 전략과 마찬가지로 initialValue와 allocationSize가 있다. 최적화 가능하다.
서버 여러 대여도 이런 식의 성능 최적화 문제없나??
문제 없다! 왜냐면 DB에 미리 값을 올려두는 방식이기 때문이다. 동시에 호출되더라도 각자 숫자 확보하고 값만 쭉 올라간다.
- 위의 두 전략(SEQUENCE, TABLE) 같은 경우, DB에서 다음 키 값을 가져온 후에 INSERT를 한다.
- 하지만 IDENTITY 전략은 null로 먼저 INSERT를 해야 다음 키 값을 알 수 있다.
결론 : SQL 버퍼링 (모아서 INSERT)하는 것이 IDENTITY 전략에서는 불가능하다.
해당 게시글은 인프런 김영한님의 <자바 ORM 표준 JPA 프로그래밍 - 기본편>을 듣고 정리한 내용입니다.