[JPA] JPA 소개

Swim Lee·2021년 1월 26일
1

JPA

목록 보기
2/10
post-thumbnail

JPA란?

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

ORM이란?

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

객체는 객체대로 설계하고 관계형 DB는 관계형 DB대로 설계한 후, 둘 사이의 차이들은 ORM 프레임워크가 해결해주겠다는 것

JPA 동작

JPA는 애플리케이션과 JDBC 사이에서 동작

  • JPA가 마법을 부리고 그러는게 아님🤣
  • JPA는 Java 애플리케이션과 JDBC API 사이에서 동작
    • 참고로 JDBC API는 자바를 이용한 데이터베이스 접속 (Connection), SQL문의 실행, 실행결과 데이터 핸들링을 제공하는 방법과 절차에 관한 규약
    • 자바 프로그램 내에서 SQL문을 실행하기 위한 자바 표준 인터페이스
    • SQL과 프로그래밍 언어의 통합 접근 중 한 형태
  • 개발자가 직접 JDBC API를 사용하는 것이 아니라, JPA에게 명령을하면 JPA가 JDBC API를 사용해서 SQL을 만들어서 보내고 그 결과를 받아서 Java 엔티티 객체로 매핑해줌

JPA 동작 - 저장

  • 과거에는 개발자는 JDBC API나 JDBC Template이나 MyBatis를 사용했다면(쿼리 중심 개발), JPA를 쓰게 되면 그냥 JPA에게 member 엔티티 객체를 넘김
  • JPA가 해당 엔티티 객체 분석
  • JPA가 적절한 INSERT 쿼리 생성 (개발자가 쿼리 생성하지 않고 JPA가 만들어줌!!)
  • JPA가 내부적으로 JDBC API를 사용하여 생성한 INSERT 쿼리를 DB에 보냄
  • 패러다임 불일치 해결!

JPA 동작 - 조회

  • JPA에 조회 요청하면 JPA가 내부적으로 JDBC API사용해서 ResultSet가져옴
  • JPA가 ResultSet을 객체에 Mapping 해줌 (번잡한일 다 처리)

JPA 소개

EJB - 엔티티 빈(자바 표준)
🔽
하이버네이트(오픈소스)
🔽
JPA(자바 표준)

JPA는 표준 명세

  • JPA는 인터페이스의 모음
  • JPA 2.1 표준명세를 구현한 3가지 구현체
  • 하이버네이트, EclipseLink, DataNucleus

대부분 8~90% 이상 하이버네이트를 사용한다고 생각하면 됨

JPA를 왜 사용해야하는가?

  • SQL 중심적인 개발에서 객체 중심으로 개발
  • 생산성
  • 유지보수
  • 패러다임의 불일치 해결
  • 성능
  • 데이터 접근 추상화와 벤더 독립성 (DB벤더에 독립적으로 개발 가능)
  • 표준

생산성 - JPA와 CRUD

  • 저장 : jpa.persist(member)
  • 조회 : Member member = jpa.find(memberId)
  • 수정 : member.setName("변경할 이름")
  • 삭제 : jpa.remove(member)

기본적인 CRUD 코드가 다 구현되어있다. 그냥 메서드 가져다가 사용하면 된다! (개발자가 쿼리 작성하고 있지 않아도됨)

제일 환상적인 것은 수정부분
엔티티 객체의 속성을 변경하면, JPA가 이를 감지해서 자동으로 UPDATE 쿼리를 생성해서 날린다.

WHY?

생각해보면 JPA라는 것은 마치 자바 컬렉션에 데이터 넣고 빼듯이 RDB에 데이터 넣고 뺄 수 있게 하기위해 나온 것이다.
자바 컬렉션에서 꺼내온 객체를 수정하고, 수정한 객체를 다시 컬렉션에 집어넣지 않듯이 (왜냐면 레퍼런스를 가져와서 수정한 것이기 때문에 원본객체의 속성이 변경됨) JPA를 사용하면 따로 사용자가 UDPATE 쿼리를 날려주지 않아도 해당 변경사항을 자동으로 DB에 반영시켜준다.

유지 보수

😥 기존 : 필드 변경시 모든 SQL 수정

이전 게시글에서도 보았듯이 객체에 필드하나만 추가하더라도 모든 쿼리들을 다 수정해주어야한다. (오류 발생확률 다분함)

😍 JPA : 필드만 추가하면 된다, SQL은 JPA가 처리

하지만 JPA를 사용한다면 DB에 새로운 컬럼이 추가되어있다는 가정하에 객체의 필드만 추가하면 된다.
개발자가 쿼리에 손댈필요 없음!!!
왜? JPA가 변경된 객체보고 쿼리 생성해주니까!

JPA와 패러다임 불일치 해결

JPA는 관계형 DB와 객체의 패러다임 불일치 문제를 해결해준다!

  1. JPA와 상속
  2. JPA와 연관관계
  3. JPA와 객체 그래프 탐색
  4. JPA와 비교하기

JPA와 상속

🟥 저장

  • 개발자가 할일
    jpa.persist(album);
  • 나머진 JPA가 처리
    INSERT INTO ITEM ...
    INSERT INTO ALBUM ...

개발자는 부모객체를 상속받는 객체를 JPA를 통해 저장하면, JPA가 알아서 INSERT쿼리를 2개로 나눠서 날린다. 개발자는 DB의 구조에 대해 크게 고민하지 않고 객체를 DB에 저장할 수 있다.

🟧 조회

  • 개발자가 할일
    Album album = jpa.find(Album.class, albumId);
  • 나머진 JPA가 처리
    SELECT I.*, A.*
    FROM ITEM I
    JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID

개발자가 조회하고 싶은 엔티티 클래스 타입과 PK값을 넘기면 JPA에서 알아서 테이블 조인해서 연관된 객체 데이터까지 가져온다.

JPA와 연관관계, 객체 그래프 탐색

🔵 연관관계 저장

member.setTeam(team);
jpa.persist(member); //persist : 영구저장하다

🟣 객체 그래프 탐색

Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();

JPA사용하면 엔티티계층을 신뢰하고 객체 그래프를 탐색할 수 있다!!

JPA와 비교하기

String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);

member1 == member2; //같다

JPA는 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다!

JPA의 성능 최적화 기능

  1. 1차 캐시와 동일성(identity) 보장
  2. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
  3. 지연 로딩(Lazy Loading)

JPA와 같이 계층 사이에 중간계층을 두면 할 수 있는 것이 2가지가 있다

  • 모아서 쏘는 buffering과
  • 저장해두고 읽어오는 caching

JPA를 잘 다룬다면 단순하게 SQL 사용하는 것보다 성능 끌어올릴 수 있다.

참고(캐싱전략)

캐싱전략 종류
write through와 write behind 차이

  • write through : 캐시에 데이터 쓰고, 수정된 캐시 데이터 내용 즉시 DB에 반영
    • 캐시 데이터를 동기적으로 DB에 저장
  • write behind : 캐시에 데이터 쓰는 것은 똑같으나 일정시간 기다린 후 DB에 저장, 성능이슈 때문에 사용되는 방식 (DB같은 저장장치 접근 시간은 대부분 느리니까) - 일정시간 기다린다 : 쓰기지연
    • 데이터를 먼저 캐시에 쓴다음, 비동기식으로 DB에 업데이트하여 쓰기 성능 향상

1차 캐시와 동일성 보장

  1. 같은 트랜잭션 안에서는 같은 엔티티 반환 - 약간의 조회 성능 향상
  2. DB Isolation Level이 Read Committed이어도 애플리케이션에서 Repeatable Read 보장
  • Read Committed : 하나의 트랜잭션에서 데이터 읽어들일 때, 커밋이 완료된 데이터만 읽어올 수 있다. 아직 커밋되지 않은 변경사항은 읽지 않는다.
    • 해당 격리수준은 하나의 트랜잭션에서 동일한 데이터 여러번 조회할 때, 중간에 커밋되어서 변경된 데이터가 있다면, 항상 동일한 값을 조회하지 않는다.
    • 바로 Non-Repeatable Read
  • JPA는 DB Isolation level이 Read Committed여도 애플리케이션에서 Reapeatable Read를 보장한다.
  • 왜냐? 트랜잭션에서 처음 DB에서 데이터 조회해오면 1차캐시에 저장해두고 그후에는 DB에서 가져오지 않고 1차캐시에 저장된 데이터 가져오기 때문!
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); //SQL
Member m2 = jpa.find(Member.class, memberId); //캐시

println(m1==m2) //true (결과적으로 SQL 한번만 실행)

똑같은 pk로 엔티티조회시, 처음 조회할 때는 SQL을 날리지만 다음부터는 캐싱된 값을 조회해온다.

우리가 일반적으로 생각하는 캐싱과는 다름
하나의 요청이 들어와서 트랜잭션이 시작되고 끝나는 그 사이에만 유지되는 캐싱이기 때문에 사실 그렇게 엄청난 성능상 이득은 없다 😂

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

버퍼링 기능

🟨 INSERT

  1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
  2. JDBC BATCH SQL기능을 사용해서 한번에 SQL 전송
transaction.begin(); //트랜잭션 시작

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 DB에 보내지 않는다

//커밋하는 순간 DB에 INSERT SQL을 모아서 보낸다
transaction.commit(); //트랜잭션 커밋

위 코드에서 각각의 요청마다 쿼리를 날리면 네트워크를 3번타야함.
➡ 속도상 느림
하지만 한번에 SQL을 전송할 수 있다면 네트워크를 한번만 타도 됨.
➡ JDBC BATCH 를 사용하면 가능하지만, 코드가 굉장히 더러움
➡ JPA를 사용한다면 옵션하나 켜줌으로써 해결 (spring 사용하면 batch size 옵션 간단히 설정 가능)

지연로딩과 즉시로딩

  • 지연로딩 : 객체가 실제 사용될 때 로딩
  • 즉시로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회

🚲 지연로딩

Member member = memberDAO.find(memberId); // SELECT * FROM MEMBER 쿼리 날라감
Team team = member.getTeam();
String teamName = team.getName(); // SELECT * FROM TEAM, TEAM객체가 실제로 사용될 때 쿼리 날라감

지연로딩은 연관된 객체 필드를 프록시로 초기화해두고, 해당 객체가 실제로 사용될 때 쿼리 날려서 객체를 가져와서 초기화한다.

🚝 즉시로딩

Member member = memberDAO.find(memberId); // SELECT M.*, T.* FROM MEMBER JOIN TEAM ... (한번에 연관된 엔티티 객체까지 다 가져옴)
Team team = member.getTeam();
String teamName = team.getName();

결론

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

객체지향과 RDB 모두 다 잘알아야한다.
더 중요한거 굳이 고르라하면, RDB라고 할 수 있다.
왜냐면, 객체지향적인 언어는 바뀔 수 있음. 하지만 RDB의 데이터는 훨씬 더 오래 살아남음.
따라서 꾸준하게 둘다 공부하고 잘하는 상태에서 사용해야한다!


해당 게시글은 인프런 김영한님의 <자바 ORM 표준 JPA 프로그래밍 - 기본편>을 듣고 정리한 내용입니다.

profile
백엔드 꿈나무 🐥

0개의 댓글