1. 객체와 테이블 매핑: @Entity
, @Table
2. 필드와 컬럼 매핑: @Column
3. 기본 키 매핑: @Id
4. 연관관계 매핑: @ManyToOne
, @JoinColumn
@Entity
가 붙은 클래스는 JPA가 관리하고 이를엔티티
라 한다.- JPA를 사용해서 테이블과 매핑할 클래스 👉
@Entity
필수- 📌주의점
- 기본 생성자는 필수로 존재해야 한다 (파라미터가 없는 public 또는 protected 생성자)
- final 클래스, enum, interface, inner 클래스는
@Entity
로 매핑할 수 없다- 데이터베이스에 저장할 필드에 final 사용하면 안된다
JPA에서 사용할 엔티티 이름을 지정한다.
@Entity(name = "Member") // JPA가 관리하게 된다.
public class Member {
…
}
- 속성: name
- 기본값으로 클래스 이름을 그대로 사용한다.(예: Member)
- 같은 클래스 이름이 없으면 가급적 기본값을 사용한다.
@Table은 엔티티와 매핑할 테이블 지정한다.
@Entity// JPA가 관리하게 된다.
@Table(name = "MBR") // MBR이라는 테이블과 매핑한다.
public class Member {
…
}
MBR
테이블을 사용하는 쿼리가 나간다.
JPA는 어플리케이션 로딩 시점에 데이터베이스 Table을 생성하는 기능을 지원한다.
- 애플리케이션 실행(로딩) 시점에 DDL을 통해 테이블을 자동 생성한다
- 테이블 중심 👉 객체 중심
- 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성한다.
- 이렇게 생성된 DDL은 꼭 개발 장비에서만 사용해야 한다.
- 생성된 DDL은 운영서버에 적용할 때 바로 사용하지 말고 적절히 다듬은 후 사용할 것을 권장한다.
persistence.xml
파일에서 속성을 설정할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="hello">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<!-- 옵션 -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create" /> // 이 부분
<!-- <property name="hibernate.jdbc.batch_size" value="10"/>-->
</properties>
</persistence-unit>
</persistence>
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/test
username: sa
password:
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
ddl-auto: create
show_sql: true
format_sql: true
use_sql_comments: true
show-sql: true
generate-ddl: true
open-in-view: false
<property name="hibernate.hbm2ddl.auto" value="create" />
@Entity// JPA가 관리하게 된다.
public class Member {
@Id
private Long id;
private String name;
public Member() {
}
public Member(Long id, String name) {
this.id = id;
this.name = name;
}
}
drop table Member if exists
: Member라는 테이블이 존재하면 drop한다create table Member ..
: Member라는 테이블을 생성한다.
👉 어플리케이션 로딩 시점에@Entity
가 매핑된 클래스에 맞는 테이블을 생성한다.
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
create와 같지만 어플리케이션 종료 시점에 테이블을
drop
한다.
👉 테스트케이스 실행시킬 때 사용된다.
<property name="hibernate.hbm2ddl.auto" value="update" />
Member 테이블은 처음에
id
,name
만 존재한다.
👉 이후에 age
속성을 추가한다.
drop table Member
하지 않고alter table Member
로 추가된 속성을 반영한다.
만약 age
속성을 다시 지운다면 어떻게 될까 ❓
👉 아무 일도 일어나지 않는다. (속성 추가만 반영된다)
<property name="hibernate.hbm2ddl.auto" value="validate" />
@Entity// JPA가 관리하게 된다.
public class Member {
@Id
private Long id;
private String name;
private int age2; // Member 테이블에 없는 속성
public Member() {
}
public Member(Long id, String name) {
this.id = id;
this.name = name;
}
}
private int age2;
에 매핑된 속성이 없다고 알려준다.
👉 엔티티와 테이블이 정상 매핑되었는지 확인할 때 사용한다.
H2의 기본 문자 자료형은
varchar
이다.
오라클의 기본 문자 자료형은
varchar2
이다.
- 📌운영 장비에는 절대
create, create-drop, update
사용하면 안된다. (큰일난다)- 개발 초기 단계는
create
또는update
를 사용한다.- 테스트 서버는
update
또는validate
를 사용한다.
- 여러 개발자가 함께 테스트한다.
create
를 사용하면 데이터가 날아가기 때문에 사용을 권장하지 않는다.- 스테이징과 운영 서버는
validate
또는none
를 사용한다.
👉 그냥 로컬 PC에서만 사용해라.
회원 이름 속성이 필수인 경우 nullable = false
,
길이를 10자 미만으로 설정할 경우 length = 10
👉 @Column(nullable = false, length = 10)
@Entity// JPA가 관리하게 된다.
public class Member {
@Id
private Long id;
@Column(unique = true, length = 10)
private String name;
...
}
- length = 10 제약조건으로
👉name varchar(10)
- unique = true 제약조건으로
👉add constraint UK_ektea7vp6e3low620iewuxhlq unique (name)
✍유니크 제약조건 추가
👉 @Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )})
DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.
@Entity
public class Member {
@Id
private Long id;
@Column(name = "name")
private String username; // 객체 이름은 username, DB 컬럼 이름은 name
private Integer age;
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob // varchar보다 큰 사이즈의 데이터 타입
private String description;
public Member() {
}
}
속성값에
@Transient
어노테이션을 설정하면 DB에 생성되지 않는다. (메모리에서만 사용하는 변수가 됨)
unique 제약조건
:@Column
보다@Table
에서 사용하는 것을 권장
자바 enum 타입을 매핑할 때 사용하는 어노테이션이다.
기본 값이
EnumType.ORDINAL
이지만 절대 사용하면 안된다.
👉EnumType.STRING
을 사용할 것 !
- enum의 순서가 변경되면 꼬이게 된다.
- 📌
@Enumerated(EnumType.STRING)
필수
날짜 타입 (java.util.Date, java.util.Calendar) 을 매핑할 때 사용하는 어노테이션이다.
- Java8 이후의
LocalDate (연도, 월)
,LocalDateTime (연도, 월, 일)
을 사용할 때는 생략 가능 (최신 하이버네이트에서 지원)
데이터베이스 BLOB, CLOB 타입과 매핑하는 어노테이션이다.
@Lob
에는 지정할 수 있는 속성이 없다.- 매핑하는 필드 타입이 문자면
CLOB
매핑, 나머지는BLOB
매핑
CLOB
: String, char[], java.sql.CLOBBLOB
: byte[], java.sql. BLOB
필드 매핑을 사용하지 않는 변수에 사용하는 어노테이션이다.
- 데이터베이스에 저장, 조회가 안된다.
- 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용한다.
사용할 수 있는 어노테이션은
@Id
,@GeneratedValue
이다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
직접 할당
:@Id
만 사용
👉 개발자가 테이블의 pk를 설정할 경우@Id
를 사용한다.자동 생성
:@GeneratedValue
👉 데이터베이스가 pk 값을 자동으로 할당할 경우@GeneratedValue
를 사용한다.
@GeneratedValue(strategy = GenerationType.IDENTITY)
- 데이터베이스에 위임
- MYSQL
@GeneratedValue(strategy = GenerationType.SEQUENCE)
- 데이터베이스 시퀀스 오브젝트 사용
- ORACLE
- @SequenceGenerator 필요
@GeneratedValue(strategy = GenerationType.TABLE)
- 키 생성용 테이블 사용, 모든 DB에서 사용
- @TableGenerator 필요
@GeneratedValue(strategy = GenerationType.AUTO)
- 방언에 따라
IDENTITY, SEQUENCE, TABLE
중 하나로 자동 지정- 기본
- 기본 키 생성을 데이터베이스에 위임
- 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
- 예: MySQL의 AUTO_ INCREMENT
- JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
- AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
- IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
H2 방언의 경우 (H2Dialect)
MySQL 방언의 경우 (MySQL5Dialect)
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // id에 값을 넣으면 안되는 전략
private Long id;
@Column(name = "name", nullable = false)
private String username; // 객체 이름은 username, DB 컬럼 이름은 name
public Member() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
- pk값이 IDENTITY 전략을 따르면 값을 넣어줄 수 없다.
- 👉 pk값이 null로 데이터베이스에 날라가면
(null, ?)
insert 쿼리를 실행할 때 pk값이 설정된다- 👉 데이터베이스에 insert되기 전까지 pk값을 모른다.
- 영속성 컨택스트에 관리되기 위해서 무조건 pk값이 있어야 한다. (1차 캐시는 pk가 key, 데이터가 value인 테이블)
- 📌하지만 IDENTITY 전략을 따르게 되면 데이터베이스에 insert되기 전까지 pk값을 알지 못한다.
- IDENTITY 전략을 따를 경우에만
em.persist()
시점에 INSERT 쿼리를 실행한뒤 데이터베이스에서 pk값을 조회한다.
- JPA는 보통
tx.commit()
시점에서 INSERT 쿼리를 실행한다.
IDENTITY 전략에서 쓰기 지연 SQL 저장소
를 통해 INSERT 쿼리를 날리는 것은 불가능하다.
- 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트 (예: 오라클 시퀀스)
- 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE) // id에 값을 넣으면 안되는 전략
private Long id;
@Column(name = "name", nullable = false)
private String username; // 객체 이름은 username, DB 컬럼 이름은 name
public Member() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
create sequence hibernate_sequence start with 1 increment by 1
: 데이터베이스에서 값을 생성하는 시퀀스 객체를 만든다call next value for hibernate_sequence
: 시퀀스 객체를 통해 값을 가져온 뒤, 가져온 값으로 id를 세팅한다.
테이블 마다 시퀀스를 따로 관리하기 위해서
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;
@Column(name = "name", nullable = false)
private String username; // 객체 이름은 username, DB 컬럼 이름은 name
public Member() {
}
}
sequenceName = "MEMBER_SEQ"
: MEMBER_SEQ 라는 시퀀스가 생긴다generator = "MEMBER_SEQ_GENERATOR"
: 원하는 SequenceGenerator를 MEMBER_SEQ_GENERATOR로 매핑한다.
create sequence MEMBER_SEQ start with 1 increment by 1
- 시퀀스 MEMBER_SEQ를 1부터 시작하고 1씩 증가시킨다.
- 영속성 컨택스트에 넣기 위해 pk값을 반드시 알아야 한다.
- SEQUENCE 전략에서는 시퀀스(MEMBER_SEQ)에서 pk값을 가져와야 한다.
- 그래서
call next value for MEMBER_SEQ
가 실행된다.- 즉, 데이터베이스의 MEMBER_SEQ 시퀀스에서 pk값을 얻어와서 member 객체의 id 속성에 넣어준 뒤 영속성 컨택스트에 저장한다.
- 데이터베이스에 INSERT 쿼리가 실행되지 않고
tx.commit()
시점에서 실행된다.
SEQUENCE전략에서 쓰기 지연 SQL 저장소
를 통해 INSERT 쿼리를 날리는 것이 가능하다.
call next value for MEMBER_SEQ
를 계속 실행하는 것 보다SequenceGenerator의
allocationSize
속성을 통해 성능 최적화가 가능하다.
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 50)
public class Member {
@Id // 기본 키 매핑 어노테이션
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
@Column(name = "name", nullable = false)
private String username; // 객체 이름은 username, DB 컬럼 이름은 name
public Member() {
}
}
allocationSize = 50
으로 미리 데이터베이스의 시퀀스 값을 50개를 증가시켜둔 다음 (50)
메모리에서 1씩 증가시킨다.
메모리의 값이 50이 되면 그때서야call next value for MEMBER_SEQ
을 호출하여
데이터베이스의 시퀀스 값 50개를 증가시킨다 (100)
👉 데이터베이스에서call next value for MEMBER_SEQ
가 실행될 때마다 시퀀스 값이 50개씩 증가한다.
- 처음에는
DB의 MEMBER_SEQ 값 = 1
이다.
- 어플리케이션 메모리에서 시퀀스 값을 50개씩 사용해야하는데
call next value for MEMBER_SEQ
을 실행하니 DB의 시퀀스 값이 1이다- 그래서 한번 더 호출하여 DB의 시퀀스 값을 51까지 늘려준다.
em.persist(member1);
: DB의 MEMBER_SEQ 값 =51
, 어플리케이션 메모리에서 시퀀스 값은1
이다em.persist(member2);
: DB의 MEMBER_SEQ 값 =51
, 어플리케이션 메모리에서 시퀀스 값은2
이다em.persist(member3);
: DB의 MEMBER_SEQ 값 =51
, 어플리케이션 메모리에서 시퀀스 값은3
이다
- 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
- 장점: 모든 데이터베이스에 적용 가능
- 단점: 성능
@Entity
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCE", //매핑할 데이터베이스 시퀀스 이름
pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
public class Member {
@Id // 기본 키 매핑 어노테이션
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
@Column(name = "name", nullable = false)
private String username; // 객체 이름은 username, DB 컬럼 이름은 name
public Member() {
}
}
- @TableGenerator의
table = "MY_SEQUENCE"
으로MY_SEQUENCE
라는 테이블이 만들어진다.
pkColumnValue = "MEMBER_SEQ", allocationSize = 1
으로MY_SEQUENCE
테이블에 값이 채워진다.
Member
테이블의Id=1
값이MY_SEQUENCE
테이블로부터 설정된 것
allocationSize 속성을 설정하면 SQUENCE 전략과 동일한 방식으로 사용된다.
기본 키 제약 조건
: null이면 안되고 (NOT NULL), 유일해야 하고 (UNIQUE), 값이 변하면 안된다.
- 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 다시말해, 값이 변하지 않는 자연키를 찾기 어렵다
- 예를 들어 주민등록번호도 기본 키로 적절하기 않다.
- 👉 대신 대리키(대체키)를 사용하자.
자연키
: 비즈니스적으로 의미있는 키대체키
: 비즈니스적으로 의미 없는 키권장
:Long 형 + 대체키 + 키
생성전략 사용