스터디/JPA 프로그래밍 스터디

JPA 프로그래밍 스터디 4장 정리

ysk(0soo) 2022. 9. 13. 20:35

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를 매핑한다.
    • 데이터베이스 카탈로그는 데이터베이스의 개체들에 대한 정의를 담고 있는 메타데이터들로 구성된 데이터베이스 내의 인스턴스이다. 기본 테이블, 뷰 테이블, 동의어(synonym)들, 값 범위들, 인덱스들, 사용자들, 사용자 그룹 등등과 같은 데이터베이스의 개체들이 저장된다.
  • 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 다양한 매핑 사용

회원 관리 프로그램에서 요구사항이 추가되었다.

  1. 회원은 일반 회원과 관리자로 구분해야 한다 -> 회원 테이블에 구분값 컬럼이 필요
  2. 회원가입과 수정일이 있어야 한다. -> 날짜 관련 컬럼이 필요
  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)

  1. 직접 할당 : 기본 키를 애플리케이션에 직접 할당
  2. 자동 생성 : 대리 키 사용 방식.

자동 생성 전략 : IDENTITY (GenerationType(strategy=IDENTITY))

IDENTITY: 기본키 생성을 데이터베이스에 위임한다.

 

  1. GenerationType.IDENTITY
  2. MySQL, PostgreSQL, SQL Server, DB2, MariaDB에서 사용.
  3. AUTO_INCREMENT 기능 제공
  4. 저장(persist, save) 하고 나서야 기본 키 값을 얻을 수 있다.
  5. 엔티티를 실제 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 사용
  • 따라서 SEQUENCEIDENTITY 전략은 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는 모든 엔티티에 일관된 방식으로 대리 키 사용을 권장한다

  • 비즈니스 환경은 언젠간 변하므로. 유일한 값이라도 생각되었떤 것들이 변화될 수 있다.