4.1 객체와 테이블 매핑
@Entity
- @Entity가 붙은 클래스는JPA가 관리한다. "엔티티"
- JPA를 사용해서 테이블과 매핑할 클래스는 @Entity가 필수!
- 주의 사항
- 파라미터 없는 기본 생성자가 필요하다. (public or protected)
- fianl, enum, interface, inner 클래스에는 @Entity를 사용할 수 없다.
- DB에 저장할 필드에는 final을 사용하지 않는다.
- 클래스 이름을 그대로 테이블로 사용한다. (기본값)
4.2 데이터베이스 스키마 자동 생성
데이터베이스 스키마 자동 생성
- DDL을 애플리케이션 실행 시점에 자동으로 생성한다. CREATE
- 테이블 중심 => 객체 중심
- 데이터베이스 dialect를 활용해서 데이터베이스에 맞는 적절한 DDL을 생성한다.
- ex) 가변 문자열의 경우: Oracle(varchar2), MySQL(varchar) 다르게 생성한다.
- 생성된 DDL은 개발 장비에서만 사용해야하며, 운영 서버에서는 사용하지 않아야한다. 또는 적절히 다듬은 후에 사용
Ex) 자동 생성
- properties에 다음과 같이 속성을 추가한다.
- value에 "create-drop"를 넣으면 애플리케이션 종료 시점에 테이블을 지운다.
<property name="hibernate.hbm2ddl.auto" value="create" />
- 멤버 클래스에 테이블 이름을 지정해서 엔티티 추가
<hide/>
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "MBR")
public class Member {
@Id
private Long id;
private String name;
public Member() {
}
public Member(Long id, String name) {
this.id = id;
this.name = name;
}
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;
}
}
Note) 실행 결과
- 기존에 있다면 테이블을 drop을 먼저 실행한다.
- 애플리케이션 로딩 시점에 entity가 붙은 클래스에 대해 모두 테이블을 만든다.
- H2에도 확인 가능
DDL(Data Definition Language) 자동 생성 옵션
- create: 기존 테이블 삭제 후 다시 생성한다. (drop + create)
- create-drop: create와 같으나 종료 시점에 테이블을 drop해버린다. 테스트 케이스를 날리는 경우에 주로 사용한다.
- update: 변경분만 반영한다. (운영 DB에는 사용하면 안된다.)
- validate: 엔티티와 테이블이 정상 매핑되었는지 확인만 한다.
- none: 사용하지 않는다.
Ex) create-drop
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
// 영속 상태
Member member = em.find(Member.class, 150L);
member.setName("AAAAA");
System.out.println("_=========");
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- drop => create => drop
Ex) update
- age를 추가하고 싶은데 drop은 하지 않고 alter만 하고 싶은 경우
- Member 클래스에도 private int age를 추가한다.
<property name="hibernate.hbm2ddl.auto" value="update" />
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
// 영속 상태
Member member = em.find(Member.class, 150L);
member.setName("AAAAA");
System.out.println("=========");
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
==================== 오류 화면 ========================
- 오류: alter가 나오지 않는다.
- 원인: 다시 create를 했다가 Member클래스에 age 컬럼을 추가하고 update, 다시 매인 클래스를 실행하니까 정상 실행됐다.
- 오류 화면
- alter table이 안 나옴
- 강사님 화면 - "alter table Member"... 가 나온다.
Note) 정상 실행 화면
- 위 상태에서 int age 컬럼을 지우면 ?
- update 쿼리가 날라가지 않는다.
- 새로운 컬럼이 생긴 다음에 update 하면 쿼리가 생기는데 컬럼을 지운 다음에 실행하면 update쿼리가 실행되지 않는다.
Ex) validate - 엔티티가 테이블에 정상 매핑되었는지 확인한다.
- properties에 "validate"를 추가한다.
<property name="hibernate.hbm2ddl.auto" value="validate" />
- 멤버 클래스에도 임시로 변수 추가
private int gogo;
Note) 실행 결과 - 에러 나는 것이 정상이다. SchemaManagementException
- 테이블에 gogo 라는 필드가 없기 때문에 다음과 오류가 난다.
※주의 사항※
- 운영 장비에는 절대로 create, create-drop, update를 사용하면 안된다.
- update는잘못했다가 데이터베이스 lock이 걸려서 서비스 중단되는 경우도 있다.
- 개발 초기 단계는 create, update
- 테스트 서버는 update, validate
- 스테이징과 운영 서버는 validate 또는 none
DDL 생성 기능
- 제약 조건 추가: 회원 이름은 필수, 10자 이하로 작성해야한다.
- @Column(nullable = false, length = 10)
- Unique 제약 조건 추가
- Unique 제약 조건이 애플리케이션에 영향을 주지는 않는다. DDL 생성만 도와준다.
- DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.
Ex) Unique 제약 조건
<hide/>
@Column(unique = true, length = 10)
private String name;
Note) 실행 결과
- 그럼 다음과 같이 이름에 길이 10이라는 제약 조건이 붙는다.
- unique 제약 조건도 붙는다.
4.3 필드와 컬럼 매핑
Ex) 요구사항이 있는 경우
- 요구사항
- 회원은 일반 회원 / 관리자 구분
- 회원은 가입일과 수정일이 있어야한다.
- 회원을 설명할 수 있는 필드가 있어야한다.
- Enum 만든다.
<hide/>
package hellojpa;
public enum RoleType {
USER, ADMIN
}
- 멤버 클래스를 수정
- DB에는 ENUM 타입이 없다. => @Enumerated
- DATE => @Temporal()
- 매개변수로 DATE(날짜), TIME(시간), TIMESTAMP(날짜 시간)
- @Lob: 데이터베이스에 varchar를 넘어서는 큰 값을 주고 싶은 경우
- blob, clob(문자 타입, 디폴트) 두 종류가 있다.
<hide/>
package hellojpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
public class Member {
@Id
private Long id;
@Column(name= "name") // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
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;
public Member() {
}
}
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- description이 String이니까 @clob으로 생성된다.
매핑 어노테이션 정리
- @Column: 컬럼 매핑
- @Temporal: 날짜 타입 매핑
- @Enumerated: enum 타입 매핑
- @Lob: BLOB, CLOB 매핑
- @Transient: 매핑하고 싶지 않은 경우에 사용, 특정 필드를 컬럼에 매핑하지 않는다. 매핑을 무시한다.
@Column 속성 - 기본값
- name: 필드와 매핑할 테이블의 컬럼 이름 - 객체의 필드 이름
- insertable, updatable: 등록, 변경 가능 여부 - TRUE
- nullable(DDL): FALSE로 설정하면 NOT NULL 제약 조건이 따라 붙는다. - TRUE
- unique(DDL): 필드 보다는 클래스 앞에 속성을 붙이는 게 좋다.
- 필드에 붙이면 UUID 같은 값이 생성되서 이름을 반영하기 어렵다.
<hide/>
@Table(uniqueConstraints = ??)
- columnDefinition (DDL): 데이터베이스 컬럼 정보를 직접 줄 수 있다. ex) varchar(100) default 'EMPTY'
- length(DDL): 문자 길이 제약 조건, String 타입에만 사용한다.
- precision, scale(DDL): Big Decimal 타입에서 사용한다. precision은 소수점을 포함한 전체 자릿수를, scale은 소수의 자릿수이다. double, float 타입에는 적용되지 않는다. 아주 큰 숫자나 정밀한 소수를 다룰 때만 사용한다.
- value -
ORDINAL을 사용하지 말아야 한다.- 왜? 나중에 enum의 맨 앞에 다른값이 추가되는 경우가 있다. 그럴 때 문제가 생긴다. (기존에 저장되어 있던 rollType 값이 바뀌지 않기 때문)
EnumType.ORDINAL: enum 순서를 데이터베이스에 저장 - 기본값이다.- EnumType.String: enum 이름을 데이터베이스에 저장
Ex) column 속성 값
- member
<hide/>
package hellojpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
public class Member {
@Id
private Long id;
@Column(name= "name", nullable = false) // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
private Integer age;
@Enumerated(EnumType.ORDINAL)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
public Member() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public RoleType getRoleType() {
return roleType;
}
public void setRoleType(RoleType roleType) {
this.roleType = roleType;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setId(1L);
member.setUserName("A");
member.setRoleType(RoleType.USER);
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- ordinal은 RoleType에 디폴트로 숫자가 들어간다. (enum 순서이므로)
- properties를 update로 수정하고 실행
- Main
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setId(2L);
member.setUserName("B");
member.setRoleType(RoleType.ADMIN);
em.persist(member);
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- 회원은 RollType: 0 / admin은RollType이 1로 들어간다.
Ex) EnumType.ORDINAL을 쓰면 안 되는 이유
- Enum 추가
<hide/>
package hellojpa;
public enum RoleType {
GUEST, USER, ADMIN
}
- enumType을 String으로 바꾼다.
<hide/>
@Enumerated(EnumType.STRING)
private RoleType roleType;
Note) 실행 결과 - STRING이 명확하다.
@Temporal
- 날짜 타입을 매핑할 때 사용한다.
- LocalDate, LocalDateTime을 사용할 때는 생략 가능하다. (최신 하이버네이트 지원)
Ex) @Temporal
<hide/>
package hellojpa;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
public class Member {
@Id
private Long id;
@Column(name= "name", nullable = false) // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
private int age;
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
private LocalDate testLocalDate;
private LocalDateTime testLocalDateTime;
@Lob
private String description;
public Member() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public RoleType getRoleType() {
return roleType;
}
public void setRoleType(RoleType roleType) {
this.roleType = roleType;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Note) 실행 결과
- LocalDate => 데이터베이스에 date
- LocalDateTime => 데이터베이스에 timestamp로 들어간다.
- description은 String이므로 CLOB로 매핑된다.
@Lob(Large Object)
- 데이터베이스 BLOB(Binary Large Object), CLOB 타입과 매핑
- @Lob에는 지정할 수 있는 속성이 없다.
- 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
- CLOB: String, Char[], java.sql.CLOB
- BLOB: byte[], java.sql.BLOB
@Transient
- 필드에 매핑하지 않고 데이터베이스에도 조회, 저장되지 않는다.
- 주로 메모리 상에서만 임시로 어떤 값을 보관하고 싶을 때 사용한다.
4.4 기본 키 매핑
JPA가 제공하는 기본 키 매핑 전략
- 직접 할당 방식: @Id만 사용한다.
- 자동 생성 방식: @GeneratedValue - null 을 넣어주면 순차적으로 값이 올라가도록 세팅해준다.
- AUTO: DB dialect에 따라 자동 지정, 기본값
- IDENTITY: 데이터베이스에 기본 키 생성을 위임한다. - MySQL(auto_increment)
- SEQUENCE(시퀀스): 데이터베이스 시퀀스 오브젝트 사용 - Oracle
- SEQUENCE: 유일한 값을 생성해주는 오라클 객체를 말한다.
- 시퀀스를 기본키와 같이 순차적으로 증가하는 컬럼을 자동적으로 생성 가능하다. (pk를 생성하기 위해 사용한다.)
- @SequenceGenerator: 테이블마다 sequence를 다르게 관리할 수 있다.
- TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용한다.
- @TableGenerator 필요하다.
Ex) 기본 키 매핑 - IDENTITY
- properties를 MySQL로 세팅한다.
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
@Column(name= "name", nullable = false) // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
public Member() {
}
}
- Main
- userName으로 c를 넣고 그 다음에 b를 넣는다. 둘다 모두 userId()는 세팅하지 않는다.
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setUserName("b");
em.persist(member);
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- MySql 에 맞추어 auto_increment가 들어간다.
- id값을 세팅해주지 않았지만 다음과 같이 id가 자동으로 1씩 증가하면서 값이 들어간다.
Ex) 기본 키 매핑 - SEQUENCE
- create
<hide/>
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
- update
- 아래 처럼 바꾸고 Main클래스의 setUserName()에는 "B"를 넣는다.
<property name="hibernate.hbm2ddl.auto" value="none" />
Note) 실행 결과
- call next value에서 SEQUENCE에서 가져와서 값을 세팅
Ex) sequence generator
<property name="hibernate.hbm2ddl.auto" value="create" />
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
@Entity
@SequenceGenerator(name = "member_seq_generator", sequenceName ="member_seq")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_seq_generator")
private Long id;
@Column(name= "name", nullable = false) // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
public Member() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
Note) 실행 결과
- sequence를 member_seq라고 만든다.
TABLE 전략
- 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략이다.
- 장점: 모든 데이터베이스에 적용 가능하다.
- 어떤 데이터베이스는 auto_increment, 어떤 데이터베이스는 sequence라고 할 때, 둘 중 하나를 선택해야 하는데 테이블 전략은 모든 DB에 적용 가능하다.
- 단점: 성능
Ex) TABLE 전략 - 매핑
- GenerationType.TABLE: 매핑 전략을 TABLE로 잡는다.
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.TableGenerator;
@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;
@Column(name= "name", nullable = false) // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
public Member() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
Note) 실행 결과
- 데이터를 넣을 때마다 NEXT_VAL은 증가한다.
@TableGenerator 속성과 속성 별 기본값
- name: 식별자 생성기 이름 - 필수
- table: 키 생성 테이블명 - hibernate_sequences
- pkColumnName: 시퀀스 컬럼명 - sequence_name
- valueColumnNa: 시퀀스 값 컬럼명 - next val
- pkColumnValue: 키로 사용할 값 이름 - 엔티티 이름
- initialValue: 초기 값, 마지막으로 생성된 값이 기준이다. - 0
- allocationSize: 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용된다.) - 50
- catalog, scheme: 데이터베이스 catalog, schema 이름
- uniqueConstraints(DDL): 유니크 제약 조건을 지정 가능하다.
권장하는 식별자 전략
- 기본 키 제약 조건: null 불가능 & 불변이어야한다.
- 미래까지 이 조건을 만족하는 자연 키는 찾기 어렵다. 대리 키(대체 키)를 사용하도록 한다.
- ex) 주민등록번호도 기본 키로 적절하지 않다.
- 권장: Long형 + 대체 키(UUID) + 키 생성 전략 사용
- ex) auto_increment, sequence object
IDENTITY 전략, 특징
- 기본 키 생성을 데이터베이스에 위임한다.
- 그래서, IDENTITY 전략은 DB에 데이터가 들어가야 ID를 알 수 있다.
- 그런데 영속성 컨텍스트에서 관리되려면 PK가 있어야한다.
- 주로 MySQL, PostgreSQL, SQL Server, DB2 에서 사용한다. ex) MySQL - auto_increment
- JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행한다. (IDENTITY 와 다른 점)
- auto_increment는 데이터베이스에 INSERT SQL을 실행한 다음에야 ID값을 알 수 있다.
- IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회한다.
Ex) IDENTITY 전략
- 내가 ID에 값을 직접 넣으면 안된다. (데이터베이스에 위임해야하므로)
- 커밋이 아닌 persist()를 호출한 시점에 insert 쿼리가 날아간다.
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.TableGenerator;
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name= "name", nullable = false) // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
public Member() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setUserName("C");
System.out.println("===============");
em.persist(member);
System.out.println("member.id = " + member.getId());
System.out.println("===============");
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- 따라서, id는 null이라고 나온다.
- SELECT 쿼리는 나오지 않는다.
- 왜? JDBC 드라이버에 값을 넣자마자 반환하는 부분이 설정되어 있다. 그래서 INSERT 하는 시점에 바로 알 수 있다.
- persist() 시점에 DB에 데이터를 넣는다. 그 다음 getId()를 하면 DB에서 데이터를 읽어온다.
SEQUENCE - @SequenceGenerator
- name: 식별자 생성기 이름 - 필수
- sequenceName: 데이터베이스에 등록되어 있는 시퀀스이름 - hibernate_sequence
- initialValue: DDL 생성 시에만 사용된다. 시퀀스 DDL을 생성할 때, 처음 시작하는 수를 지정한다. - 1
- allocationSize: 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용된다. 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야한다.) - 50
- allocation size가 왜 50일까?
- ex) 멤버 1, 2, 3이 있다고 하는 경우, em.persist(member1); em.persist(member2); em.persist(member3)
- em.persist() 를 할 때마다 next call로 가져온다면 성능 문제가 생길 수 있다.
- 따라서 next call을 할 때 데이터베이스에 미리 50개를 쌓아 놓는다.
- catalog, schema: 데이터베이스 catalog, schema 이름
Ex) SEQUENCE
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.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) // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
public Member() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setUserName("C");
System.out.println("===============");
em.persist(member);
System.out.println("member.id = " + member.getId());
System.out.println("===============");
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- start with 1 => 1부터 시작해서 1까지 증가시켜라 (size는 1이니까)
- call next value for MEMBER_SEQ: 데이터베이스에 다음 값을 달라고 요청한다.
- commit() 하는 시점에 INSERT 쿼리가 호출된다.
Ex) SEQUENCE - allocationSize = 50
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
@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) // DB에 넣고 싶은 값
private String userName; // 객체에 주는 값
public Member() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member1 = new Member();
member1.setUserName("A");
Member member2 = new Member();
member2.setUserName("B");
Member member3 = new Member();
member3.setUserName("C");
System.out.println("===============");
// em.persist(member);
System.out.println("member.id = " + member1.getId());
System.out.println("member.id = " + member2.getId());
System.out.println("member.id = " + member3.getId());
System.out.println("===============");
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- 1부터 50까지 늘리겠다는 설명이 나온다.
- 여러 웹 서버가 있어도 동시성 이슈 없이 해결된다.
- 현재 값이 -49
- Main클래스에 persist() 추가
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member1 = new Member();
member1.setUserName("A");
Member member2 = new Member();
member2.setUserName("B");
Member member3 = new Member();
member3.setUserName("C");
System.out.println("===============");
em.persist(member1);
// em.persist(member2);
// em.persist(member3);
System.out.println("member.id = " + member1.getId());
System.out.println("member.id = " + member2.getId());
System.out.println("member.id = " + member3.getId());
System.out.println("===============");
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- call next value가 왜 두 번 호출될까?
- DB SEQ 값은 1이었다가 두 번 호출하면 51이 된다.
- persist() 세 개 추가
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member1 = new Member();
member1.setUserName("A");
Member member2 = new Member();
member2.setUserName("B");
Member member3 = new Member();
member3.setUserName("C");
System.out.println("===============");
em.persist(member1);
em.persist(member2);
em.persist(member3);
System.out.println("member.id = " + member1.getId());
System.out.println("member.id = " + member2.getId());
System.out.println("member.id = " + member3.getId());
System.out.println("===============");
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
Note) 실행 결과
- call next value: 1로 맞춤
- call next value: 51번 까지 미리 확보하기 위해 호출된다.
- 그 다음 부터는 더 이상 next call 이 호출되지 않는다.
- 여러 개 추가하고나서 51번을 만나면 미리 100개를 확보해야 하므로 한 번 더 next call이 호출될 것이다.
4.5 요구 사항 분석과 기본 매핑
Ex)
- 새로운 maven 프로젝트를 만든다.
<hide/>
package jpabook.jpashop;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close(); // 팩토리를 나중에 닫는다.
}
}
=======================================오류 ========================================
- 오류: 데이터베이스가 없다는 오류가 난다.
- 원인: 초기 접속 url 입력 오류
- 정상 실행 화면
- 데이터베이스에 처음 연결할 때는 아래과 같이 :"tcp localhost"를 빼고 URL을 넣어야 데이터베이스가 생성된다.
Note) 실행 결과 - 데이터베이스와 테이블이 생긴다.
- Order 테이블에 member 필드를 추가하면 다음과 같은 알림이 뜬다.
데이터 중심 설계의 문제점
- 현재 방식은 객체 설계를 테이블 설계에 맞춘 방식이다.
- 테이블의 외래 키를 객체에 그대로 가져온다.
- 객체 그래프 탐색이 불가능하다
- 참조가 없으므로 UML(Unified Modeling Language, 통합 모델링 언어)도 잘못됨
- Order클래스에서 MemberId가 아닌 진짜 멤버를 가질 수 있도록 연관 관계 매핑을 배운다!
'Spring Framework > [인프런] Java ORM 표준 프로그래밍 - JPA' 카테고리의 다른 글
Chapter 06. 다양한 연관관계 매핑 (0) | 2022.09.05 |
---|---|
Chapter 05. 연관 관계 매핑 기초 (0) | 2022.09.05 |
Chapter 03. 영속성 관리 - 내부 동작 방식 (0) | 2022.09.03 |
Chapter 02. JPA 시작하기 (0) | 2022.09.03 |
Chapter 01. JPA 소개 (2) | 2022.09.02 |