[Spring / JPA] 프로젝트 설계를 위한 어노테이션
서버개발

[Spring / JPA] 프로젝트 설계를 위한 어노테이션

JAVA JPA와 SPRING 개발을 동시에 공부하는 중 입니다. 간혹 내용이 섞여있을 수 있습니다.

 

Entity 설계

# 클래스 어노테이션 ( Class Annotation )

// Class Annotation

@Entity
- 테이블과의 매핑
- @Entity가 붙은 클래스는 JPA가 관리하는 것으로, 엔티티라고 불림
	> 속성
	- Name : JPA에서 사용할 엔티티 이름을 지정. ex) @Entity(name = "asdf")
		- 보통 기본값인 클래스 이름을 사용

@Table
- Entity와 매핑할 테이블을 지정
- 생략 시 매핑한 Entity이므로 테이블 이름으로 사용

	> 속성 
	- Name : 매핑할 테이블 이름 (default. 엔티티 이름 사용)
	- Catalog : catalog 기능이 있는 DB에서 catalog 를 매핑 (default. DB 명)
	- Schema : schema 기능이 있는 DB에서 schema를 매핑
	- uniqueConstraints : DDL 생성 시 유니크 제약조건을 만듦
     	> 스키마 자동 생성 기능을 사용해서 DDL을 만들 때만 사용

@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
 - 상속관계 매핑
 	> 속성
	- JOINED : 각각의 테이블로 변환하는 조인 전략
	- SINGLE_TABLE : 통합 테이블로 변환하는 단일 테이블 전략
	- TABLE_PER_CLASS : 서브타입 테이블로 변환하는 구현 클래스마다 테이블을 생성하는 전략
    (default. SINGLE_TALE)
 
@DiscriminatorColumn(name="dtype")
 - 부모 클래스에 선언한다. 하위 클래스를 구분하는 용도의 컬럼이다. 관례는 default = DTYPE
 
@DiscriminatorValue("M")
 - 하위 클래스에 선언한다. 엔티티를 저장할 때 슈퍼타입의 구분 컬럼에 저장할 값을 지정한다.
 - 어노테이션을 선언하지 않을 경우 기본값으로 클래스 이름이 들어간다.

@Embeddable
 - 필드들을 묶어 데이터베이스에 하나의 필드로 표시될 수 있도록 임베디드 클래스화 시키는 것

- 클래스 선언 시 클래스 상단에 붙이는 어노테이션이다

 

#  필드 어노테이션 ( Field Annotation )

// Field Annotation

 @Id
 - 기본키 매핑 : JPA 엔티티 객체의 식별자로 사용할 필드에 적용하여, 유니크한 DB의 컬럼과 맵핑한다
 - @Id 애노테이션이 getter 메서드에 걸린 경우에는 JPA가 맵핑 정보를 판단하는데 있어서 모든 필드에 
 대한 Getter 메서드를 기준으로 참고합니다.

 @GeneratedValue
 - 기본키를 자동으로 생성할 때에는 @Id와 @GenerratedValue 어노테이션이 함께 사용되어야 한다.
 	(default. AUTO)
 - 기본 설정 값으로 각 데이터베이스에 따라 기본키를 자동으로 생성한다.
 - 기본키의 제약조건
	> null이면 안된다.
	> 유일하게 식별할 수 있어야한다.
	> 변하지 않는 값이어야 한다.
 
 @Column(name="item_id")
 - 객체 필드를 테이블 컬럼에 매핑
 - 속성 중 name, nullable이 주로 사용되고 나머지는 잘 사용되지 않음

	> 속성
	- name : 필드와 매핑할 테이블 컬럼 이름 (default. 객체의 필드 이름)
	- nullable (DDL) : null 값의 허용 여부 설정, false 설정 시 not null (default. true)
   	@Column 사용 시 nullable = false 로 설정하는 것이 안전
	- unique (DDL) : @Table 의 uniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제약조건을 적용
	- length (DDL) : 문자 길이 제약조건, String 타입에만 사용 (default. 255)

 @ManyToOne(fetch = LAZY) 
 @OneToOne(mappedBy = "delivery", fetch = LAZY)
 @ManyToOne(fetch = LAZY)
 @OneToMany(mappedBy = "parent")
 @ManyToMany(mappedBy = "items")
  - 연관관계 매핑에 사용 (E-R)
  - 기본적으로 fetch가 LAZY이지 않은 어노테이션도 존재하므로 fetch = LAZY하는게 좋음
  - cascade : 영속성 전이기능 사용
  - mappedBy : 양방향 매핑관계일 때 사용하는 반대쪽 매핑의 필드 이름을 값으로 준다.
 
 @JoinColumn(name="order_id")
  - 외래키를 매핑할 때 사용한다. name 속성에는 매핑할 외래키 이름을 지정한다
 
 @JoinTable(name="category_item",
        joinColumns = @JoinColumn(name="category_id"),
            inverseJoinColumns = @JoinColumn(name="item_id"))
 
 > JoinColumn과 JoinTable의 시각화
 ( https://velog.io/@sa1341/JPA-%EC%A1%B0%EC%9D%B8-%EC%BB%AC%EB%9F%BC-%EB%B0%8F-%EC%A1%B0%EC%9D%B8-%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0 )
 - 조인 컬럼을 사용하는 방법은 단순히 외래 키 컬럼만 추가해서 연관관계를 맺지만 조인 테이블을 사용하는 
 방법은 연관관계를 관리하는 조인 테이블을 추가하고 여기서 두 테이블의 외래 키를 가지고 연관관계를 관리합
 니다. 따라서 MEMBER, LOCKER 테이블에는 연관관계를 관리하기 위한 외래 키 컬럼이 없습니다.
 
 - 조인 테이블의 가장 큰 단점은 테이블을 하나 추가해야 한다는 점입니다. 
 따라서 관리해야 하는 테이블이 늘어나고 회원과 사물함 두 테이블을 조인하려면 MEMBER_LOCKER 테이블까지 
 추가로 조인해야 합니다.
 
 @Embedded
  - 새로운 값 타입을 직접 정의해서 사용할 수 있는데, JPA에서는 이것을 임베디드 타입이라 합니다.
  - 데이터베이스에서 분할되어 보이지 않도록 설계
 ---
 
  @Temporal(TemporalType.DATE)
  Date startDate;
  @Temporal(TemporalType.DATE)
  Date endDate;
  
  => 
  
  @Embedded
  private Period workPeriod;	// 근무 기간
  
---
 
 @Enumerated(EnumType.STRING) // String으로 써야함
  - 자바의 enum 타입을 매핑할 때 사용
  
 @Temporal
  - 날짜 타입을 매핑할 때 사용
  
      > 속성
		- TemporalType.DATE : 날짜, 데이터베이스 data 타입과 매핑 (2020-12-18)
		- TemporalType.TIME : 시간, 데이터베이스 time 타입과 매핑 (23:36:33)
		- TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스 timestamp 타입과 매핑 (2020-12-18 23:36:33)
 		(default. TemporalType은 필수로 지정)
        
 @Transient
  - 이 필드는 매핑하지 않음
  - 데이터베이스 조회 / 저장 X
  - 객체에 임시 값을 보관하고 싶을 때 사용
  
 @Access
  - JPA의 엔티티 데이터 접근하는 방식 지정
  - AccessType.FIELD : 필드에 직접 접근 (private도 접근함)
  - AccessType.PROPERTY : 접근자 Getter를 사용하여 접근

- 필드 선언 시 필드 상단에 붙는 어노테이션이다

 

 

JPA 데이터베이스 스키마 자동 생성 기능

# JPA는 클래스의 매핑정보와 데이터베이스 방언을 사용하여 데이터베이스 스키마를 자동 생성하는 기능을 제공한다

// application.yml

jpa:
    hibernate:
      ddl-auto: create
      
> create : 기존 테이블을 삭제하고 새로 생성(DROP + CREATE)
> create-drop : CREATE 속성에 추가로 애플리케이션을 종료할 때 생성한 DDL을 제거 (DROP + CREATE + DROP)
> update : DB 테이블과 엔티티 매핑 정보를 비교해서 변경 사항만 수정
> validate : DB 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 
않음.DDL을 수행하지 않음
> none : 자동 생성 기능을 사용하지 않음

 

# 주의사항

- 개발 초기 단계는 create또는 update 사용

- 초기화 상태로 자동화된 테스트를 진행하는 개발자 환경과 CI서버는 create 또는 create-drop

- 테스트 서버는 update 또는 validate

- 스테이징과 운영 서버는 validate 또는 none

 

Cascade란?

# @OneToMany와 @ManyToOne에 옵션으로 줄 수 있는 값 ( Entity의 상태 변화를 전파시키는 옵션 )

- Entity의 상태 변화를 전파시키는 옵션이다

- 만약 Entity의 상태 변화가 있으면 연관되어 있는(ex. @OneToMany, @ManyToOne) Entity에도 상태 변화를 전이시키는 옵션이다

- 기본적으로는 아무 것도 전이시키지 않는다

 

# Entity의 상태
- 비영속(Transient): 객체를 생성하고, 값을 주어도 JPA나 hibernate가 그 객체에 관해 아무것도 모르는 상태. 즉, 데이터베이스와 매핑된 것이 아무것도 없다.
- 영속(Managed / Persistent): 저장을 하고나서, JPA가 아는 상태(관리하는 상태)가 된다. 그러나 .save()를 했다고 해서, 이 순간 바로 DB에 이 객체에 대한 데이터가 들어가는 것은 아니다. JPA가 persistent 상태로 관리하고 있다가, 후에 데이터를 저장한다.(1차 캐시, Dirty Checking(변경사항 감지), Write Behind(최대한 늦게, 필요한 시점에 DB에 적용) 등의 기능을 제공한다)
- 준영속(Detached): JPA가 더이상 관리하지 않는 상태. JPA가 제공해주는 기능들을 사용하고 싶다면, 다시 persistent 상태로 돌아가야한다.
- 삭제(Removed): JPA가 관리하는 상태이긴 하지만, 실제 commit이 일어날 때, 삭제가 일어난다.

cascade는 이러한 상태변화를 전이시키는 것이다.

 

# Cascade 적용

- CascadeType.PERSIST
엔티티를 영속화 할 때이 필드에 보유 된 엔티티도 유지합니다. EntityManager가 flush 중에 새로운 엔티티를 참조하는 필드를 찾고이 필드가 CascadeType.PERSIST를 사용하지 않으면 오류이므로이 Cascade 규칙의 자유로운 적용을 제안합니다.

- CascadeType.MERGE
엔티티 상태를 병합 할 때, 이 필드에 보유 된 엔티티도 병합하십시오.

- CascadeType.REFRESH
엔티티를 새로 고칠 때, 이 필드에 보유 된 엔티티도 새로 고칩니다.

- CascadeType.REMOVE
엔티티를 삭제할 때, 이 필드에 보유 된 엔티티도 삭제하십시오.

- CascadeType.DETACH
부모 엔티티가 detach()를 수행하게 되면, 연관된 엔티티도 detach() 상태가 되어 변경사항이 반영되지 않는다.

- CascadeType.ALL
모든 Cascade 적용

 

 

Validation 어노테이션 (부제 NotNull vs NotEmpty vs NotBlank)

implementation 'org.springframework.boot:spring-boot-starter-validation' 을 추가하여야 한다 ( build.gradle )

# @NotNull

- 이름 그대로 Null만 허용하지 않는다. ""와 " "은 허용한다

 

# @NotEmpty

- Null와 "" 둘다 허용하지 않는다 

 

# @NotBlank

- null와 "", " " 모두 허용하지 않는다.

 

@Size

- name의 최소, 최대 사이즈를 지정

 

@Min @Max

- @Size 에서 min, max를 의미하며 똑같이 message를 속성에 추가할 수 있다

 

@Email

- 메일 형식이 아닌경우 예외를 던지도록 설정할 수 있다

 

 

 

#스프링 프레임워크 #스프링 컴포넌트스캔 #어노테이션