본문 바로가기

Server Development/Data API

JPA - EntityManager, EntityMapping, Context

 

이번에는 ORM-JPA-Hibernate 가 어떻게 동작을 하는지 자세히 알아보려고 한다.

또한 Spring Data JPA를 사용하는 것과 안하는 것의 차이 또한 같이 알아보려고 한다.

 

데이터를 처리하는 Layer를 DAO라고 하고 저장하려는 DB가 Mysql이라고 가정하자.

이때, 동작 과정은 다음과 같을 것이다.

  • DAO에 데이터 전달
  • DAO가 MySql에 데이터 전달
  • Mysql은 데이터 저장
  • Mysql은 데이터를 DAO에게 반환한다.

 

이 때, 서로 데이터 전달, 반환을 돕는 것을 JPA가 한다고 하였다.

내부 구조를 좀 더 상세히 보려고 한다.

  • Service Layer에서 DTO를 Entity로 변환 후 DAO에 전달
  • DAO는 설정 정보를 주입받은 EntityManagerFactory 객체 생성
  • EntityMangerFactory를 통해 EntityManager 객체 생성
  • EntityManger를 통해 Transaction 객체 생성
  • Transaction 시작
  • EntityManager의 메서드를 통해 데이터 CRUD 요청
  • EnrtityManagerFactory에 주입한 DB 정보에 맞추어 쿼리문 생성
  • EnrtityManagerFactory에 주입한 JDBC Driver 정보에 맞추어 DB 연결후 데이터 전달
  • DB는 데이터를 저장 후 반환해야할 정보 반환
  • 오류 발생시 Transaction은 RollBack 처리, 오류 없다면 commit처리
  • EntityManager 종료
  • EntityManagerFactory 종료

 

정말 큰 틀은 위와 같다. 여기서 Spring Data JPA와 비교해가면서 상세히 하나하나 설명해보려고 한다.

 

 

 

 

1. Service Layer에서 DTO를 Entity로 변환 후 DAO에 전달

개발자에 따라 다르겠지만 나는 보통 Servie에서 DAO에 데이터를 전달할 떄 DTO에서 Entity로 데이터를 변경하고 전달한다. 

여기서 DTO는 자바환경에서 사용하는 데이터 클래스이고 Entity는 이러한 자바 데이터를 DB에 저장하기 위한 DB형식에 맞추어 만든 Entity를 의미한다.

 

EntityManagerFacotry enf = Persistence.createEntityManagerFactory("주입하는 빈의 이름");

 

 

 

 

 

선언 해줘야할 애너테이션

  • @Entity : 이 클래스가 Entity임을 선언
  • @Table("TableName") : 이 Entity가 저장될 테이블 명을 설정 
  • @Id : 이 Entity의 아이디, 즉 식별자가 무엇인지 정해주며 프로퍼티 위에 선언
  • @NoArgsConstructor : Default 생성자를 위해서 사용하는 애너테이션으로 Lombok 라이브러리를 통해 사용할 수 있다. 이는 나중에 EntityManager가 데이터를 DB에서 가져와 DAO로 돌려줄때, 꼭 필요하다.
  • @Getter, @Setter, @Tostring : 나중에 Entity를 변경하거나 출력하기 위해서 사용된다.

 

 

 

 

 

 

(+) 각 애너테이션 설명 및 주요 사항

 

1. @Entity

- 자바의 객체를 DB의 테이블과 매핑

- 애너테이션만 설정하며 클래스이름이 테이블이름으로 설정된다.

- JPA는 default 생성자로 객체 생성 후, 세터를 통해 매핑하므로 둘 다 필요하다.

- final, 인터페이스, 열거형, 내부 클래스들은 기본적으로는 영속 객체가 되지 못한다.

- 따라서 final은 필드에 설정하지 않음.

 

 

 

2. @Table

- name 옵션 : 테이블 명 지정

- catalog 옵션 : 데이터베이스의 catalog 매핑

- schema 옵션 : 데이터베이스의 스키마 매핑 

- uniqueConstraints 옵션 : DDL(테이블 CRUD 명령어) 생성에서 unique 제약 조건 생성(자동 생성 시)

 

- Schema의 옵션 : create, create-drop, update, validate, none

 

 

 

3. @Access

- 객체를 DB와 매핑시 필드를 통해 접근할 것인지, setter/getter를 통해 접근(프로퍼티 접근)할 것인지 설정

- 자주 사용하지 않는다.

- 주로, @Id를 필드위에 선언시 게터/세터를 통한 접근을 하게 되고 세터/게터 위에 @Id 선언시 필드를 통해 접근함을 통용

- 필드 접근 방식을 주로 사용, 세터/게터 접근시 문제가 존재.

 

 

 

4. @Id

- 기본키 생성

- 자바에서 기본키를 생성하는 경우 vs DB에서 기본키를 생성하는 경우

- float, double, Timestamp는 사용을 피해야 함.

- @IdClass, @EmbaddedId : 복합키 관련

 

자바에서 기본키를 생성하는 경우 : @Id

DB에서 기본키를 생성하는 경우 : @Id + @GeneratedValue

예시) @GeratedValue(Strategy = GenetationType.IDENTITY);

  • identity : 기본키 생성 처리를 데이터베이스에 위임, Mysql, Oracle 등에서 사용 가능, persist() 시점에 실행(예외 적으로 커밋 전 insert 진행)
  • sequence : sequence 오브젝트를 이용한 기본키 생성, sequence 전략을 사용하는 DB(Oracle 등)에서 사용 가능, 추가적으로 @SequeceGenerator 필요 (커밋 후에 insert 진행)
  • table : 기본키 생성을 위한 별도의 테이블을 생성하고 이를 통해 기본키 생성, DB 종류 상관없이 사용 가능, 추가적으로 @TableGenerator 사용, GenetationType.TABLE, 테이블 생성-테이블로부터 키를 가져옴-사용 구조, allocation=50이라고 가정한다면 50개의 아이디를 미리 만들어놓고 가져와 사용하는 방식
  • auto : 데이터 베이스의 종류에 따른 생성 전략 선택 (default)

 

 

5. @Column

- name : DB 칼럼의 이름

- nullable = true or false (해당 값 널 가능 여부)

- length : 문자열의 길이 지정

- unique : Unique 제약 조건 여부

- precision : 숫자의 전체 자리수 설정

- scale : 소수점 이하 자리수 설정

- insertable : insert가 일어날 때 칼럼이 저장될지에 대한 여부(default : true)

- updatable : 엔터티가 수정될 때 칼럼이 수정될지에 대한 여부(default : true)

- table : 칼럼이 속한 테이블 지정

- columnDefinition : 칼럼에 대한 정의 지정

 

- @Column 사용안할시 기본적으로 필드들은 @Basic(기본 매핑 매카니즘)이 사용되고 있다고 생각하면 됨. 

 

 

 

6. @Temporal

- 자바의 DATE 클래스 형태를 가진 필드를 어떤 형태로 데이터 베이스에 지정할지 설정

- LocalDate, LocalTime, LocalDateTime 사용시 사용할 필요없이 호환되어 저장 됨. 

- Temopral.Type.DATE : 날짜(년, 월, 일)

- Temopral.Type.TIME : 시간(시, 분, 초)

- Temopral.Type.TIMESTAMP : 날짜와 시간

 

 

 

7. @LoB

- BLOB, CLOB 등 데이터베이스에 용량이 큰 데이터 저장시 사용하는 애너테이션

- @Basic(fetch=FetchType.LAZY)를 기본적으로 같이 설정해 불러올 때 해당 데이터를 안 불러올 수도 있게끔 설정

 

 

 

8. @Enumerated

- ENUM 타입에 사용

- ENUM 생성 후, ENUM 속에 내용을 타입으로 설정해 사용

 

 

 

9. @Transient

- 특정 필드를 영속화하고 싶지 않을 때 사용

 

 

 

 

 

 

 

 

2. DAO는 설정 정보를 주입받은 EntityManagerFactory 객체 생성 후 EntityManager 생성

  • EntityManagerFactorty : EntityManager를 만들기 위해서 생성하는 인터페이스로 생성시 설정파일에 설정한 빈들을 주입해서 넣는다. 팩토리패턴의 한 구조임.
  • 주입할 필수 설정정보 : 연결할 DB, 어떤 DB의 쿼리문을 사용할지, 사용할 JDBC Driver 이름과 DB와 Application을 연결하기 위해 필요한 아이디, 비번, 주소
  •  EntityManager : Transaction 생성, 쿼리문 생성 메서드 등이 있으며 EntityManagerFactory를 통해 생성가능하다.

 

EntityManager em = emf.createEntityManager();

 

추가적으로 EntityManager는 영속성 컨텍스트(PersistenceContext)를 통해 영속 객체를 관리한다 라는 말을 많이 들어봤을 것이다.

여기서 영속 객체는 영속성 컨텍스트에 들어간 Entity를 의미하며 영속성 컨텍스트는 하나의 공간, 상황(app)이라고 생각하면 된다.

 

 

 

 

 

 

 

 

영속성 컨텍스트(PersistenceContext) - 캐시(ID, Entity Object, SnapShot), SQL 저장소

 

하나의 application을 생각해보면, 예를 들어, 로그인을 한다고 가정하면 사용자로부터 로그인 정보를 받아서 일련의 과정을 처리한다. 이때 다양한 작업을 통해 일련의 과정을 처리하는데, Entity Manager 또한 하나의 application 형태로 데이터를 저장하고 관리를 한다.

 

정의

- 고유 ID를 갖는 모든 영속 객체 인스턴스 집합을 의미.

- 영속 객체의 생명주기를 관리한다.

- 관리는 EntityManager의 메서드를 통해 관리된다.

 

영속 객체의 상태 (Life Cycle)

- New : 비영속 상태

- Managed : 영속 상태

- Detached : 준영속 상태, 영속성 컨텍스트에서 관리되다 분리된 상태, detach() 사용하며 컨텍스트로 부터 잠시 빼두는 경우 사용. 이 후, merge()를 통한 합병

- Removed : 영속 컨텍스트를 비울 때의 객체 상태

 

과정

  • 1. 자바에서 만들은 Entity 객체를 EntityManager가 받는다.
  • 2. EntityManager는 Persistence Context에 해당 Entity의 객체를 인스턴스로 올린다. 즉, 해당 객체를 영속화영속객체로 만든다. 또한 이는 1차캐시에서 관리된다고 하고 DB에 아직 반영은 안된 상태이다.
  • -> 만약 한 Transaction 속에서 다른 CRUD가 더 있다면? -> 그걸 반영 후 Transaction이 commit()되는 시점에 DB에 저장
  • -> 없다면? -> commit()되는 시점에 DB에 저장
  • 3. EntityManager 종료시 Persistence Context 환경(application) 종료 

"Persistence Context는 기본적으로 Transaction 생성과 종료(commit or rollback) 사이의 상황이다."

 

 

추가적인 여러가지 상황에 대한 내용 및 추가 용어 정리

  • 예를 들어, 동일한 객체를 persist()를 두번하여 context에 올리면 인스턴스는 하나만 올라간다. 그 이유는 컨텍스트는 아이디 단위로 인스턴스를 저장하는데 동일한 아이디 존재시 또 저장하지 않음.
  • context(상황) 안에 있는 영속객체에 대해서 수정시도를 하면(settter 등을 이용한) 객체는 수정됨. (이때, 영속성 컨텍스트 안에 SQL 저장소에 UPDATE 쿼리가 생겨서 commit 시점에 반영되는 개념이라 update 등의 쿼리 작성 없이 set 만으로 수정 가능)
  • find를 통한 객체 검색시 해당 검색하려는 객체가 갖는 아이디와 동일한 영속객체가 컨텍스트 안에 존재시 DB에 접근하지 않음 즉, 컨텍스트는 캐시의 역할도 수행한다. 예를 들어, A라는 객체를 persist() 후 find()를 실행하면 컨텍스트가 종료되지 않았다는 전제하에 이미 인스턴스에 존재하기 때문에 DB에 안가고 find를 한다.
  • flush() : 영속성 컨텍스트안에 객체들을 DB와 동기화 하는 개념, 커밋은 안돼서 DB에 저장은 안되지만 특정 객체를 저장 후 해당 객체를 포함해 관련 객체들을 SELECT 함께 불러오는 경우와 같은 상황에 사용한다. 이 후, 커밋해야 DB에 저장된다.

 

flush()의 실행위치

- EntityManager.flush() 를 통한 호출

- 트랜잭션의 커밋시 자동으로 전에 호출됨

- JPQL 쿼리 실행을 통한 자동 호출

 

 

 

 

 

 

 

 

 

 

3.  데이터 처리

Transaction 객체 생성

데이터를 처리하기 위해서는 우선, Transaction 객체를 생성해야 한다. 이는 EntityManager를 통해 생성할 수 있으며 간단하게 설명하면 데이터가 잘 CRUD 되었는지 즉, 데이터의 원자성이 잘 유지되었는지를 확인해주는 것이다. 예를 들어, 돈을 송금한다면 한 명의 계좌에서는 돈이 빠져야하고 한명의 계좌에는 돈이 들어가야한다. 이러한 일련의 과정이 잘 끝났는지 확인을 해주는 객체이며 실패시 rollback(), 성공시 commit()처리를 통해 데이터의 원자성을 유지한다.

 

EntityTransaction tx = em.getTransaction();

 

EntityManager를 통한 CRUD

데이터를 이제 삽입, 검색, 삭제, 변경을 해야한다.

 

삽입

em.persist(Entity 객체) : 해당 Entity 객체를 저장한다.

(상세 구조 : select-insert)

 

검색

Entity이름 test = em.find(Entity명.class, id); : Id로 Entity 객체를 찾는다.

(추가적으로 find의 원리는 해당 정보를 찾은 후 새로운 Entity 객체를 생성 후 거기에 데이터를 넣어서 반환하는 구조이다 따라서 Entity 객체는 꼭 @NoArgsConstructor가 필요하다.)

 

수정

Entity이름 test = em.find(Entity명.class, id); : Id로 Entity 객체를 찾는다.
test.setName("test2"); : 이름 수정한다.

(이 후, 다른 작업을 안해도 수정이 되는데, 이는 현재 EntityManager가 작업중에 있고 즉, 영속컨텍스트 안에 존재하고 저 상태로 끝나면 수정을하고 영속컨텍스트가 종료 되기 때문에 수정이 되는 것이다. 세부적으로는 select 후 update하는 구조이다.)

 

삭제

em.remove(객체) : 삭제하려는 객체를 삽입해 삭제한다.

 

이 후 처리

em.flush() // 중간 저장 내용 있을시 or 작성 없을 시 알아서 동작
tx.commit(); or tx.rollback():
em.close();
enf.close();

 

Spring Data JPA와 차이점은 

우선 Spring Data JPA는 위의 과정 중 자주 쓰이는 것들을 묶어서 개발자에게 제공하는 방식이다.

EntityManagerFactory, EntityManager 등 객체를 생성할 필요가 없고 JpaRepository라는 인터페이스를 만들어 DAO에 주입하는 형태이다. 또한 수정 같은 경우에도 두번의 연산과정없이 알아서 수정해준다.

 

 

 

 

'Server Development > Data API' 카테고리의 다른 글

JPA - @Query with Spring JPA  (0) 2023.04.05
JPA - Query Method with Spring JPA  (0) 2023.04.05
JPA - Auditing  (0) 2023.04.04
JPA - Spring JPA  (0) 2023.03.29
Data API - Basic Concept  (0) 2023.03.28