JPA 구성요소

wujin·2023년 3월 23일
0

1. Entity

  • 데이터베이스의 테이블과 매핑되는 객체이다.
  • @Entity 어노테이션을 사용하여 정의한다.
  • 각 Entity는 고유하게 식별되어야 하며, @Id 어노테이션으로 Primary Key를 정의한다.

Entity 생명주기 4가지 상태

  1. Transient(임시) 상태
    • 새로운 엔티티 객체를 생성한 상태
    • 이 상태에서는 영속성 컨텍스트(persistence context)와 관련이 없다.
  2. Persistent(영속) 상태
    • 영속성 컨텍스트에 엔티티 객체가 저장된 상태
    • 엔티티 매니저(Entity Manager)를 통해 엔티티를 저장하면 영속 상태가 된다.
  3. Detached(분리) 상태
    • 영속성 컨텍스트에 저장된 엔티티 객체가 영속성 컨텍스트에서 분리된 상태
    • 분리된 객체는 데이터베이스와의 동기화가 끊겨서 엔티티 매니저를 통해 수정하거나 삭제할 수 없다.
    • 다시 영속 상태로 만들기 위해서는 엔티티 매니저의 merge() 메소드를 사용하면 된다.
  4. Removed(삭제) 상태
    • 영속성 컨텍스트에서 삭제된 엔티티 객체
    • 이 상태에서도 데이터베이스와의 동기화가 유지되기 때문에, 커밋 시점에 데이터베이스에서 삭제된다.

엔티티의 상태는 엔티티 매니저를 통해 변경된다. 엔티티 매니저의 persist() 메소드를 호출하면 Transient 상태에서 Persistent 상태로, remove() 메소드를 호출하면 Persistent 상태에서 Removed 상태로, detach() 메소드를 호출하면 Persistent 상태에서 Detached 상태로 변경된다.

2. EntityManager

  • 엔티티 객체를 관리하는 역할을 수행하는 클래스이다.
  • 영속성 컨텍스트를 관리하고 데이터베이스와의 통신을 담당한다.

영속성 컨텍스트(Persistence Context)

  • "Entity를 영구 저장하는 환경"이라는 의미이다.
  • 데이터베이스 트랜잭션(Transaction) 내에서 엔티티 객체의 상태를 추적하고, 엔티티 객체의 변화를 데이터베이스에 반영한다.

  1. 엔티티 매니저 내에 존재한다.
  2. 데이터베이스와는 별도로 존재하며, 엔티티 객체의 생명주기를 관리한다.
  3. 엔티티 객체가 영속성 컨텍스트에 저장될 때, 해당 엔티티의 식별자(primary key)가 생성된다.
  4. 영속성 컨텍스트 내에서 엔티티 객체를 검색할 때, 먼저 영속성 컨텍스트 내부 캐시에서 검색하고, 없을 경우 데이터베이스에서 검색한다.
  5. 영속성 컨텍스트 내부 캐시에 저장된 엔티티 객체는 다른 트랜잭션에서도 공유할 수 있다.
  6. 엔티티 객체의 상태 변화를 추적하여, 데이터베이스에 반영하는 책임을 지닌다. 즉, 트랜잭션이 커밋될 때, 영속성 컨텍스트는 엔티티 객체의 변경 내용을 데이터베이스에 반영한다.

3. EntityManagerFactory

  • EntityManager를 생성하기 위한 공장 역할을 한다. 즉, 데이터베이스 연결과 같은 한 번만 생성해야 하는 작업을 담당하며, 이를 통해 여러 개의 EntityManager를 생성할 수 있다.
  1. 애플리케이션 전역에서 하나의 EntityManagerFactory를 생성한다.
  2. EntityManagerFactory를 생성하는 과정은 매우 비용이 크며, 메모리를 많이 사용한다.
  3. 따라서 애플리케이션에서 EntityManagerFactory는 한 번만 생성하고, 이후에는 재사용하는 것이 좋다.
  4. EntityManagerFactory를 생성할 때는 persistence.xml 파일을 사용하여, 데이터베이스 연결 정보 및 JPA 설정을 지정한다.
  5. EntityManagerFactory를 사용하여 EntityManager를 생성할 수 있다.

4. JPQL

  • 엔티티 객체를 대상으로 하는 쿼리 언어이다.
  • SQL과 유사한 문법을 가지고 있지만, 엔티티 객체를 직접 다루므로, 데이터베이스 테이블과는 관계가 없다.
  • 엔티티 객체의 필드를 사용하여 쿼리를 작성할 수 있다.
  • 객체 그래프를 따라 엔티티 객체를 탐색할 수 있다.
  • 엔티티 객체 간의 연관관계를 이용하여 쿼리를 작성할 수 있다.
  • JPQL은 데이터베이스와 독립적이므로, 데이터베이스 변경에 영향을 받지 않는다.
@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private int age;
    // getter, setter, constructor
}

EntityManager em = entityManagerFactory.createEntityManager();
String jpql = "SELECT m FROM Member m WHERE m.age > :age";
List<Member> members = em.createQuery(jpql, Member.class)
                    .setParameter("age", 20)
                    .getResultList();

위 코드는 Member 엔티티에서 나이(age)가 20 이상인 회원을 조회하는 JPQL입니다. JPQL은 엔티티 객체를 대상으로 쿼리를 작성하므로, "Member"라는 엔티티 객체를 대상으로 "m"이라는 별칭을 사용하여 조회합니다.

JPQL은 SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY 등의 기능을 지원합니다. 이 코드에서는 SELECT와 WHERE 절을 사용하였습니다. SELECT 절에서는 "m"이라는 별칭을 사용하여 Member 엔티티의 모든 필드를 조회합니다. WHERE 절에서는 "m.age > :age"라는 조건을 사용하여, 나이가 20 이상인 회원을 조회합니다.

JPQL을 실행하기 위해, EntityManager의 createQuery() 메서드를 사용하여 JPQL 문자열과 반환 타입을 지정합니다. 그리고 setParameter() 메서드를 사용하여 JPQL에 사용되는 파라미터 값을 지정합니다. 이후에 getResultList() 메서드를 호출하여 조회 결과를 반환합니다.

5. 트랜잭션(Transaction)

  • 데이터베이스 상태를 변경하는 일련의 작업을 하나의 논리적인 작업 단위로 묶는 개념
  • Transaction은 ACID 원칙을 따르며, Atomicity(원자성), Consistency(일관성), Isolation(격리성), Durability(지속성)의 네 가지 특성을 가지고 있다.
  1. Atomicity(원자성)
    • Transaction은 하나의 작업 단위로 묶이며, 이 작업이 완전히 실행되거나 실패할 경우 롤백되어야 한다. 따라서 Transaction은 모든 작업이 원자적으로 실행되어야 한다.
  2. Consistency(일관성)
    • Transaction이 실행되기 전과 실행된 후의 데이터베이스 상태는 일관성이 있어야 한다. 즉, 모든 제약 조건과 규칙을 준수하고, 데이터베이스의 무결성이 유지되어야 한다.
  3. Isolation(격리성)
    • Transaction은 서로 격리되어야 하며, 하나의 Transaction이 완전히 실행되기 전까지는 다른 Transaction이 해당 데이터에 접근할 수 없다.
  4. Durability(지속성)
    • Transaction이 완전히 실행되면, 해당 작업의 결과는 영구적으로 데이터베이스에 저장되어야 한다.

JPA에서 Transaction은 EntityManager를 통해 제어된다. EntityManager는 트랜잭션을 시작하고 커밋 또는 롤백하는 메서드를 제공한다. 트랜잭션은 다음과 같은 방식으로 사용된다.

EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();

try {
    transaction.begin();

    // 엔티티를 수정, 추가, 삭제하는 작업을 수행한다.
    // ...
    
    transaction.commit();
} catch (Exception e) {
    if (transaction.isActive()) {
        transaction.rollback();
    }
} finally {
    entityManager.close();
}
  1. EntityManager.getTransaction() 메서드를 호출하여 Transaction 객체를 가져온다.
  2. Transaction 객체의 begin() 메서드를 호출하여 트랜잭션을 시작한다.
  3. 엔티티를 수정, 추가, 삭제하는 작업을 수행한다.
  4. Transaction 객체의 commit() 메서드를 호출하여 변경사항을 데이터베이스에 반영한다. 만약 예외가 발생하면 rollback() 메서드를 호출하여 이전 상태로 되돌린다.
  5. Transaction 객체를 close() 메서드를 호출하여 종료한다.

6. Mapping

  • 객체와 데이터베이스 간의 데이터를 변환하는 기능을 의미한다.
  • 객체는 계층 구조, 다중성, 상속 등 다양한 기능을 가지고 있지만, 데이터베이스는 테이블, 칼럼, 제약 조건 등의 구조로 이루어져 있다. Mapping은 이러한 객체와 데이터베이스 간의 차이를 해결하기 위해 객체를 데이터베이스에 저장하고 조회할 때 어떻게 변환할지를 정의한다.
  1. 객체와 테이블 매핑(Objcet-Relational)
    객체와 테이블 간의 매핑을 정의한다. 이러한 매핑을 통해 객체를 데이터베이스에 저장하고 조회할 수 있다. 대표적인 어노테이션으로는 @Entity, @Table, @Column 등이 있다.

  2. 연관관계 매핑(Objcet-Objcet)
    객체와 객체 간의 관계를 정의한다. 객체 간의 관계를 테이블 간의 관계로 변환하여 데이터베이스에 저장하고 조회할 수 있다. 대표적인 어노테이션으로는 @OneToOne, @OneToMany, @ManyToOne, @ManyToMany 등이 있다.

7. Life Cycle Callback Methods

Entity의 상태 변화에 따라 호출되는 메서드를 정의하는 방법이다. 즉, Entity의 상태 변화가 발생할 때마다 특정 메서드를 자동으로 호출하여 개발자가 원하는 작업을 수행할 수 있도록 한다.

  1. @PrePersist
    • Entity가 데이터베이스에 저장되기 전 호출
  2. @PostPersist
    • Entity가 데이터베이스에 저장된 후 호출
  3. @PreUpdate
    • Entity가 데이터베이스에서 업데이트되기 전 호출
  4. @PostUpdate
    • Entity가 데이터베이스에서 업데이트된 후 호출
  5. @PreRemove
    • Entity가 데이터베이스에서 삭제되기 전 호출

이러한 Callback Methods는 주로 Entity의 상태 변화에 따른 보조 작업을 수행하기 위해 사용된다. 예를 들어, 데이터베이스에 저장되기 전에 입력값을 검증하거나, 저장 시간을 기록하거나, 삭제 시 관련 데이터를 함께 삭제하는 등의 작업을 수행할 수 있다.

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Transient
    private boolean modified;

    @PrePersist
    public void prePersist() {
        System.out.println("prePersist() called");
    }

    @PostPersist
    public void postPersist() {
        System.out.println("postPersist() called");
    }

    @PreUpdate
    public void preUpdate() {
        System.out.println("preUpdate() called");
    }

    @PostUpdate
    public void postUpdate() {
        System.out.println("postUpdate() called");
    }

    @PreRemove
    public void preRemove() {
        System.out.println("preRemove() called");
    }

    // getter, setter, constructor 생략
}

0개의 댓글