4 엔티티 매핑
4.1 @Entity
4.2 @Table
4.3 다양한 매핑 사용
4.4 데이터베이스 스키마 자동 생성
4.5 DDL 생성 기능
4.6 기본 키 매핑
4.7 필드와 컬럼 매핑 : 레퍼런스
JPA는 다양한 어노테이션을 지원한다.
* 객체와 테이블 매핑 : @Entity, @Table
* 기본키 매핑 : @Id
* 필드와 컬럼 매핑 : @Column
* 연관관계 매핑 : @ManyToOne, @JoinColumn, @OneToMany ...
4.1 @Entity
관계형 데이터베이스의 테이블과 매핑할 클래스는 @Entity 어노테이션을 클래스 위에 필수로 붙여줘야 한다.
@Entity(name = "")
public class Member {
...
public Member() {} // 또는 protected
}
@Entity가 붙은 클래스는 JPA가 관리하게 되며 엔티티라 부른다
@Entity의 속성(어노테이션 필드)
- name : JPA에서 사용할 엔티티 이름을 지정한다. 테이블의 이름을 지정하는 것이 아니다.
- 기본 값은 "" 이며 설정하지 않으면 클래스 이름을 그대로 사용한다.
- 기본 생성자가 필수이다(파라미터가 없는 public 생성자 또는 protected 생성자).
- 왜? -> 리플렉션이나 프록시 기반 기술을 사용할 때는 기본 생성자가 필수이다.
- private 기본 생성자는 상속받아서 구현 할 수 없기 때문이다.
- 또한 지연 로딩을 사용할 때는 임시로 hibernate가 생성한 proxy 객체를 사용하는데, 이때 기본 생성자가 없거나 private 이면 프록시 패턴으로 객체를 만들 수 없다.
- 읽어보면 좋다
- 자바는 생성자가 없으면 기본 생성자를 자동으로 만들어 주는데, 만약 생성자를 하나 이상 만들면 기본생성자를 자동으로 만들지 않는다.
- 왜? -> 리플렉션이나 프록시 기반 기술을 사용할 때는 기본 생성자가 필수이다.
- final 클래스, enum, interface, inner 클래스에는 @Entity 어노테이션을 사용할 수 없다.
- 저장할 필드에 final을 사용하면 안된다.
- JPA는 엔티티의 필드 변경과 변경감지로 값을 update 하기 때문.
4.2 @Table
@Table은 엔티티와 실제로 매핑할 테이블을 지정한다.
생략하면 매핑한 엔티티의 이름을 테이블로 사용한다.
@Entity(name = "")
@Table
public class Member {
...
public Member() {} // 또는 protected
}
@Target(TYPE)
@Retention(RUNTIME)
public @interface Table { // Table 어노테이션의 필드들
String name() default "";
String catalog() default "";
String schema() default "";
UniqueConstraint[] uniqueConstraints() default { };
Index[] indexes() default {};
}
@Table의 속성(어노테이션 필드)
- name : 매핑할 테이블의 이름을 지정한다. 생략하면 엔티티 이름을 사용한다.
- catalog : catalog 기능이 있는 DB에서 catalog를 매핑한다.
- schema : schema 기능이 있는 데이터베이스에서 schema를 매핑한다.
- uniqueConstraints : DDL 생성 시에 유니크 제약조건을 만든다.
- 2개 이상의 복합 유니크 제약조건도 만들 수 있다.
- 이 기능은 스키마 자동 생성 기능을 사용해서 DDL을 만들때만 사용된다.
- 스프링 부트에서는 properties 설정을 통해ddl-auto 옵션으로 자동으로 만들 때.
// uniqueConstaints 옵션 사용
@Entity
@Table(name="MEMBER", uniqueConstraints = {@UniqueConstraint( //추가 //**
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"} )})
public class Member {
private String name;
private Integer age;
}
4.3 다양한 매핑 사용
회원 관리 프로그램에서 요구사항이 추가되었다.
- 회원은 일반 회원과 관리자로 구분해야 한다 -> 회원 테이블에 구분값 컬럼이 필요
- 회원가입과 수정일이 있어야 한다. -> 날짜 관련 컬럼이 필요
- 회원을 설명할 수 있는 필드가 있어야 한다. 이필드는 길이 제한이 있다 -> text 형식의 컬럼이 필요.
@Entity
@Table(name="MEMBER")
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME", nullable = false, length = 10) //추가 //**
private String username;
private Integer age;
//=== 추가
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
@Transient
private String temp;
}
public enum RoleType {
ADMIN, USER
}
- enum 타입을 필드로 가질 수 있다.
- 이 때, 필드에 @Enumerated 어노테이션이 필요하다.
- date 타입을 가질 수 있다.
- 날짜 타입은 필드에 @Temporal을 사용하여 매핑.
- description 필드는 String이며 Lob 타입이다.
- @Lob 타입은 CLOB, BLOB 타입을 매핑할 수 있다.
필드와 컬럼 매핑
어노테이션설명
@Column | 컬럼과 필드를 매핑 |
@Enumerated | 자바의 enum 타입 매핑 |
@Temporal | 날짜 타입 매핑 |
@Lob | BLOB, CLOB 타입 매핑 |
@Transient | 특정 필드를 데이터베이스에 매핑하지 않을떄 사용. 매핑 제외 |
@Access | JPA가 엔티티에 접근하는 방식 지정 |
@Column
객체 필드를(멤버변수) 테이블 컬럼에 매핑할 때 사용
속성기능기본값
name | 필드와 매핑할 테이블의 컬럼 이름 | 객체의 필드 이름. 카멜 캐이스 |
nullable | null 값의 허용 여부 설정. false로 설정시 허용 안하므로 not null 제약 조건 추가 | true |
unique | 한 컬럼에 유니크 제약 조건 걸 때 사용. 만약 두 컬럼 이상을 사용하여 유니크 제약조건을 걸 경우, 클래스 레벨(엔티티) 에서 @Table.uniqueConstraints 를 사용해야 한다 | |
length | 문자 길이 제약 조건, String 타입에만 사용 | 255 |
precision, scale | BigDecimal 타입에서 사용. precision : 소수점 포함 전체 자릿수, scale: 소수점 아래 자릿수.dobule, float 타입에는 적용되지 않고, 아주 큰 숫자나 정밀한 소수에만 사용 | |
columnDefinition | DB에 컬럼 정보를 직접 줄 수 있음 | |
insertable | 엔티티 저장시 이 필드도 같이 저장. false로 설정시 이 필드는 DB에 저장하지 않음 | true |
updatable | 엔티티 수정시 이 필드도 같이 수정. false로 설정시 이 필드는 DB에 수정하지 않음. 읽기 전용일 때 사용 | true |
table | 하나의 엔티티를 두 개 이상의 테이블에 매핑할 때 사용 | 현재 매핑된 테이블(엔티티클래스) |
@Enumerated
객체 필드를(멤버변수) enum 타입 맵핑시 사용
속성기능기본값
value | EnumType.ORDINAL: enum 순서를 DB에 저장, (숫자) | EnumType.ORDINAL |
EnumType.String: enum 이름을 DB에 저장 | EnumType.ORDINAL |
@Temporal
날짜 타입 (Date, Calendar)를 매핑할 때 사용
속성기능기본값
value | TemporalType.DATE : 날짜. DB date 타입과 매핑 | 필수로 지정해야함 |
TemporalType.TIME : 시간. DB time 타입과 매핑 | ||
TemporalType.TIMESTAMP : 날짜와 시간. DB timestamp 타입과 매핑 |
@Lob
DB의 BLOB, CLOB 타입과 매핑한다
@Lob은 지정할 수 있는 속성이 없다. 대신 매핑 필드 타입이 문자면 CLOB, 나머지는 BLOB으로 매핑한다
- CLOB : String, char[], java.sql.CLOB
- BLOB : byte[], java.sql.BLOB
@Transient
이 필드는 잘 매핑하지 않는다. DB에 값을 저장하지도, 조회하지도 않고 임시로 객체에 어떤 값을 보관하고 싶을 때 사용
- 예를 들어 컬럼들을 이용한 응답값을 엔티티에 담고 있을때도 한 예가 된다.
@Transient
private String 임시메모;
@Access
JPA가 엔티티 클래스에 접근하는 방식을 지정
- 필드 접근 : AccessType.FIELD로 지정. 필드에 직접 접근. private 이여도 접근할 수 있음!
- 프로퍼티 접근 : AccessType.PROPERTY로 접근 . 접근자(getter)를 사용
4.4 데이터베이스 스키마 자동 생성
JPA는 데이터베이스 스키마를 자동하는 생성하는 기능을 지원한다 -> 테이블들도 자동으로 생성
- 책에서는 xml 기반으로 설정하였지만, 스프링 부트 properties를 이용한 설정을 적겠다.
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/h2/test # h2 jdbc url
driver-class-name: org.h2.Driver # JDBC Driver
username: sa # userID
password: # password
jpa:
database: h2
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect #Dialect, ?? ??
show_sql: true
format_sql: true
use_sql_comments: true
id:
new_generator_mappings: true
hibernate:
ddl-auto: validate none create update create-drop
- spring.jpa.hibernate.show-sql : true로 지정하면 콘솔에 실행되는 sql을 출력한다.
- spring.jpa.hibernate.ddl-auto : 속성 이름처럼, ddl을 자동으로 설정하거나 설정하지 않는 옵션들을 지정할 수 있다.
- 애플리케이션 실행 시점에 테이블이 자동으로 생성되므로 테이블을 직접 생성하는 수고를 덜 수 있따.
- 하지만 운영환경이나 개발서버에서는 사용하지 않는 것이 좋다.
ddl-auto의 옵션들
- create : 기존 테이블을 삭제하고 새로 생성한다. DROP + CREATE -> 애플리케이션 실행시점에 실행
- create-drop : create 속성에 추가로 애플리케이션을 종료할 때 생성한 DDL을 제거. DROP + CREATE + DROP
- update : 테이블과 엔티티 매핑정보를 비교하여, 변경 사항만 수정한다
- 참고 : 기존에 있던 컬럼을 없애지는 않는다.
- validate : 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 않는다.
- DDL을 수정하진 않는다. 검증하고 다르다면 실행X
- none : 자동 생성 기능을 사용하지 않는다.
- 자동 생성 기능을 사용하지 않을것이라면 속성 자체를 적지 않거나(생략) none이나 유효하지 않은 옵션값을 주면 된다.
- none은 유효하지 않는 옵션 값에 포함된다.
책에서의 개발 환경에 따른 ddl-auto의 추천 전략이다.
- 개발 초기단계 create or update
- 자동화된 테스트 단계와 CI 서버는 create or create-drop
- 테스트 서버는 update 또는 validate
- 스테이징과 운영 서버는 validate 또는 none
이름 매핑전략
단어와 단어사이에 자바 언어는 관례상 Camel Case를 사용하고, DB는 언더스코어(_)를 주로 사용한다.
네이밍 컨벤션에는 여러가지가 있다.
- camelCase: 앞자는 소문자 단어 사이에 대문자
- PascalCase: camleCase와 같은데 첫 글자가 대문자이다. Initial Capitals, Initial Caps, InitCaps 로도 불린다.
- snake_case: 모든 문자는 소문자(lowercase)로 단어간 구분은 언더스코어(_)로 구분한다.
- kebab-case: 모든 문자는 소문자(lowercase)로 단어간 구분은 대시(-)로 구분한다.
- ALL CAPS: all capitals 의 줄임말로 모든 글자를 대문자로 쓴다.
@Column을 사용하여 필드의 이름을 지정할 때, 속성을 명시적으로 사용해서 _를 붙여줘야 하는 귀찮음이 있다.
하지만 책에선 hibernate.jeb.naming_strategy 이지만,
- 공식 doc 문서에 따르면 org.hibernate.cfg.NamingStrategy는 더 이상 사용하지 않는다
spring boot에서는 spring.jpa.hibernate. 의 implicitStrategy 또는 physicalStrategy 를 이용한다.
스프링 부트에서는 기본적으로 물리 네이밍 전략을 SpringPhysicalNamingStrategy 를 사용
모든 도트는 밑줄로 대체, Camel Case 대문자는 밑줄로 대체, 모든 테이블은 소문자로 구성
- ImplicitNamingStrategy(암시적 명칭 전략)
- PhysicalNamingStrategy(물리적 명칭 전략)
Implict Naming Strategy (암시적 명칭 전략)
- 명시적으로 naming이 지정되지 않은 Entity들의 명칭을 만들어주는 방식.
- 그렇기 @Table, @Column 등의 방법으로 명칭을 미리 지칭한 경우 해당 전략은 적용되지 않는다.
The ImplicitNamingStrategy would only be applied if an explicit name was not given. 출처 : Hibernate ORM 5.4.27.Final User Guide
- 해당 방법은 기본적으로 ImplicitNamingStrategyJpaCompliantImpl 방식을 따르며, JPA에 정의한 변수, 클래스 명칭 그대로 적용된다.
- 만약 사용자가 직접 전략을 구상하고 싶다면 ImplicitNamingStrategy 인터페이스나 해당 자식 클래스들을 상속받아 구현하고 적용하면 된다.
- 구현한 클래스명을 properties에 설정.
- spring.jpa.hibernate.implicitStrategy = 구현한 클래스명
예시) 참조
Physical Naming Strategy (물리적 명칭 전략)
전략 이름대로, 물리 데이터베이스 테이블명과 컬럼명을
DB의 기본 전략인 under_score로 사용하지 않고 지정된 형식대로 사용.
- 만약 기존의 개발자가 Entity의 일부 필드는 @Column으로 명시했고 그 외의 경우는 따로 만든 CustomImplicitNamingStrategy를 적용했다고 가정.
- 하지만 DB변경으로 명칭 정책이 바뀜에 따라 Column과 Table 명칭을 Uppercase, Snake 표기법으로 변경한다면 ?
개발자는 기존의 암시적 명칭 전략을 변경하는 작업과 동시에, 코드상에 명시적으로 정의한 명칭들을 전부 수정해야 한다.
물리적 명칭 전략은 위에 대해 훌륭한 대책이 된다.
- 해당 전략은 명시적 또는 암시적으로 명칭이 결정되든 항상, 마지막으로 적용.
- 명시적, 암시적 전략이 먼저 적용되고 그 이후에 적용이 된다.
- 공식 문서에서는 DB의 공통적인 규칙(Upper or lowercase, Snake case, 명칭 약자 등)을 적용할 때 사용하도록 권장하고 있다.
기본값으로는 logical_name 즉, 명시적, 암시적 명칭을 그대로 사용
이를 변경하려면 PhysicalNamingStrategy 인터페이스나 해당 자식들을 상속받아 정의하면 된다.
물리적 명칭 전략에선 다음 3가지가 기본으로 주어진다
- org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
- Deprecated 됨. 사용 x
- org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
- 변수 이름을 그대로 DB에서 사용 - @Table에서 지정한 설정대로 그대로 사용 가능.
- org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
- 변수 이름이 카멜케이스면 언더스코어로 바꿔주는 전략
- DB의 공통적인 명칭규칙은 물리적 명칭 전략, 그 외의 경우는 명시적, 암시적 명칭 전략을 사용하는 것이 권장된다.
4.6 기본 키 매핑
@Entity
public class Member {
@Id
@GeneratedValue(strategy = "전략") // 기본키 생성 전략
@Column(name="ID")
private String id;
}
public @interface GeneratedValue {
GenerationType strategy() default GenerationType.AUTO;s
String generator() default "";
}
public enum GenerationType {
TABLE,
SEQUENCE,
IDENTITY,
AUTO;
private GenerationType() {
}
}
기본 키 지정
- @Id 어노테이션으로 필드에 매핑.
- 자바 기본형(primitve type) 또는 래퍼(Wrapper type)만 가능.
- String
- java.util.Date
- java.util.LocaldateTime
- javal.sql.Date
- java.math.BigDecimal
- java.math.BigInteger
기본 키 생성 전략
- @GeneratedValu 어노테이션으로 ID 필드에 매핑
- strategy = GenerationType 으로 전략을 지정.
- 디폴트는 AUTO
JPA가 제공하는 기본 키 생성 전략 (Strategy)
- 직접 할당 : 기본 키를 애플리케이션에 직접 할당
- 자동 생성 : 대리 키 사용 방식.
자동 생성 전략 : IDENTITY (GenerationType(strategy=IDENTITY))
IDENTITY: 기본키 생성을 데이터베이스에 위임한다.
- GenerationType.IDENTITY
- MySQL, PostgreSQL, SQL Server, DB2, MariaDB에서 사용.
- AUTO_INCREMENT 기능 제공
- 저장(persist, save) 하고 나서야 기본 키 값을 얻을 수 있다.
- 엔티티를 실제 DB에 저장해야 ID값을 얻을 수 있으므로 save 즉시 INSERT SQL이 발동하며 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다
- 데이터베이스에 저장한 후에 식별자를 조회해서 엔티티의 식별자에 값을 할당하는것
자동 생성 전략 : SEQUENCE (GenerationType(strategy=SEQUENCE))
데이터베이스 시퀀스를 사용해서 기본키 할당
- 오라클, PostgreSQL, DB2, H2 에서 주로 사용
- GenerationType.SEQUENCE
- @GeneratedValue(generator="시퀀스 생성기 등록")
-
@Entity @SequenceGenerator ( name="BOARD_SEQ_GENERATOR", sequenceName="BOARD_SEQ", //매핑할데이터베이스시퀀스이름 initialValue=1, all○cati○nsize=1) public Class B○ard { @Id @GeneratedValue(Strateqv = Generati○nType.SEQUENCE/ generat○r="BOARDSEQGENERATOR") private L○ng id; ... }
- SEQUENCE 전략은 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회 한 후 엔티티에 식별자를 set 하고 엔티티를 영속성 컨텍스트에 저장. 이후 플러시가 일어나면 엔티티를 실제로 디비에 저장.
@SequenceGenerator의 옵션들
- name : 식별자 생성기 이름. 값은 무조건 필수이다.
- sequenceName : DB에 등록되어있는 시퀀스 이름.
- 기본값 : hibernate_sequence
- 간혹가다 MySQL 등을 사용하는데, hibernate_sequence 테이블이 있따면 어떤 엔티티에 전략을 잘못 설정한것
- initiolValue : DDL 생성시에 사용. 시퀀스 DDL을 생성할 떄 처음 시작하는 수를 지정
- 기본값: 1
- allocationSize : 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용)
- 기본값 : 50
- catalog, schema : DB catalog, schema 이름
기본 키 자동 생성 전략이 다른 이유는 데이터베이스 벤더마다 지원하다 방식이 다르다
- 오라클은 시퀀스 제공, MySQL는 시퀀스 제공 X -> AUTO_INCREMENT 사용
- 따라서 SEQUENCE나 IDENTITY 전략은 DB에 의존한다
기본 키 직접 할당 전략은 개발자가 직접 setId로 값을 할당하는 방식.
TABLE 전략
TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 시퀀스를 흉내내는 전략
이 전략은 테이블을 사용하므로 모든 DB에 적용 가능.
이 전략은 데이터베이스와 한번 더 통신하는 단점이 있다.
최적화 하려면 @TableGenerator.allicationSize를 사용하면 되며, SEQUENCE 전략과 같다.
@GeneratedValue(strategy = GenerationType.TABLE, generator= "BOARDSEQ_GENERATOR")
create table MY_SEQUENCES {
sequence_name varchar(255) not null,
next_val bigint,
primary key(sequence_name)
}
- sequence_name을 시퀀스 이름으로 사용
- next_val을 시퀀스 값으로 사용
```JAVA
@Entity
@TableGenerator (
name="BOARD_SEQ_GENERATOR",
table="MY_SEQUENCES",
pkColumnValue="BOARD_SEQ", allocationSize=1
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator= "BOARDSEQ_GENERATOR")
private Long id;
```
- 테이블에 값이 없으면 JPA가 값을 INSERT 하면서 초기화 하므로 미리 초기화 할 필요는 없다 .
TableGenerator 속성
- name : 식별자 생성기 이름
- 기본 값 필수
- table : 키생성 테이블명
- 기본 값: hibernate_sequences
- 변경이 가능하다.
- pkColumnName : 시퀀스 컬럼명
- 기본 값 : sequence_name
- valueColumnName : 시퀀스 값 컬럼명
- 기본 값 : next_val
- pkColumnValue : 키로 사용할 값 이름
- 기본 값 : 엔티티이름
- initialValue : 초기 값, 마지막으로 생성된 값이 기준이다.
- 기본 값 0
- allocationSize : 시퀀스한번호출에증가하는수(성능최적화에 사용)
- 기본 값 50
- catalog, schema : 데이터베이스 catalog, schema
- 기본 값 이름
- uniqueconstraints(DDL) : 유니크 제약 조건을 지정할 수 있다
AUTO 전략
@GenerationValue(strategy = GenerationType.AUTO) 는 DB 방언(Dialect)에 따라
IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다.
예를들어 오라클을 사용하면 SEQUENCE를, MySQL을 사용하면 IDENTITY를 사용한다
기본키 매핑 정리
영속성 컨텍스트는 엔티티를 식별자 값 으로 구분하므로 영속 상태로 만들라면 반드시 식별자 값이 있어야 한다. em.persist() 메서드를 호출한 직후에 발생하는 일을 식별자 할당 전략별로 정리하면,
- 직접 할당 : em.persist()를 호출하기 전에 애플리케이션에서 직접 식별자 값을 할당해야함. 없으면 예외 발생
- Member member = new Member();
member.setId(1L); - SEQUENCE : DB 시퀀스에서 시퀀스 값을 획득한 후 영속성 컨텍스트에 저장
- TABLE : DB 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 위와 동일
- IDENTITY: DB에 엔티티를 저장해서 식별자 값을 획득한 후 위와 동일
- IDENTITY 전략은 테이블에 데이터를 저장해야 식별자 값을 획들할 수 있다.
권장하는 식별자 선택 전략
기본키의 조건 3가지
- NOT NULL, UNIQUE, 변하면 안된다.
테이블 기본 키 선택 전략
- 자연 키
- 주민번호, 이메일, 전화번호
- 대리 키
- 시퀀스, auto_increment, 키생성 테이블 사용
JPA는 모든 엔티티에 일관된 방식으로 대리 키 사용을 권장한다
- 비즈니스 환경은 언젠간 변하므로. 유일한 값이라도 생각되었떤 것들이 변화될 수 있다.
'스터디 > JPA 프로그래밍 스터디' 카테고리의 다른 글
JPA 프로그래밍 스터디 7장 정리 (1) | 2022.10.01 |
---|---|
JPA 프로그래밍 스터디 6장 정리 (0) | 2022.10.01 |
JPA 프로그래밍 스터디 5장 정리 (0) | 2022.09.13 |
JPA 프로그래밍 스터디 3장 정리 (0) | 2022.09.04 |
JPA 프로그래밍 스터디 1장,2장 (0) | 2022.08.31 |