JPA - 엔티티와 영속성

들어가기 전에

JPA

JPA는 Java의 ORM 기술 표준이다.

태초에 ORM이 있었다

ORM은 Object-Relational Mapping 의 두문자어이며, 이를 번역하여 객체-관계 매핑이라고 이르는 문서도 많이 보인다. 이름을 보면, 대충 OOP와 RDB에 관련된 기술 같아 보이는데, 짐작대로 ORM은 객체지향 언어의 객체와 관계형 데이터베이스의 데이터 사이에서 나타나는 미묘한 차이와 이로 인한 문제들을 극복하기 위해 등장한 기술이다.

객체지향과 데이터

객체는, 보통 데이터와 관련된 연산의 집합 을 이르는 말이다. 데이터의 묶음은 보통 자료구조라고 한다.(보통, 자료구조에는 데이터 입출력에 관련한 연산이 수반되는 경우가 많다.) 객체의 큰 특징으로 캡슐화 , 추상화 , 다형성 , 상속 의 네 가지를 드는 경우가 많다.

관계형 데이터베이스에서, 데이터는 연산을 가지지 않는다. 상속이나 캡슐화 등이 될 리는 더더욱 만무하다. 대신, 데이터는 외래 키를 사용한 양방향 관계 를 가질 수 있는데, 이러한 양방향 관계는 객체에서 참조를 통해 유사하게 구현할 수 있지만, 완전히 일치하게 구현하기 위해서는 많은 개발 비용이 든다.

public class Reader {
    private long id;
    // ...
    private List<Book> bookList;
}

public class Book {
    private long id;
    // ...
    private Reader reader;
}

간단히 말하면, 생각보다 두 개념을 예쁘게 붙이기는 힘들다는 것이다.

그래서 ORM은…

JPA가 필요할 때

물론 JDBC를 생짜로 사용하는 사람은 별로 없다, MyBatis가 있으니까. 그러면 JPA를 언제 사용하는가? 많은 서적이나 개발 회고에서, 아래와 같은 경우에 JPA 도입을 추천, 또는 검토하였다.

반복되는 SQL 작성

JPA를 사용하여, 개발자는 SQL 문장을 Java 코드로 대체할 수 있다. 몇 몇 프레임워크는, 특정 클래스를 상속하는 것으로 원하는 데이터에 대한 CRUD 메서드를 자동으로 생성해 개발자에게 제공한다. 그대로 사용하기엔 여러 문제점이 있지만(딜리트는 무섭다), 이 정도면 꽤 혹하지 않는가.

SQL 방언

DBMS마다 SQL 문법이 일부 다른 경우가 있는데, 이러한 문법적 차이가 발생하는 부분을 SQL 방언이라고 한다(그렇다는 것 같다).

SELECT *
FROM table
LIMIT 1, 10;
SELECT *
FROM (
    SELECT page.* 
    FROM (
        SELECT *
        FROM table 
        ) page
    WHERE rnum <= 10
    )
WHERE rnum >= 1;

JPA는 연결한 DB 종류에 따라 적절한 SQL을 스스로 생성하여, 서비스가 DBMS 중립적이 될 수 있도록 한다.

Entity

개념과 선언

엔티티 클래스는 테이블과 매핑되는 클래스이다. 엔티티는 테이블의 데이터로 사상된다(아마도).

엔티티 클래스의 선언은 XML 파일에 작성하는 방법과 Java 파일에 직접 애노테이션으로 작성하는 방법이 있는데, 나는 가능하면 애노테이션을 사용하는 편을 선호한다.(나중에 읽기가 편하다)

@Entity , @Table , @Colum 등의 애노테이션을 사용하여 적절히 엔티티 클래스를 구성하고, 완성된 엔티티 클래스는 명세를 기반으로 테이블과 매핑된다. 애노테이션에 대한 자세한 설명은 생략한다.

@Entity
@Table(name = "reader")
public class Reader {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Colum(name = "id")
    private long id;
    // ...
}

엔티티의 상태

엔티티 클래스를 바탕으로 생성된 엔티티는 생성부터 제거되기까지 4가지의 상태를 가질 수 있다.

단어만 봐서는 감이 잘 오지 않는다. 엔티티의 상태는 트랜잭션, 즉 데이터의 입출력 상태와 관련이 깊다. 간단한 설명을 아래에 추가하였다.

NEW / 비영속

엔티티를 생성한 시점에서 트랜잭션 구간에 진입하기 전까지, 엔티티는 비영속 상태를 유지한다. 이 상태에서는 엔티티는 데이터베이스와 전혀 관계가 없고, JPA의 어떤 특징도 보이지 않는, 그냥 평범한 객체이다.

Reader reader = new Reader();

MANAGED / 영속

엔티티가 트랜잭션 영역에 진입하여 엔티티 매니저의 관리 하에 들어가면 해당 트랜잭션 구간동안 엔티티는 영속 상태가 된다. 영속 상태에서 엔티티는 몇 가지 중요한 특징을 가지게 된다.

DETACHED / 준영속

엔티티가 커밋되어 트랜잭션 구간에서 빠져나오는 경우, 이 엔티티는 준영속 상태가 된다. 사실상 비영속 상태와 거의 같다.

REMOVED / 삭제

엔티티가 트랜잭션 구간 내에서 관련 메서드에 의해 삭제되는 경우, 매핑되는 데이터의 삭제와 함께 엔티티 또한 삭제 상태가 된다. 객체는 사용 가능한 상태이나, 재활용 하지 않는 편이 좋다.

Entity Manager

엔티티 매니저는 이름과 같이, 엔티티를 관리하는 객체이다.

매니저의 책임이 전부 영속성 엔티티의 CRUD에 관련되어 있다. 엔티티 매니저는 영속성 상태의 엔티티 관리를 위해 DB 세션과 밀접한 연관을 가지기 때문에, 하나의 엔티티 매니저를 여러 스레드에서 공유하여 사용하면 위험하다. 스레드-안전한 엔티티 매니저 팩토리를 공유하여 각 스레드에서 엔티티 매니저를 생성하는 방식이 권장된다.

엔티티 매니저와 영속성

엔티티 메니저에 의해 관리되는, 영속성 상태의 엔티티는 고유한 식별값(ID)으로 구분되어 관리된다. 즉, 영속성 상태에 있는 모든 엔티티는 식별값을 가지고 있어야 한다.

영속성 상태에 있는 엔티티는 아래의 특징을 가진다

주의가 필요한 부분

Flush

영속 상태의 엔티티를 수정하면, 트랜잭션 종료 단계(커밋)에서 해당 엔티티에 대응하는 데이터에 업데이트가 발생한다.

  1. 트랜잭션 구간 내에서 (영속성)엔티티 수정
  2. 트랜잭션 종료
  3. 변경 사항 커밋

일부 객체의 getter / setter 로직에 따라(입력 값에 필터를 적용하거나) 개발자가 의도하지 않은 업데이트가 발생할 수 있다는 문제점이 있다.

“왜 데이터가 날아간걸까” “난 데이터가 덮어써지던데” “?” “?”

이를 잘 고려하여 엔티티 클래스를 정의할 필요가 있다.

DDL.auto

JPA는 프로젝트의 엔티티 클래스 명세들로부터 이에 대응하는 테이블을 데이터베이스에 자동으로 생성하는 기능을 가지고 있다. 이 때, 자동으로 생성되는 DDL은 완벽하진 않으나 참고할 수준은 된다. 이 기능을 사용하는 하이버네이트 설정 예시는 아래와 같다.

<property name="hibernate.hbm2ddl.auto" value="create">

value에는 옵션이 들어간다. 옵션을 아래를 참고.

옵션 동작 주의사항 요약하면
create 애플리케이션 시작 시 엔티티 클래스 명세를 바탕으로 테이블을 추가하는 DDL을 생성하여 커밋한다. 기존에 존재하는 테이블은 드랍 한다. 기존 테이블 드랍 drop + create
create-drop 애플리케이션 시작 시 기존 테이블을 drop + create 후 애플리케이션 종료시 존재하는 테이블을 모두 드랍한다. 기존 테이블 드랍 drop + create + drop
update 엔티티 클래스 명세를 기준으로 기존 테이블을 업데이트 한다. 기존 테이블 변경(할 가능성이 있음) update
valid 애플리케이션 시작 시 엔티티 클래스와 테이블 스키마가 일치하는지 여부를 확인한다. 일지하지 않으면, 경고 메세지와 함께 애플리케이션 시작을 중단한다.    
<property name="hibernate.hbm2ddl.auto" value="asdasdqwejlkanjfclkjhwas123123">

코드를 멀쩡하게 짰으니 제 디비는 안전할 것입니다.

응 드랍~

따라서 우리는 엔티티 매니저 생성 시, 엔티티 매니저 팩토리 생성 옵션을 잘 살펴봐야 한다. 리얼 스테이지에서 DDL 관련 옵션을 사용하는 것은 대단히 위험하므로, 이 기능을 사용하고자 한다면 개발 단계에 따라 설정을 분리할 수 있는 환경을 구축 후, 신중하게 옵션을 결정하자.

뭐든 안 그렇겠느냐만, DB 날아가면 골치아픔.