3. JPA API 이해

애플리케이션에서 JPA를 이용하여 CRUD 기능을 처리하려면 EntityManager 객체를 사용해야 한다.

이 EntityManager는 EntityManagerFactory로부터 얻을 수 있다.

 

 

EntityManager 획득 과정)

 

1. Persistence 클래스를 이용하여 JPA 메인 환경설정 파일(persistence.xml)을 로딩함.

2. 영속성 유닛 설정 정보를 바탕으로 EntityManagerFactory 객체를 생성.

3. EntityManagerFactory로부터 EntityManager를 얻어서 데이터베이스 연동을 처리한다.

 

이렇게 얻은 EntityManager 객체를 이용하여 CRUD 관련 작업을 처리할 수 있다.

다만 EntityManager를 이용하여 데이터를 처리할 때, 등록, 수정, 삭제 작업은 반드시 트랜잭션 안에서 처리되어야 한다.

 

 

EntityManager의 메서드)

1. persist(Object Entity) : 엔티티를 영속화한다.

2. merge(Object Entity) : 준영속 상태의 엔티티를 영속화한다.

3. remove(Object Entity) : 영속 상태의 엔티티를 제거한다.

4. find(Class<T> entityClass, Object primaryKey) : 하나의 엔티티를 검색한다(pk를 통해)

5. createQuery(String jpql, Class<T> resultClass) : JPQL에 해당하는 엔티티 목록을 검색한다.

 

 

3.2. 영속성 컨텍스트와 엔티티 상태

 

3.2.1 영속성 컨텍스트 이해하기

 

 

영속성 컨텍스트란 엔티티 객체들을 관리하는 일종의 컨테이너라고 할 수 있다.

영속성 컨텍스트에 저장된 엔티티의 상태는 다음과 같다.

 

비영속(New) : 엔티티가 영속성 컨텍스트와 전혀 무관한 상태

영속(Managed) : 엔티티가 영속성 컨텍스트에 저장된 상태

준영속(Detached) : 엔티티가 한 번 영속성 컨텍스트에 저장되었다가 분리된 상태

삭제(Removed) : 엔티티가 영속성 컨텍스트에서 삭제된 상태

 

 

 

3.2.2 엔티티 상태 이해하기

 

엔티티의 상태에 대해 더 자세히 알아본다.

 

1. 비영속 상태(New)

엔티티 객체를 생성만 했을 뿐 아직 엔티티를 영속성 컨텍스트에 저장하지 않은 상태이다.

 

2. 영속 상태(Managed)

EntityManager를 통해 엔티티가 영속성 컨텍스트에 저장된 상태이다.

persist() 메서드를 사용하거나 find() 메서드를 통해 엔티티를 영속 상태로 만들 수 있다.

 

find() 메서드는 상세 조회의 기능을 가진 메서드지만 이 메서드를 호출했을 때, 조회하고자 하는 엔티티가 영속성 컨텍스트에 있으면 해당 엔티티를 반환하고 만약 없으면 데이터베이스에서 데이터를 조회하여 새로운 엔티티 객체를 생성하여 영속성 컨텍스트에 저장(Managed 상태로 만듦)한다.

 

-> 캐시 역할. 엔티티가 영속성 컨텍스트에 있으면 그걸 사용하고 없으면 DB에서 조회하여 저장.

 

3. 준영속 상태(Detached)

한 번 영속성 컨텍스트에 들어간 엔티티가 어떤 이유에 의해 영속성 컨텍스트에서 벗어난 상태를 의미한다.

영속성 컨텍스트에서 벗어났기 때문에 준영속 상태의 엔티티는 값을 수정해도 데이터베이스에 아무런 영향을 미치지 못한다.

 

준영속 상태로 전환되는 경우는 다음의 메서드를 호출했을 때이다.

 

detach(entity) : 특정 엔티티만 준영속 상태로 전환한다.

clear() : 영속성 컨텍스트를 초기화한다.

close() : 영속성 컨텍스트를 종료한다. 영속성 컨텍스트는 종료되기 직전에 자신이 관리하던 엔티티를 모두 삭제한다.

 

준영속 상태의 엔티티는 메모리에서 완전히 사라진 것이 아니기 때문에 merge() 메서드를 통해 다시 영속상태로 전환될 수 있다.

 

4. 삭제 상태(Removed)

삭제 상태는 엔티티가 영속성 컨텍스트에서도 제거되고 테이블의 데이터도 삭제된 상태이다.

영속 상태의 엔티티는 remove() 메서드를 이용해서 삭제할 수 있다. 

일반적으로 삭제된 엔티티는 재사용하지 않고 가비지 컬렉션이 되도록 내버려둔다.

 

 

3.2.3 영속성 컨텍스트와 1차 캐시

 

EntityManager의 persist() 메서드를 통해 특정 엔티티를 영속성 컨텍스트에 등록하지만

persist()에 의해 JPA가 곧바로 테이블에 INSERT를 실행하는 것은 아니다.

영속성 컨텍스트 내부에 1차 캐시라는 것을 사용해서 엔티티를 일단 등록한다.

1차 캐시는 일종의 Map같은 컬렉션으로서 Key(@Id로 매핑한 식별자 값), Value(앤티티 객체)로 엔티티를 관리한다.

 

1차 캐시에 저장된 엔티티는 EntityTransaction으로 트랜잭션을 종료할 때 비로소 실제 데이터베이스에 반영된다.

 

즉, 트랜잭션 객체의 commit() 메서드를 호출하면 1차 캐시에 저장된 엔티티에 해당하는 INSERT 구문이 생성되고 데이터베이스로 전송된다. 이렇게 영속성 컨텍스트에 저장된 엔티티를 데이터베이스에 반영하는 과정을 플러시(Flush)라고 한다.

 

 

3.3 영속성 컨텍스트와 SQL 저장소 이해하기

3.3.1 엔티티 저장하기

영속성 컨텍스트는 1차 캐시뿐만 아니라 SQL 저장소라는 것도 가지고 있다.

 

엔티티를 persist() 메서드로 영속성 컨텍스트에 저장하면 영속성 컨텍스트는 두 가지 작업을 순차적으로 처리한다.

1. 엔티티를 1차 캐시에 등록한다.

2. 1차 캐시에 등록된 엔티티에 해당하는 INSERT 구문을 생성하여 SQL 저장소에 등록한다.

 

만약 계속해서 새로운 엔티티가 등록되면 등록 순서대로 1차 캐시와 SQL 저장소에 각각 엔티티와 SQL 구문들이 누적되어 자장된다.

그리고 나서 commit() 메서드로 트랜잭션을 종료하면 SQL 저장소에 저장되었던 모든 SQL이 한꺼번에 데이터베이스로 전송되는 것이다.

 

-> 한 번의 데이터베이스 통신으로 SQL 구문을 한꺼번에 처리할 수 있다(마치 JDBC에서 배치 업데이트 하듯이?)

 

3.3.2 엔티티 수정과 스냅샷

 

엔티티를 수정하는 과정은 등록보다 조금 더 복잡한 과정이 필요하다.

 

일단, 엔티티를 수정하기 전에 find()로 수정할 엔티티를 검색해야 한다.(영속화 하는 것)

왜냐하면 엔티티 수정을 위해서는 수정할 엔티티가 반드시 영속성 컨텍스트에 존재해야 하기 때문이다.

 

이렇게 검색한 엔티티를 영속성 컨텍스트 내의 1차 캐시에 저장하게 된다.

그러고 나서 엔티티의 변수 값을 수정하면 JPA는 수정된 변수들을 찾아 UPDATE 구문을 작성하여 SQL 저장소에 저장한다.

이후 트랜잭션이 종료될 때 실질적으로 DB에 UPDATE가 처리된다.

 

 

JPA는 검색된 엔티티를 영속성 컨텍스트에 저장할 때, 기존 엔티티의 복사본을 만들어서 별도의 컬렉션에 저장하는데 이 저장 공간을 스냅샷(Snapshot) 이라고 한다.

트랜잭션이 종료될 때, 스냅샷에 저장된 원래의 엔티티와 1차 캐시에 저장된 수정 엔티티를 비교하여 변경된 값을 이용하여 UPDATE를 만드는 것이다. 그리고 이렇게 만든 UPDATE 구문을 SQL 저장소에 저장했다가 트랜잭션이 종료될 때 다른 SQL과 같이 데이터베이스에 한 번에 전송된다.

 

엔티티 수정에서 JPA의 기본 전략은 모든 필드 수정이다. 그래서 엔티티의 필드 하나만 수정해도 모든 칼럼이 수정되는 SQL이 실행되는 것이다.

모든 필드를 수정했을 때의 장점은 수정 쿼리가 항상 같기 때문에 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있다는 점이다. 그리고 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱했던 쿼리를 재사용할 수 있어서 성능상의 이점을 가질 수 있다.

 

 

3.3.3 엔티티 삭제하기

 

remove() 메서드로 엔티티를 삭제하면 일차적으로 영속성 컨텍스트에서 해당 엔티티가 빠지고 DELETE 구문이 SQL 저장소에 등록된다. 그러고 나서 트랜잭션이 종료될 때 SQL 저장소에 저장된 DELETE가 데이터베이스에 전송된다.

 

수정과 마찬가지로, 삭제할 엔티티를 먼저 검색하여 영속성 컨텍스트에 등록해야 한다. 그렇지 않으면 에러가 발생한다.

 

 

3.3.4 목록 검색과 JPQL

 

테이블에 저장된 특정 데이터를 상세 조회하려면 EntityManager의 find() 메서드를 사용하면 된다.

하지만 목록(List)를 조회하기 위해서는 JPQL(Java Persistence Query Language)이라는 JPA에서 제공하는 별도의 쿼리 명령어를 사용해야 한다.

 

public class JPAClient {
    public static void main(String[] args) {
        // EntityManager 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hellojpa");

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();

        /* 생성
        try {
            // Transaction 시작
            tx.begin();

            Board board = new Board();
            board.setTitle("JPA 제목");
            board.setWriter("관리자");
            board.setContent("JPA 글 등록 잘 되어라...222");
            board.setCreateDate(new Date());
            board.setCnt(0L);

            // 글 등록
            em.persist(board);

            // Transaction commit
            tx.commit();

        } catch(Exception e) {
            e.printStackTrace();
            // Transaction rollback
            tx.rollback();
        } finally {
            em.close();
            emf.close();
        }
        */

        try {
            tx.begin();
//
//            // 수정할 게시글 조회
//            Board board = em.find(Board.class, 1L);
//            board.setTitle("검색한 게시글의 제목 수정");
//

            // 게시글 삭제
//            Board board1 = em.find(Board.class, 1L);
//
//            em.remove(board1);

            Board board = new Board();
            board.setTitle("JPA 제목");
            board.setWriter("관리자");
            board.setContent("JPA 글 등록 - 1");
            board.setCreateDate(new Date());
            board.setCnt(0L);
//            board.setSeq(100L);

            // 글 등록
            em.persist(board);

            tx.commit();

            tx.begin();
            // 글 목록 조회
            String jpql = "select b from Board b order by b.seq desc";
            List<Board> boardList = em.createQuery(jpql, Board.class).getResultList();

            for(Board brd : boardList) {
                System.out.println("--->" + brd.toString());
            }

            tx.commit();

        } catch(Exception e) {
            e.printStackTrace();
            tx.rollback();
        } finally {
            em.close();
            emf.close();
        }

    }
}

글 목록 조회 부분을 보면,

EntityManager의 createQuery() 메서드를 이용하여 작성한 JPQL을 전송하였다.

JPQL은 SQL과 거의 유사한 문법을 사용하므로 해석하는 데 특별한 어려움은 없지만

검색 대상이 테이블이 아니라 엔티티라는 점이 다르다.

따라서 엔티티 이름과 엔티티가 가지고 있는 변수를 이용하여 쿼리를 구성해야 한다.

 

참고로 JPQL로 검색 기능을 수행하면 쿼리를 실행하기 전에 SQL저장소에 저장되어 있던 모든 SQL 구문을 데이터베이스에 전송한다. 그래야 영속성 컨텍스트에 없는 데이터를 데이터베이스로부터 조회하여 영속성 컨텍스트에 등록할 수 있기 때문이다.

 

 

 

'Spring & SpringBoot > JPA 퀵스타트' 카테고리의 다른 글

JPA 퀵스타트 Part 4  (0) 2023.05.12
JPA 퀵스타트 Part 2  (0) 2023.05.10
JPA 퀵스타트 Part 1  (0) 2023.04.27

+ Recent posts