JPA는 도대체 뭘까? (orm, 영속성, hibernate, spring-data-jpa)

adam2·2020년 4월 8일
103

자바를자바라

목록 보기
3/5

JPA(Java Persistence API)

  • 자바 ORM 기술에 대한 표준 명세로, JAVA에서 제공하는 API이다. 스프링에서 제공하는 것이 아님!
  • 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다.
    • 여기서 중요하게 여겨야 할 부분은, JPA는 말 그대로 인터페이스라는 점이다. JPA는 특정 기능을 하는 라이브러리가 아니다. 스프링의 PSA에 의해서(POJO를 사용하면서 특정 기술을 사용하기 위해서)표준 인터페이스를 정해두었는데, 그중 orm을 사용하기 위해 만든 인터페이스가 바로 jpa이다.
  • 기존 EJB에서 제공되던 엔티티 빈을 대체하는 기술이다.
  • ORM이기 때문에 자바 클래스와 DB테이블을 매핑한다.(sql을 매핑하지 않는다)

🤔ORM이 뭐죠? SQL Mapper와 ORM

자바에서 db를 건드린다 = jdbc를 쓴다 라고 고정관념? 그렇게만 써봤어서 jpa와 jdbc의 차이점이 궁금했다.

  • ORM은 DB 테이블을 자바 객체로 매핑함으로써 객체간의 관계를 바탕으로 SQL을 자동으로 생성하지만 Mapper는 SQL을 명시해주어야 한다.
  • ORM은 RDB의 관계를 Object에 반영하는 것이 목적이라면, Mapper는 단순히 필드를 매핑시키는 것이 목적이라는 점에서 지향점의 차이가 있다.

SQL Mapper

  • SQL ←mapping→ Object 필드
  • SQL 문으로 직접 디비를 조작한다.
  • Mybatis, jdbcTemplate

ORM(Object-Relation Mapping/객체-관계 매핑)

  • DB 데이터 ←mapping→ Object 필드
    • 객체를 통해 간접적으로 디비 데이터를 다룬다.
  • 객체와 디비의 데이터를 자동으로 매핑해준다.
    • SQL 쿼리가 아니라 메서드로 데이터를 조작할 수 있다.
    • 객체간 관계를 바탕으로 sql을 자동으로 생성한다
  • Persistant API라고 할 수 있다.
  • JPA, Hibernate

JDBC


JDBC는 DB에 접근할 수 있도록 자바에서 제공하는 API이다.

모든 JAVA Data Access 기술의 근간이다. ⇒ 모든 Persistance Framework는 내부적으로 JDBC API를 이용한다.

😫그럼 spring-data-jpa 같은건 뭐죠?


JPA은 ORM을 위한 자바 EE 표준이며 Spring-Data-JPA는 JPA를 쉽게 사용하기 위해 스프링에서 제공하고 있는 프레임워크이다.

추상화 정도는 Spring-Data-JPA -> Hibernate -> JPA 이다.

Hibernate를 쓰는 것과 Spring Data JPA를 쓰는 것 사이에는 큰 차이가 없지만

  • 구현체 교체의 용이성
  • 저장소 교체의 용이성

이라는 이유에서 Spring Data JPA를 사용하는것이 더 좋다.

  1. 자바의 Redis클라이언트가 Jdis에서 Lettuce로 대세가 넘어갈 때 Spring Data Redis를 사용하면 아주 쉽게 교체가 가능했다.
  2. Spring Data JPA, Spring Data MongoDB, Spring Data Redis등 Spring Data의 하위 프로젝트들은 findAll(), save()등을 동일한 인터페이스로 가지고 있기 때문에 저장소를 교체해도 기본적인 기능이 변하지 않는다.

🙀JPA 동작 과정


JPA는 애플리케이션과 JDBC 사이에서 동작한다.
개발자가 JPA를 사용하면, JPA 내부에서 JDBC API를 사용하여 SQL을 호출하여 DB와 통신한다.
즉, 개발자가 직접 JDBC API를 쓰는 것이 아니다.

insert


MemberDAO에서 객체를 저장하고 싶을 때 개발자는 JPA에 Member 객체를 넘긴다.

JPA는

  1. Member 엔티티를 분석한다.
  2. INSERT SQL을 생성한다.
  3. JDBC API를 사용하여 SQL을 DB에 날린다.

find


개발자는 member의 pk 값을 JPA에 넘긴다.

JPA는

  1. 엔티티의 매핑 정보를 바탕으로 적절한 SELECT SQL을 생성한다.
  2. JDBC API를 사용하여 SQL을 DB에 날린다.
  3. DB로부터 결과를 받아온다.
  4. 결과(ResultSet)를 객체에 모두 매핑한다.
    쿼리를 JPA가 만들어 주기 때문에 Object와 RDB 간의 패러다임 불일치를 해결할 수 있다.

JPA 특징

  1. 데이터를 객체지향적으로 관리할 수 있기 때문에 개발자는 비즈니스 로직에 집중할 수 있고 객체지향 개발이 가능하다.
  2. 자바 객체와 DB 테이블 사이의 매핑 설정을 통해 SQL을 생성한다.
  3. 객체를 통해 쿼리를 작성할 수 있는 JPQL(Java Persistence Query Language)를 지원
  4. JPA는 성능 향상을 위해 지연 로딩이나 즉시 로딩과 같은 몇가지 기법을 제공하는데 이것을 잘 활용하면 SQL을 직접 사용하는 것과 유사한 성능을 얻을 수 있다.

🔑JPA를 왜 사용해야 할까

  1. sql 중심적인 개발에서 객체 중심적인 개발이 가능하다.

    sql 코드의 반복, 객체지향과 관계지향 데이터베이스의 페러다임 불일치

    Object -> [SQL 변환] -> RDB에 저장
    [개발자 == SQL 매퍼] 라고 할만큼 SQL 작업을 너무 많이 하고 있다.

  2. 생산성이 증가

    간단한 메소드로 CRUD가 가능하다

  3. 유지보수가 쉽다
    기존: 필드 변경 시 모든 SQL을 수정해야 한다.
    JPA: 필드만 추가하면 된다. SQL은 JPA가 처리하기 때문에 손댈 것이 없다.

  4. Object와 RDB 간의 패러다임 불일치 해결

참고

JPA 하이버네이트

하이버네이트는 JPA 구현체의 한 종류이다.

JPA는 DB와 자바 객체를 매핑하기 위한 인터페이스(API)를 제공하고 JPA 구현체(하이버네이트)는 이 인터페이스를 구현한 것이다.

하이버네이트 외에도 EclipseLink, DataNucleus, OpenJPA, TopLink Essentials 등이 있다.

  • Hibernate가 SQL을 직접 사용하지 않는다고 해서 JDBC API를 사용하지 않는다는 것은 아니다.
    • Hibernate가 지원하는 메서드 내부에서는 JDBC API가 동작하고 있으며, 단지 개발자가 직접 SQL을 직접 작성하지 않을 뿐이다.
  • HQL(Hibernate Query Language)이라 불리는 매우 강력한 쿼리 언어를 포함하고 있다.
    • HQL은 SQL과 매우 비슷하며 추가적인 컨벤션을 정의할 수도 있다.
    • HQL은 완전히 객체 지향적이며 이로써 상속, 다형성, 관계등의 객체지향의 강점을 누릴 수 있다.
    • HQL은 쿼리 결과로 객체를 반환하며 프로그래머에 의해 생성되고 직접적으로 접근할 수 있다.
    • HQL은 SQL에서는 지원하지 않는 페이지네이션이나 동적 프로파일링과 같은 향상된 기능을 제공한다.
    • HQL은 여러 테이블을 작업할 때 명시적인 join을 요구하지 않는다.

영속성

데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성을 말한다.

영속성을 갖지 않으면 데이터는 메모리에서만 존재하게 되고 프로그램이 종료되면 해당 데이터는 모두 사라지게 된다.

그래서 우리는 데이터를 파일이나 DB에 영구 저장함으로써 데이터에 영속성을 부여한다.

Persistance Layer

프로그램의 아키텍처에서 데이터에 영속성을 부여해주는 계층을 말한다.

JDBC를 이용해 직접 구현이 가능하나 보통은 Persistance Framework를 사용한다.


프레젠테이션 계층 (Presentation layer) - UI 계층 (UI layer) 이라고도 함
애플리케이션 계층 (Application layer) - 서비스 계층 (Service layer) 이라고도 함
비즈니스 논리 계층 (Business logic layer) - 도메인 계층 (Domain layer) 이라고도 함
데이터 접근 계층 (Data access layer) - 영속 계층 (Persistence layer) 이라고도 함

Persistance Framework

JDBC프로그래밍의 복잡함이나 번거로움 없이 간단한 작업만으로 DB와 연동되는 시스템을 빠르게 개발할 수 있고 안정적인 구동을 보장한다.

Persistance Framework는 SQL Mapper와 ORM으로 나눌 수 있다.

JPA에서의 영속성

JPA의 핵심 내용은 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈린다. JPA의 엔티티 매니저가 활성화된 상태로 트랜잭션(@Transactional) 안에서 DB에서 데이터를 가져오면 이 데이터는 영속성 컨텍스트가 유지된 상태이다. 이 상태에서 해당 데이터 값을 변경하면 트랜잭션이 끝나는 시적에 해당 테이블에 변경 내용을 반영하게 된다. 따라서 우리는 엔티티 객체의 필드 값만 변경해주면 별도로 update()쿼리를 날릴 필요가 없게 된다! 이 개념을 더티 체킹이라고 한다.

Spring Data Jpa를 사용하면 기본으로 엔티티 매니저가 활성화되어있는 상태이다.

영속 컨텍스트: 엔티티를 담고 있는 집합. JPA는 영속 컨텍스트에 속한 엔티티를 DB에 반영한다. 엔티티를 검색, 삭제, 추가 하게 되면 영속 컨텍스트의 내용이 DB에 반영된다.
영속 컨텍스트는 직접 접근이 불가능하고 Entity Manager를 통해서만 접근이 가능하다.

엔티티: @Entity 어노테이션을 붙인 클래스

JPA 성능 최적화

기본적으로 중간 계층이 있는 경우 아래의 방법으로 성능을 개선할 수 있는 기능이 존재한다.

  1. 모아서 쓰는 버퍼링 기능

  2. 읽을 때 쓰는 캐싱 기능

JPA도 JDBC API와 DB 사이에 존재하기 때문에 위의 두 기능이 존재한다.

1차 캐시와 동일성(identity) 보장 - 캐싱 기능

같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상 (크게 도움 X)

    String memberId = "100";
    
    Member m1 = jpa.find(Member.class, memberId); // SQL
    
    Member m2 = jpa.find(Member.class, memberId); // 캐시 (SQL 1번만 실행, m1을 가져옴)
    
    println(m1 == m2) // true

결과적으로, SQL을 한 번만 실행한다.

DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장

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

INSERT

/** 1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음 */

transaction.begin(); // [트랜잭션] 시작

em.persist(memberA);

em.persist(memberB);

em.persist(memberC);

// -- 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

// 커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다. --

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

transaction.commit(); // [트랜잭션] 커밋
  1. [트랜잭션]을 commit 할 때까지 INSERT SQL을 메모리에 쌓는다.

    이렇게 하지 않으면 DB에 INSERT Query를 날리기 위한 네트워크를 3번 타게 된다.

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

    JDBC Batch를 사용하면 코드가 굉장히 지저분해진다.

    지연 로딩 전략(Lazy Loading) 옵션을 사용한다.

UPDATE

/** 1. UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화 */

transaction.begin(); // [트랜잭션] 시작

changeMember(memberA);

deleteMember(memberB);

비즈니스_로직_수행(); // 비즈니스 로직 수행 동안 DB 로우 락이 걸리지 않는다.

// 커밋하는 순간 데이터베이스에 UPDATE, DELETE SQL을 보낸다.

/** 2. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋 */

transaction.commit(); // [트랜잭션] 커밋
  1. UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
  2. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋

지연 로딩(Lazy Loading)

객체가 실제로 사용될 때 로딩하는 전략


memberDAO.find(memberId)에서는 Member 객체에 대한 SELECT 쿼리만 날린다.

Team team = member.getTeam()로 Team 객체를 가져온 후에 team.getName()처럼 실제로 team 객체를 건드릴 때!

즉, 값이 실제로 필요한 시점에 JPA가 Team에 대한 SELECT 쿼리를 날린다.

Member와 Team 객체 각각 따로 조회하기 때문에 네트워크를 2번 타게 된다.

Member를 사용하는 경우에 대부분 Team도 같이 필요하다면 즉시 로딩을 사용한다.

즉시 로딩

JOIN SQL로 한 번에 연관된 객체까지 미리 조회하는 전략


Join을 통해 항상 연관된 모든 객체를 같이 가져온다.

애플리케이션 개발할 때는 모두 지연 로딩으로 설정한 후에, 성능 최적화가 필요할 때에 옵션을 변경하는 것을 추천한다.


참고

https://gmlwjd9405.github.io/2019/08/04/what-is-jpa.html
https://gmlwjd9405.github.io/2018/12/25/difference-jdbc-jpa-mybatis.html
https://dev-troh.tistory.com/150
https://gmlwjd9405.github.io/2019/08/03/reason-why-use-jpa.html
https://engkimbs.tistory.com/790
https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/
스프링부트와 aws로 혼자 구현하는 웹 서비스

3개의 댓글

comment-user-thumbnail
2020년 6월 28일

뭐야~ 인기글이야~

1개의 답글