[JPA] 엔티티 매니저 설정 + 트랜잭션 관리 + 비즈니스 로직

간단한 JPA 애플리케이션 코드를 개발해보자.

- 시작 코드

package jpa_test.start;

import javax.persistence.*;
import java.util.List;

public class JpaMain {
    public static void main(String[] args) {
        
        // [엔티티 매니저 팩토리] - 생성
        EntityManagerFactory emf = 
      	    Persistence.createEntityManagerFactory("jpa_test");
        
        // [엔티티 매니저] - 생성
        EntityManager em = emf.createEntityManager();
        
        // [트랜잭션] - 획득
        EntityTransaction tx = em.getTransaction();
        
        try {
            tx.begin();    // [트랜잭션] - 시작
            logic(em);     // 비즈니스 로직 실행
            tx.commit();   // [트랜잭션] - 커밋
            
        } catch (Exception e) {
            tx.rollback(); // [트랜잭션] - 롤백
        } finally {
            em.close();    // [엔티티 매니저] - 종료
        }
        
        emf.close(); // [엔티티 매니저 팩토리] - 종료
 }
 
 private static void logic(EntityManager em) { ... }

위 코드는 크게 3부분으로 나뉘어 있다.

  • 엔티티 매니저 설정
  • 트랜잭션 관리
  • 비즈니스 로직

하나씩 살펴보자.

 

📂 엔티티 매니저 설정

아래 그림을 보면서 엔티티 매니저의 생성 과정을 분석해보자.

1️⃣ 엔티티 매니저 팩토리 생성

JPA를 시작하려면 우선 persistence.xml의 설정 정보를 사용해 엔티티 매니저 팩토리를 생성해야 한다. 이때 Persistence 클래스를 사용하는데 이 클래스는 엔티티 매니저 팩토리를 생성해서 JPA를 사용할 수 있게 준비한다. 

EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa_test");

이렇게 하면 META-INF/persistence.xml에서 이름이 "jpa_test"인 영속성 유닛(persistence-unit)을 찾아서 엔티티 매니저 팩토리를 생성한다. 

보통 엔티티 매니저 팩토리를 생성하는 비용은 아주 크다. 따라서

엔티티 메니저 팩토리는 애플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용한다. 

2️⃣ 엔티티 매니저 생성

EntityManager em = emf.createEntityManager();

엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다. JPA의 기능 대부분은 이 엔티티 매니저가 제공한다. 엔티티 매니저를 사용해서 엔티티를 데이터베이스에 등록/수정/삭제/조회할 수 있다.

엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)를 유지하면서 데이터베이스와 통신한다. 따라서 엔티티 매니저를 가상의 데이터베이스로 생각할 수 있다. 

‼️ 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있기 때문에 스레드 간에 공유하거나 재사용하면 안된다!

3️⃣ 종료

사용이 끝난 엔티티 매니저는 다음처럼 반드시 종료해야 한다. 

em.close(); // 엔티티 매니저 종료

애플리케이션을 종료할 때 엔티티 매니저 팩토리도 다음처럼 종료해야 한다.

emf.close(); // 엔티티 매니저 팩토리 종료

📂 트랜잭션 관리

JPA를 사용하려면 항상 트랜잭션 안에서 데이터를 변경해야 한다. 트랜잭션 없이 데이터를 변경하면 예외가 발생한다. 트랜잭션을 시작하려면 엔티티 매니저(em)에서 트랜잭션 API를 받아와야 한다. 

EntityTransaction tx = em.getTransaction(); // 트랜잭션 API

try {
    tx.begin();    // [트랜잭션] - 시작
    logic(em);     // 비즈니스 로직 실행
    tx.commit();   // [트랜잭션] - 커밋

} catch (Exception e) {
    tx.rollback(); // 예외 발생 시 트랜잭션 롤백
}

트랜잭션 API를 사용해서 비즈니스 로직이 정상 동작하면 트랜잭션을 커밋하고 예외가 발생한다면 트랜잭션을 롤백한다. 


📂 비즈니스 로직

회원 엔티티를 생성한 다음 엔티티 매니저를 통해 데이터베이스에 등록, 수정, 삭제, 조회를 해보자.

- 비즈니스 로직 코드

public static void logic(EntityManager em) {
    
    String id = "id1";
    Member member = new Member();
    member.setId(id);
    member.setUsername("철수");
    member.setAge(25);
    
    // 등록
    em.persist(member);
    
    // 수정
    member.setAge(26);
    
    // 단 건 조회
    Member findMember = em.find(Member.class, id);
    System.out.println("findMember = " + findMember.getUsername()
        + ", age = " + findMember.getAge());
    
    // 목록 조회
    List<Member> members = 
        em.createQuery("select m from Member m", Member.class).getResultList();
        
    // 삭제
    em.remove(member);
}

위 코드를 보면 등록, 수정, 조회, 삭제 작업이 엔티티 매니저를 통해서 수행되는 것을 확인할 수 있다. 

하나씩 코드를 분석해보자. 

📄 등록 - em.persist()

String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("철수");
member.setAge(25);

// 등록
em.persist(member);

엔티티를 저장하려면 엔티티 매니저의 persist() 메소드에 저장할 엔티티를 넘겨주면 된다. 

JPA는 회원 엔티티의 매핑 정보(어노테이션)를 분석해서 다음과 같은 SQL을 만들어 데이터베이스에 전달한다.

INSERT INTO MEMBER (ID, NAME, AGE) VALUES ('id1', '철수', 25)

📄 수정

// 수정
member.setAge(26);

엔티티의 수정은 단순하게 엔티티의 값만 변경하면 된다. JPA는 어떤 엔티티가 변경되었는지 추적하는 기능을 갖추고 있다. 따라서 위와 같이 엔티티의 값만 변경하면 다음과 같은 UPDATE SQL을 생성해서 데이터베이스에 값을 변경한다. 

UPDATE MEMBER
    SET AGE=26, NAME='철수'
WHERE ID='id1'

📄 삭제 - em.remove()

em.remove(member);

엔티티를 삭제하려면 엔티티 매니저의 remove() 메소드에 삭제하려는 엔티티를 넘겨준다. JPA는 다음 DELETE SQL을 생성해서 실행한다.

DELETE FROM MEMBER WHERE ID='id1'

📄 단 건 조회 - em.find()

// 한 건 조회
Member findMember = em.find(Member.class, id);

find() 메소드조회할 엔티티의 타입@Id로 데이터베이스 테이블의 기본 키와 매핑한 식별자 값으로 엔티티 하나를 조회하는 조회 메소드이다. JPA는 다음 SELECT SQL을 생성해서 데이터베이스에 결과를 조회하고 조회한 결과 값으로 엔티티를 생성해서 반환한다. 

SELECT * FROM MEMBER WHERE ID='id1'

📂 JPQL

하나 이상의 회원 목록을 조회하는 코드를 자세히 살펴보자.

// 목록 조회
TypedQuery<Member> query = 
    em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();

JPA는 SQL을 추상화한 JPQL이라는 객체지향 쿼리 언어를 제공한다. JPQL은 SQL 문법과 거의 유사해서 SELECT, FROM, WHERE ... 등을 사용할 수 있다. 둘의 가장 큰 차이점은 다음과 같다. 

JPQL vs SQL

  • JPQL엔티티 객체를 대상으로 쿼리한다. 쉽게 얘기하면 클래스와 필드를 대상으로 쿼리한다.
  • SQL데이터베이스 테이블을 대상으로 쿼리한다. 

위 코드에서 "select ... m" 부분이 JPQL이다.

중요한 것은 여기서 from Member는 회원 엔티티 객체를 말하는 것이지 MEMBER 테이블이 아니다!!
JPQL은 데이터베이스 테이블을 전혀 알지 못한다.

JPQL을 사용하려면

  1. em.createQuery(JPQL, 반환 타입) 메소드를 실행해서 쿼리 객체를 생성
  2. 쿼리 객체의 .getResultList() 메소드를 호출

JPA는 JPQL을 분석해서 적절한 SQL을 만들어 데이터베이스에 전달한다.