Spring ORM JPA 관련 정리 - 1

BYEONGMIN CHOI·2022년 6월 3일
0

SPRING ORM JPA

목록 보기
1/5

인프런 이영한님의 강의를 바탕으로 작성된 내용입니다.

객체와 관계형 데이터베이서의 차이

  1. 상속
  2. 연관관계
  3. 데이터 타입
  4. 데이터 식별 방법

상속

객체의 상속관계를 가장 유사하게 표현한 Table 슈퍼타입 서브타입 관계도

객체를 관계형 데이터로 바꾸는 일련의 과정

  • 객체의 분해 \rightarrow Item/ Album
  • Insert Into Item / Insert Into Album \rightarrow 2번의 Insert Into 과정

조회한 테이블을 객체로 변환하는 일련의 과정
  • 각각의 테이블에 따른 Join SQL 작성 ( ITEM 과 ALBUM을 JOIN해 조회)
  • 각각의 객체 생성
  • 다른 테이블 조회 시 위에 과정을 반복...

결과적으로 DB에 저장할 객체에는 상속관계를 안쓴다.

위의 과정을 자바 컬렉션으로 저장한다면?

list.add(album)

위의 과정을 자바 컬렉션으로 조회한다면?

Album album = list.get(albumId);

부모 타입으로 조회 후 다형성 활용

Item item = list.get(albumId);

연관관계

  • 객체는 참조를 사용 : member.getTeam()
  • 테이블은 외래 키를 사용: JOIN ON M.TEAM_ID = T.TEAM_ID

객체의 경우 Taem 객체를 사용하여 Member 객체를 참조할 수 없지만 (단방향), 테이블의 경우 MEMBER의 FK를 통해 TEAM이 MEMBER를 확인할 수 있다.(양방향)

\rightarrow 양방향을 단방향 객체로 조회할 때 SQL문을 사용하며 복잡한 과정들이 발생하게 된다.

객체 그래프 탐색

그러나 처음 실행하는 SQL에 따라 탐색 범위가 결정된다.

SELETE M.*, T.*
	FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

member.getTeam(); // Ok
member.getOrder(); // null \rightarrow 처음에 설정시 ORDER 테이블은 JOIN하지 않았기 때문에

또한 모든 테이블을 연결하여 엔티티를 작성하였다 하더라도 엔티티에 대한 신뢰 문제로 프로그래머가 다 확인해야하는 작업이 필요하다.

계층형 아키텍처 진정한 의미의 계층 분할이 어렵다

JPA

  • Java Persistence API
  • 자바 진영의 ORM 기술 표준

ORM

  • Object-relational mapping(객체 관계 매핑)
  • 객체는 객체대로 설계
  • 관계형 데이터베이스는 관계형 데이터베이스대로 설계 - ORM 프레임워크가 중간에서 매핑
  • 대중적인 언어에는 대부분 ORM 기술이 존재

JPA 동작

- 저장

- 조회

가장 중요한 점은 패러다임의 불일치를 해결해준다? \rightarrow 어떤 의미인지 앞서 설명한 객체 와 테이블 간의 다른 점을 말하는 것인지 더 공부해 봐야 할점

JPA 란?

  • JPA는 인터페이스의 모음
  • JPA 2.1 표준 명세를 구현한 3가지 구현체

JPA를 왜 사용하는가?

  • SQL 중심적인 개발에서 객체 중심으로 개발 - 생산성
    - 저장: jpa.persist(member)
    • 조회: Member member = jpa.find(memberId)
    • 수정: member.setName(“변경할 이름”)
    • 삭제: jpa.remove(member)
  • 유지보수
  • 패러다임의 불일치 해결
    `* 데이터 지연 로딩?
  • 성능
    - 1차 캐시와 동일성(identity)보장

    1. 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
      2. DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장 - 아직 이해하기 어려운 개념...

          String memberId = "100";
          Member m1 = jpa.find(Member.class, memberId); //SQL
          Member m2 = jpa.find(Member.class, memberId); //캐시
          
          println(m1 == m2) //true

      SQL 1번만 실행

    • 트랜잭션을 지원하는 쓰기 지연 - INSERT
    1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음

    2. JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송

      	transaction.begin(); // [트랜잭션] 시작
      
          em.persist(memberA);
          em.persist(memberB);
          em.persist(memberC);
          //여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
          //커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
          transaction.commit(); // [트랜잭션] 커밋
    • 지연 로딩과 즉시 로딩
      • 지연 로딩 : 객체가 실제 사용될 때 로딩
        • 즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회
          `* JPA 옵션을 통해 지연로딩과 즉시로딩을 번갈아가며 사용할 수 있댜.
  • 데이터 접근 추상화와 벤더 독립성 - 표준

ORM은 객체와 RDB 두 기둥위에 있는 기술 !!!!


데이터베이스 방언

JPA 구동방식

 public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); // Gradle 사용시 persistance.xml 에 사용할 객체 명시 해줘야함 <class>hellojpa.Member</class>

        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();  // transaction 이라는 단위가 중요, jpa의 모든 작업은 transaction안에서 작업이 발생해야한다.
        tx.begin();
        try {
            // table column 생성
            /*Member member = new Member();
            member.setId(2L);
            member.setName("HelloB");*/

            // 조회
            /*Member findMember = em.find(Member.class, 1L);
            System.out.println("findMember.id = " + findMember.getId());
            System.out.println("findMember.name = " + findMember.getName());*/

            // 수정
            Member findMember = em.find(Member.class, 1L); // 생성 후 jpa가 관리 -> 변경이 발생시 update query가 발생
            findMember.setName("HelloJPA");  // 데이터를 수정 후 따로 persist를 할 필요가 없다 -> java의 collection 처럼 사용

            /*// 저장
            em.persist(member);*/

           /* // 삭제
            em.remove(em.find(Member.class, 2L));*/

            // database commit, 문제가 생기면 rollback 해야함
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close(); // entity manager가 내부 데이터베이스 커넥션을 가지고 동작하기 때문에 사용 후 닫아주는 것이 중요하다.
        }
        emf.close();
    }

주의 !!!!

  • EntityManagerFactory는 하나만 생성해서 애플리케이션 전체에서 공유
  • EntityManager는 쓰레드간 공유 X (사용하고 반듯이 close)
  • JPA의 모든 데이터 변경은 Transaction 안에서 실행

JPQL 간단히 맞보기...

List<Member> result = em.createQuery("select m from Member as m", Member.class) // query 문과 비슷한 문법// 테이블 대상이 아니라 객체 대상의 query
                    .setFirstResult(1)
                    .setMaxResults(6)  // 페이징이 가능 1번 부터 6번까지 Limit offset
                    .getResultList();

            for (Member member : result) {
                System.out.println("member.name = " + member.getName());
            }

• JPA를 사용하면 엔티티 객체를 중심으로 개발
• 문제는 검색 쿼리
• 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색 모든
• DB 데이터를 객체로 변환해서 검색하는 것은 불가능
• 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검 색 조건이 포함된 SQL이 필요

  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
  • SQL과 문법 유사, SELECT, FROM, WHERE, GROUP BY,HAVING, JOIN 지원
  • JPQL은 엔티티 객체를 대상으로 쿼리 SQL은 데이터베이스 테이블을 대상으로 쿼리
  • 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
  • SQL을 추상화해서 특정 데이터베이스 SQL에 의존X

  • JPQL을 한마디로 정의하면 객체 지향 SQL


영속성 관리 (JPA 내부 구조)

JPA에서 가장 중요한 2가지!!!!

  • 객체와 관계형 데이터베이스 매핑하기 (ORM Object Relational Mapping)

  • 영속성 컨텍스트

EntityManagerFactory와 EntityManager

  • JPA를 이해하는데 가장 중요한 용어
  • "엔티티를 영구 저장하는 환경" 이란 뜻
  • EntityManager.persist(entity) \rightarrow entity를 DB에 저장하는 것이 아니라 영속성 컨텍스트라는 곳에 저장시키는 것이다.

엔티티의 생명주기

  • 비영속 (new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

영속 (managed) : 영속성 컨텍스트에 관리되는 상태

EntityManagerFactory emf = Persistance.createEntityManagerFactory(persistanceUnitName);

//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1);

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

 //객체를 저장한 상태(영속)
em.persist(member);
  • 영속상태라고해서 DB에 query가 날아가는 것이 아니다.
  • transaction commit할때 DB에 query가 발생

영속성 컨텍스트의 이점

1차 캐시

 Member member = new Member();
 member.setId("member1");
 member.setUsername("회원1");
 
 //1차 캐시에 저장됨
 em.persist(member);
 
 //1차 캐시에서 조회
 Member findMember = em.find(Member.class, "member1");

\rightarrow 1차 캐시가 존재하는 경우 DB를 조회하지 않고 1차 캐시를 조회한다.

데이터베이스에서 조회

Member findMember2 = em.find(Member.class, "member2"); // DB에 있다는 가정

\rightarrow 1차 캐시에 조회한 id에 해당하는 entity값이 존재하지 않기 때문에 DB를 조회하며 조회 후 1차캐시에 저장되어 반환된다. 그 이후 member2 를 조회시 1차 캐시에서 조회된다.

1차캐시의 이점은 큰 이점이 되진 않는다. 대답의 transaction 단위로 EntityManager 가 생성되는데, transaction이 끝날때 EntityManager를 종료시킨다. \rightarrow 고객의 요청이 하나 들어와 비지니스가 끝나면 영속성컨텍스트를 지운다. 따라서 1차 캐시도 사라지게 된다.

동일성(identity) 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true

\rightarrow 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터 베이스가 아닌 애플리케이션 차원에서 제공

트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋

변경 감지(Dirty Checking)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작

// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

//em.update(member) 이런 코드가 있어야 하지 않을까?

transaction.commit(); // [트랜잭션] 커밋

\rightarrow Java의 collection 과 같이 값을 변경하면 바로 update query를 해준다.

\rightarrow 최초의 엔티티 상태를 스냅샷으로 1차 캐시에 저장후 원래의 엔티티는 쓰기지연SQL저장소에 insert query로 저장, 이후 flush() 호출에 의해 변경된 엔티티는 스냅샷과 비교하여 변경되었다면 update query가 쓰기지연SQL저장소에 저장 후 이전 query와 함께 commit된다.

지연 로딩(Lazy Loading)

플러쉬 (Flush()) : 영속성 컨텍스트의 변경내용을 데이터베이스에 반영

  • 엔티티의 변경 감지시 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록한다.
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)

플러시 하는 방법

  • em.flush() : 직접호출 (test시 사용할 수 있다.)
  • 트랜잭션 커밋 - 플러시 자동 호출 (tx.commit())
  • JPQL 쿼리 실행 - 플러시 자동 호출

그래서 플러시는!!

\rightarrow 영속성 컨텍스트를 비우지 않는다.

\rightarrow 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화 한다.

\rightarrow 트랜잭션이라는 작업단위가 중요 -> commit 직전에만 동기화 하면된다.

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

 //회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
 em.detach(member);

준영속 상태

  • 영속 -> 준영속
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
  • 영속성 컨텍스트가 제공하는 기능을 사용 못함

준영속 상태로 만드는 방법

\rightarrow em.detach(entity) : 특정 엔티티만 준영속 상태로 전환

\rightarrow em.clear() : 영속성 컨텍스트를 완전히 초기화

\rightarrow em.close() : 영속성 컨텍스트를 종료

  • 삭제 (removed) : 삭제된 상태
 //객체를 삭제한 상태(삭제)
 em.remove(member);
profile
스스로 성장하는 개발자가 되겠습니다.

0개의 댓글