ORM 표준 JPA(1)

이정원·2025년 1월 22일
post-thumbnail

1.JPA 구동 방식


JPA는 META-INF/persistence.xml 파일에 정의된 설정 정보(데이터베이스 연결 정보,JPA 구현 관련 설정)를 참조하여 Persistence 클래스에서 EntityManagerFactory를 생성한다.

EntityManagerFactory는 애플리케이션 서버가 올라올때 1개만 생성되며 데이터베이스와 상호작용하는 진입점 역할을 한다.EntityManager는 고객의 요청이 올때마다 생성을 통해 직접 DB와 CRUD 연산을 수행한다.(Thread 별로 공유되지 않는다.)

1-1.save

다음은 Member 저장 코드이다.

public class JpaMain {

    public static void main(String[] args){
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();
        //트랜잭션 생성
        EntityTransaction tx = em.getTransaction();
        //트랜잭션 시작
        tx.begin();
        try {
            Member member=new Member();
            member.setId(1L);
            member.setName("HelloA");

            em.persist(member); //데이터 저장

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

EntityManager는 내부적으로 DB 커넥션을 가지고 있기 때문에 닫는 작업이 필요하고 전체 애플리케이션이 끝나면 factory도 닫아줘야 한다.

1-2.find,remove

다음은 조회(select),삭제(remove)에 관한 코드이다.

try {
            Member findMember = em.find(Member.class, 1L);
            System.out.println("findMember.getId() = " + findMember.getId());
            System.out.println("findMember.getName() = " + findMember.getName());
            em.remove(findMember);
            tx.commit();
        }

1-3.Set

EntityManager는 DB를 JAVA 컬렉션으로 치환하여 이해하면 좋다. 다음은 수정 관련 코드이다.

 try {
            Member findMember = em.find(Member.class, 1L);
            findMember.setName("Jeongwon");
            tx.commit();
        }

find 메서드로 찾은 엔티티는 JPA의 영속성 컨텍스트(Persistence Context)에 의해 관리된다. 즉, EntityManager는 해당 엔티티를 추적하며 변경된 사항에 대해 Update 쿼리를 날린다.

JPA의 모든 데이터 변경은 트랜잭션 안에서 실행된다.

1-4.JPQL

여러 테이블에 대한 복잡한 조회나 최적화를 위한 통계성 쿼리는 JPQL(Java Persistence Query Language)을 사용하는 것이 적합하다. JPQL은 SQL과 유사하지만, 데이터베이스 테이블 대신 JPA 엔티티 객체를 대상으로 쿼리를 작성하는 언어다.

 try {
        List<Member> result = em.createQuery("select m from Member as m", Member.class).getResultList();
            
        tx.commit();
 }

JPQL은 데이터베이스 테이블이 아닌 JPA 엔티티 클래스와 그 필드를 대상으로 쿼리를 작성한다.이후 JPA가 데이터베이스가 이해할수 있는 방언(dialect)으로 번역한다.

DB에 종속적으로(Platform Dependent) 쿼리를 날리지 않게 General한 JPQL 문법이 개발되었다.

2.영속성 관리

JPA에서 가장 중요한 2가지는 아래와 같다.

  • 객체와 관계형 데이터베이스 매핑하기(Object Relational Mapping)
  • 영속성 컨텍스트


그림과 같이 고객의 요청이 오면 각각의 EntityManager를 생성하여 내부적으로 DB 커넥션을 통한 DB 작업을 수행한다. 영속성 컨텍스트란 엔티티를 영구 저장하는 환경이라 한다. EntityManager.persist(entity)는 DB에 저장하는것이 아닌 영속성 컨텍스트에 저장한다는 뜻이다.

2-1.엔티티의 생명주기

  • 비영속 (new/transient)
    영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

  • 영속 (managed)
    영속성 컨텍스트에 관리되는 상태 (em.persist(member))

  • 준영속 (detached)
    영속성 컨텍스트에 저장되었다가 분리된 상태

  • 삭제 (removed)
    삭제된 상태

데이터베이스에 객체가 저장되는 시점은 트랜잭션이 commit되는 순간 영속성 컨텍스트의 객체가 쿼리로 나간다.

2-2.영속성 컨텍스트의 이점

영속성 컨텍스트를 활용한 다음과 같은 이점이 존재한다.

  • 1차 캐시

    그림과 같이 @Id값이 Key,엔티티 자체가 Value의 map 형태로 캐시에 저장된다. 조회할때 DB에서 찾는것이 아닌 영속성 컨텍스트의 1차 캐시에서 먼저 찾게되며 이로인한 조회 성능이 향상된다. 만약 캐시에 존재하지 않으면 DB 조회 후 1차 캐시에 해당 엔티티를 저장하게 된다. EntityManager는 DB트랜잭션 단위(고객 요청당)로 만들고 1차 캐시는 지워진다. 애플리케이션 전체에서 공유하는 캐시는 2차 캐시이다.

  • 동일성(identity)보장

자바 컬렉션에서 같은 reference가 존재하는 객체를 비교하면 같다고 나오듯이 영속성 컨텍스트도 1차 캐시로 인한 동일성을 보장한다.

  • 트랜잭션을 지원하는 쓰기 지연

    쓰기 지연 SQL 저장소에 쿼리를 모았다가 트랜잭션을 commit 하는 순간 flush를 통해 쿼리를 보내고 실제 DB 트랜잭션이 commit 된다.
    batch-size를 정할수있고 버퍼링 후 한번에 전송한다.

  • 변경 감지(Dirty Checking)

 try {
            Member member = em.find(Member.class, 150L);
            member.setName("ZZZZ");
            
            em.persist(member);
            
            System.out.println("==================");
            tx.commit();
        }

이름이 A였던 데이터를 EntityManager를 통해 ZZZZ로 변경하였다. 다시 em.persist 또는 em.update를 해야할까? -> 아래 그림과 같이 최초 1차 캐시에 들어온 객체를 스냅샷으로 보관하다 모든 객체를 비교하여 DB에 반영하고 commit한다.

  • 지연 로딩(Lazy Loading)

3.플러시(Flush)

flush란 영속성 컨텍스트의 변경내용을 데이터베이스에 반영 하는것이다. 즉, 쿼리들을 DB에 날리는것이다. 플러시는 Dirty Checking 이후 쓰기 지연 저장소의 쿼리들을 DB에 전송하는 작업들을 수행한다. 다음은 영속성 컨텍스트를 Flush 하는 방법이다.

  • em.flush() - 직접 호출

  • 트랜잭션 커밋 - 플러시 자동 호출

  • JPQL 쿼리 실행 - 플러시 자동 호출

flush를 하더라도 1차 캐시는 사라지지 않는다. 단순히 Dirty Checking을 통한 쓰기 지연 저장소에 저장된 Update 쿼리들이 DB에 반영된다.

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();

JPQL 쿼리시 DB에서 조회가 가능하기 위해 flush를 중간에 호출하게 된다.

4.준영속 상태

영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 상태를 준영속 상태라 한다. 이렇게 되면 JPA가 관리하지 않는다.

 try {
            Member member = em.find(Member.class, 150L);
            //영속 상태(1차 캐시)


            member.setName("AAAAA");
            // Dirty Checking으로 인한 쿼리 버퍼링 저장

            em.detach(member);
            //준영속 상태

            System.out.println("==================");
            tx.commit();
        }

위의 코드에서 commit을 했지만 결과는 select 쿼리만 나간다. 이유는 영속성 컨텍스트에서 더이상 엔티티가 존재하지 않기 때문이다.

4-1.준영속 상태 만드는 방법

  • em.detach(entity)
    특정 엔티티만 준영속 상태로 전환
  • em.clear()
    영속성 컨텍스트를 완전히 초기화
  • em.close()
    영속성 컨텍스트를 종료

0개의 댓글