TIL_221011_자바 ORM 표준 JPA 프로그래밍_02 정리

창고·2022년 10월 11일
0

3. 프로젝트 생성, 애플리케이션 개발

(1) 프로젝트 생성

  • H2 데이터베이스 설치, 실행
  • 메이븐 설치 (최근에는 그래들이 점점 유명)
  • pom.xml 설정 (h2 버전 유의할 것)
    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.6.12.Final</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.1.214</version>
        </dependency>
    </dependencies>
  • persistence.xml 설정

    • JPA 설정 파일
    • /META-INF/persistence.xml 위치
    • persistence-unit name으로 이름 지정
    • javax.persistence로 시작 : JPA 표준 속성
    • hibernate로 시작 : 하이버네이트 전용 속성
        <persistence-unit name="hello">
          <properties>
              <!-- 필수 속성 -->
              <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
              <property name="javax.persistence.jdbc.user" value="sa"/>
              <property name="javax.persistence.jdbc.password" value=""/>
              <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
              <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
    
              <!-- 옵션 -->
              <property name="hibernate.show_sql" value="true"/>
              <property name="hibernate.format_sql" value="true"/>
              <property name="hibernate.use_sql_comments" value="true"/>
              <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
          </properties>
    • 데이터베이스 방언 (dialect)
      • JPA는 특정 데이터베이스에 종속되지 않음
      • 각각의 데이터베이스가 제공하는 SQL 문법과 함수는 조금씩 다름
      • 방언 : SQL 표준을 지키지 않는 특정 데이터베이스만의 고유 기능
      • hibernate.dialect 속성에 지정 (40가지 이상의 데이터베이스 방언 지원)

(2) 애플리케이션 개발

  • JPA 구동 방식
    • Persistence -> 설정 정보 조회 (persistence.xml) -> 생성
    • 생성 -> EntityManagerFactory -> EntityManager 생성
  • Main 클래스 생성
public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); 
        // persistence.xml 에서 지정한 name

        EntityManager em = emf.createEntityManager();

        em.close();

        emf.close();;
    }
}
  • 객체와 테이블을 생성, 매핑하기
    • @Entity : JPA가 관리할 객체
    • @Id : 데이터베이스 PK와 매핑
  • 멤버 생성 및 저장
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); // persistence.xml 에서 지정한 name
        // 애플리케이션 실행 시점에 1번 생성

        EntityManager em = emf.createEntityManager();
        // DB Connection을 얻어 쿼리를 날리고 종료하는 과정마다 생성해줘야 함

        EntityTransaction tx = em.getTransaction();
        tx.begin(); // 모든 과정이 트랜잭션 하나에서 실행되어야 함

        Member member = new Member();

        member.setId(1L);
        member.setName("HelloA");

        em.persist(member);

        tx.commit(); // 트랜잭션 커밋

        em.close();

        emf.close();;

  • /~/ 부분은 sql_comment로 인해 출력되는 부분
  • 멤버 수정 특이한 부분
        try {
            Member findMember = em.find(Member.class, 1L);
            findMember.setName("HelloJPA");

            // em.persist(findMember); -> 저장을 굳이 하지 않더라도 수정된 정보가 저장됨
            // JPA가 관리할 때 트랜잭션 직전에 정보가 바뀐 것을 캐치하여 update를 해줌줌

            tx.commit(); // 트랜잭션 커밋
        } catch (Exception e) { // 문제 상황 시 롤백
            tx.rollback();
        } finally { // 어쨌든 em을 닫아줘야 함
            em.close();
        }

        emf.close();
    }
  • 주의사항

    • EntityManagerFactory는 하나만 생성, 애플리케이션 전체에서 공유
    • EntityManager는 쓰레드 간에 공유하지 않고 사용하고 버려야 한다
    • JPA의 모든 데이터 변경은 트랜잭션 안에서 실행
      (트랜잭션 생성 후 데이터 변경한 뒤 트랜잭션 커밋 혹은 롤백)
  • JPQL 소개

    • 단순 검색이 아닌 좀 더 복잡한 조건으로 검색을 하고 싶다면?
            try {
              // Member findMember = em.find(Member.class, 1L);
              List <Member> result = em.createQuery("select m from Member as m", Member.class)
                      .setFirstResult(5) // pagination 시작 지점
                      .setMaxResults(8) // pagination 종료 지점
                      .getResultList();
              // JPA는 테이블을 대상으로 코드를 짜는 것이 아니며 객체를 대상으로 짬 (Member 테이블이 아닌, Member 객체)
              // DB 방언에 맞춰서 번역이 되어 DB에 쿼리를 쏨
    
              for (Member member : result) {
                  System.out.println("member.name = " + member.getName());
              }
    • JPA 사용 시 엔티티 객체를 중심으로 개발하는데, 검색 쿼리에서 문제가 발생할 수 있음
    • JPQL은 검색을 할 때에도 테이블이 아닌 엔티티 객체를 대상으로 검색
    • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
    • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL 필요
      -> 이를 위해 엔티티 객체를 대상으로 쿼리를 날릴 수 있는 JPQL을 사용
    • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
    • SQL과 문법 유사, SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
    • JPQL(엔티티 객체 대상) vs SQL(데이터베이스 테이블 대상)
    • 한마디로 정의하면 객체 지향 SQL

4. 영속성 관리

(1) 영속성 컨텍스트

  • JPA에서 가장 중요한 2가지
    • 객체와 관계형 데이터베이스 매핑 (정적)
    • 영속성 컨텍스트 (동적)
  • EntityManagerFactory와 EntityManager
    • EntityManagerFactory : 애플리케이션 실행 시 1번 실행되며 EntityManager를 생성
    • EntityManager : 고객의 요청에 따라 팩토리에 의해 생성되며 커넥션 풀을 사용, DB에 접근
  • 영속성 컨텍스트란?
    • "엔티티를 영구 저장하는 환경" 이라는 뜻
    • JPA를 이해하는데 가장 중요한 용어
    • EntityManager.persist(entity); -> DB에 저장하는 것이 아닌, 영속성 컨텍스트에 저장, 영속화함
    • 영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않는 개념
    • 엔티티 매니저를 통해 영속성 컨텍스트에 접근
  • 엔티티의 생명 주기
    • 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
    Member member = new member();
    member.setId("member1");
    member.setUserName("김쾅쾅");
      
    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();
    • 영속(managed) : 영속성 컨텍스트에 '관리되는' 상태 (영속이 되었다고 DB에 저장되는 것이 아님. 트랜잭션이 커밋이 되는 시점에서 저장이 됨) -> persist 되었거나 find 등으로 DB에서 가져와 1차 캐시로 얹혀진 상태
    em.persist(member);
    • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
    em.detach(member);
    • 삭제(removed) : 삭제된 상태
    em.remove(member);
    -> 스레드, runnable 객체랑 비슷한 것 같은데

(2) 영속성 컨텍스트의 이점

  • 1차 캐시 내 조회 가능
    • 영속화된 엔티티는 1차 캐시로 저장이 되며 검색 시 DB 접근 없이 1차 캐시 내에서 조회가 가능 (따라서 DB로 select 쿼리를 날리지 않고 찾을 수 있음), 1차 캐시에 없는 객체를 조회할 경우 DB에서 조회한 후 (select 쿼리 발생) 이 값을 1차 캐시에 객체로 저장한 뒤 반환함
  • 영속 엔티티의 동일성(identity) 보장
    • 1차 캐시로 반복 가능한 읽기 (REPETABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
     Member a = em.find(Member.class, "member1");
     Member b = em.find(Member.class, "member2");
     
     System.out.println(a == b); // 동일성 비교 true
  • 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
    • 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 하므로, 커밋하기 전까지는 DB에 보내지 않음
     EntityManager em = emf.createEntityManager();
     EntityTransaction tx = em.getTransaction();
     
     tx.begin(); // 트랜잭션 시작
     
     em.persist(memberA);
     em.persist(memberB);
     // 여기까지는 영속화만 진행할 뿐, 커밋을 하지 않아 DB에 INSERT SQL을 보내지 않음
     // 쓰기 지연 SQL 저장소에 저장
     
     tx.commit(); // 트랜잭션 커밋, 이 시점에 DB에 INSERT SQL 전송
     // 쓰기 지연 SQL 저장소에 있는 SQL INSERT를 flush  
  • 변경 감지 (Dirty Checking)
    • 영속 엔티티 데이터 수정 시 변경된 내용을 감지하기 때문에 persist를 하지 않더라도 커밋 시 update 쿼리가 전송되며 수정된 정보가 업데이트됨
    • flush 진행 시 1차 캐시 내의 엔티티와 스냅샷(엔티티가 1차 캐시에 최초로 들어왔을 때의 상태)을 비교함 -> 변경점이 발생하였다면 UPDATE SQL이 생성되어 쓰기 지연 SQL 저장소에 저장함 -> 마찬가지로 flush, commit 시 DB에 보냄
  • 지연 로딩 (Lazy Loading)

(3) 플러시와 준영속 상태

  • 플러시(flush) : 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
    • 변경 감지
    • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
    • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (CRUD 쿼리)
  • 플러시하는 방법
    • em.flush() : 직접 호출
    • 트랜잭션 커밋 : 플러시 자동 호출
    • JPQL 쿼리 실행 : 플러시 자동 호출
  • 플러시 모드 옵션 em.setFlushMode(FlushModeType.COMMIT)
    • AUTO : 커밋이나 쿼리 실행 시 자동으로 플러시 (기본값, 그대로 쓰는 것 권장)
    • COMMIT : 커밋 시에만 플러시
  • 헷갈리지 말 것! 영속성 컨텍스트를 비우지 않음
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화하면 됨!
  • 준영속 상태
    • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리
    • 영속성 컨텍스트가 제공하는 기능을 사용하지 못함
  • 준영속 상태로 만드는 방법
    • em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
    • em.clear() : 영속성 컨텍스트를 완전히 초기화
    • em.close() : 영속성 컨텍스트를 종료
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글