Spring JPA

bw1611·2023년 8월 27일

📕 ORM 이란?


  • 객체 관계 매핑으로써 관계형 데이터베이스에 자동으로 맵핑해주는 역활을 합니다. 객체 관점으로 바라보는 것과 관계 관점으로 바라보는 것에 필연적으로 불편한 사항이 생기기 때문에 이 문제를 개선하기 위하여 등장하였습니다.

ㅇ 장점

  • 객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중할 수 있다.
    • ORM을 이용하면 SQL Query가 아닌 직관적인 코드로 데이터를 조작할 수 있어 개발자가 객체 모델로 프로그래밍 하는데 도움을 준다.
    • 선언문, 할당, 종료 같은 부수적인 코드가 없거나 줄어듬
  • 코드 재사용, 유지보수 편리
  • DBMS 종속성이 줄어듬
    • 종속적이지 않다는것은 구현 방법 뿐만아니라 많은 솔루션에서 자료형 타입까지 유효함
    • DBMS를 교체하는 거대한 작업에도 비교적 적은 리시크와 시간 소요
    • 객체 간의 관계를 바탕으로 SQL 자동생성하기 떄문에 RDBMS의 데이터 구조와 Java의 객체지향 모델 사이의 간격 좁힘

ㅇ 단점

  • N + 1 문제
  • 생성되는 쿼리 속도의 저하, 성능 저하

📔 JPA( Java Persistence API ) 란?


  • 자바의 ORM 표준 기술
  • 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스
  • Hibernate, OpenJPA 등이 JPA를 구현함

JPA를 사용하는 이유는 반복적인 CRUD SQL을 처리해주며 개발자가 어떤 SQL이 실행되는지 생각만하면되고 예측도 쉽게 가능하다.

  • JPA랑 Spring Data JPA는 차이가 있다.

  • 스프링에서 흔히 사용하는 JPA는 JPA를 이용하는 Spring Data Jpa 프레임워크이지 JPA는 아니다.

📗 Entity Manager 란?


  • 특정작업을 위해서 DB에 엑세스 하는 역할
  • Entity를 DB에 CRUD 하는 역할, 이름 그대로 엔티티 관련 일을 처리하는 관리자이다.

JPA는 실행 시 한 DB당 하나의 EntityManagerFactory를 생성하고 WAS가 종료되는 시점에 EntityManagerFactory도 사라집니다.
고객의 요청이 들어온다면 하나의 스레드를 생성하여 EntityManager를 만들고 Transaction이 종료되면 해당 스레드를 종료합니다.
📌 엔티티 매니저 팩토리는 스레드를 세이프하기떄문에 여러 스레드가 동시 접근이 가능하지만 EntityManager는 스레드가 동시에 접근하면 동시성 문제가 발생하기 때문에 공유가 안된다.

📘 영속성 컨텍스트란?


  • 말 그대로 엔티티를 영구 저장하는 환경을 말한다.
  • entityManager가 persist() 메서드를 사용하여 컨텍스트를 저장
  • entityManager 내부에 있는 영속성 컨텍스트를 통해 entity 관리
비영속 : 영속성 컨텍스트와 관계 X
영속 : 영속성 컨텍스트에 저장
준영속 : 영속성 컨텍스트에 저장되어있다가 분리
삭제 : 삭제된 상태
  • 영속성 컨텍스트가 엔티티를 관리할때 생기는 장점
    동일성 보장, 트랙잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩

Flush

  • 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것을 말함
  • Transaction commit이 일어날 떄 flush가 동작, 이떄 쓰기 지연 저장소에 쌓아 놨던 Insert, Update, Delete SQL들이 DB에 들어감

동작 과정
변경을 감지 -> 수정된 Entity를 쓰기 지연 SQL 저장소에 등록 -> 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송

📓 연관 관계 매핑


  • 객체의 참조와 테이블의 외래 키를 매핑
  • 다중성 : OneToOne, ManyToOne, OneToMany, ManyToMany
  • 연관관계의 주인 : 객체를 양방향 연관관계로 만들기 위해서는 주인이 필요

연관 관계 매핑 방법

  • ORM : 참조를 사용, 방향성이 존재
  • RDBMS : 외래 키를 사용, 항상 양방향

객체 연관관계 vs 테이블 연관관계
1, 테이블은 외래키를 이용해 연관관계 설정
2, 객체는 참조를 이용해 연관관계 설정
3, 테이블 연관관계는 항상 양방향
4, 객체 연관관계는 항상 단반향이지만 우리가 양방향이라고 말하는 것은 사실 두개의 단방향 연관관계를 뜻함

- 연관관계의 주인
ㅇ 양방향 연관관계는 단방향 연관관계 두개를 말하는 것이다.

  • 연관관계의 주인을 설정하는 이유는 양방향일경우 두 곳에서 서로 참조하기 때문에 연관관계를 관리하는 포인트가 2가지가 된다. 객체의 참조는 2개인데 외래키는 하나이기 때문에 차이가 발생하는데 이런 문제를 위해 JPA는 두 객체의 연관관계 중 하나를 정해서 테이블의 외래키를 관리하는데 이를 연관관계의 주인이라고 한다.

ㅇ @ManyToOne ( N : 1 )

  • 다대일 관계의 반대 방향은 항상 @OneToMany 관계이다.
  • DB의 테이블의 1:N 관계의 외래키는 항상 N쪽에 있기 때문에 객체 양방향 관계에서 주인은 항상 N쪽이다. ( 연관관계의 주인은 외래 키가 있는 곳이다. )

ㅇ @OneToMany ( 1 : N )

  • 다대일의 반대
  • 일대다는 엔티티를 하나 이상 참조할 수 있으므로 컬렉션을 사용
  • 객체가 관리하는 외래키가 다른테이블에 있기 때문에 연관관계 처리를 위한 Update SQL을 추가 실행 ( JoinColum을 항상 명싱해주어야 한다. )

ㅇ @OneToOne ( 1 : 1 )

  • 일대일의 경우 어떤 테이블이든 외래키를 가질 수 있음

ㅇ @ManyTOMany ( N : M )

  • RDB에서는 정규화된 테비을 2개로 다대다 관계를 표현하지 못함
  • 연결 테이블 사용 ( @JoinTable : 다대다 매핑을 위해 연결 테이블 설정 )
  • 중간 테이블을 두고 다대일, 일대다로 표현하는게 더 안정적이다.

ㅇ 프록시 (proxy)

  • 데이터가 실제 필요할 때 데이터를 불러오는 지연 로딩을 사용할 때 실제 엔티티 객체 대신 사용되는 가짜 객체
  • 진짜 객체는 자신의 기능에만 집중을 하고 그 이외 부가 기능을 제공하거나 접근을 제어하는 역할을 프록시 객체에거 위임한다.

1, 프록시 특징

  • 처음 사용할 때 한번만 초기화 된다.
  • 프록시 객체가 초기화되면 실제 엔티티에 접근 가능
  • 영속성 컨텍스트에 실제 엔티티가 존재하는 상황이라면 프록시가 아닌 실제 엔티티 반환
  • 초기화는 영속성 컨텍스트의 도움을 받는다. 따라서 준영속 상태의 프록시를 초기화하면 문제 발생

ㅇ 즉시 로딩과 지연 로딩의 차이점
즉시 로딩 : 엔티티를 조회할 때 연관된 모든 엔티티도 함께 조회
지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회 ( 프록시 조회

  • ManyToOne, OneToOne : 즉시로딩
  • OneToMany, ManyToMany : 지연로딩

📒 객체지향 쿼리언어


  • 복잡한 검색 조건을 사용하여 객체를 조회할 수 있는 다양한 쿼리 기술

JPQL : java persistence Query Language
Criteria 쿼리 : JPQL을 편하게 작성하도록 도와주는 API, 빌더 클래스 모음
네이티브 SQL : JPA에서 JPQL 대신 쿼리를 직접 사용 가능
QueryDSL : 쿼리처럼 JPQL을 편하게 작성하도록 도와주는 빌더 클래스, 비표준 오픈소스 프레임워크

ㅇ JPQL

  • 엔티티 객체를 조회하는 객체지향 쿼리
  • 문법이 SQL과 유사하지만 더 간결
String jpql= "select m From Member m where m.name like '%hello%'";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();

m은 별칭이며 m.name은 entity의 객체 필드명이다.

위의 결과로 실행되는 SQL은 아래와 같다.

select
  m.id as id,
  m.age as age,
  m.USERNAME as USERNAME,
  m.TEAM_ID as TEAM_ID
from
  Member m
where
  m.age>18

ㅇ JPQL의 fetch 조인
SQL 조인의 종류와 다릅니다. JPQL에서는 성능 최적화를 위해 제공하는 기능이며 연관된 entity나 컬렉션을 SQL 한 번에 함께 조회하는 기능입니다.

List<Member> resultList = em.createQuery("select m from Member m join fetch m.team", Member.class).getResultList();

위와 같이 fetch 조인을 사용하여 실행한 결과 쿼리는 1개로 한번에 조회되는 것을 볼 수 있다.

Hibernate: 
        select
       member0_.id as id1_0_0_,
       team1_.team_id as team_id1_1_1_,
       member0_.name as name2_0_0_,
       member0_.team_id as team_id3_0_0_,
         team1_.name as name2_1_1_ 
     from
         Member member0_ 
       inner join
       Team team1_ 
     on member0_.team_id=team1_.team_id

ㅇ 네이티브 SQL
JPQL에서 지원하는 SQL을 직접 사용하는 기능

String sql = "SELECT ID, NAME FROM USER WHERE NAME = `kim`"
List<User> userList = em.createNativeQuery(sql, User.class).getResultList();
  • JDBC를 직접 사용하거나 마이바트같은 매퍼 프레임워크도 사용 가능 ( 영속성 컨텍스트가 인지하지 못하는 이슈가 발생할 수 있음 )

📁 Spring Data JPA


스프링 프레임 워크에서 JPA를 편리하게 사용할 수 있도록 지원한다. 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성한다.

  • Spring Data JPA를 사용하여 쿼리를 생성할 때는 메서드 이름으로 쿼리를 생성하기 때문에 규격을 지켜야한다.
    참고 사이트 : https://kihwan95.tistory.com/5
List<User> findByUsername(String username);
List<User> findById(Long Id);

ㅇ @Query
@Query 어노테이션을 사용해서 리포지터리 인터페이스에 쿼리 직접 정의

  • nativeQuery = true로 지정하면 네이티브 쿼리가 사용 가능
@Query("SELECT user FROM User user where user.createdAt between ?1 to ?2")
List<User> getUserByCreatedAt(Date from, Date to);

📄 트랜잭션


데이터베이스의 상태를 변화시키기 위해서 수행하는 작업 단위를 말한다. 즉 SELECT, INSERT, DELETE, UPDATE 작업을 할 때 트랜잭션이 발생한다.
( 보통 서비스로직에서 트랜잭션이 시작되고 컨트롤러 부분에서 트랜잭션이 끝난다. )

ㅇ 스프링 컨테이너의 기본 전략

  • 동일 트랜잭션에 동일 영속성 컨텍스트를 사용
  • 트랜잭션이 다르면 영속성 컨텍스트도 다르다.

ㅇ 레이지 로딩을 사용할 경우 준영속상태 문제

  • 뷰가 필요한 엔티티를 미리 로딩
    - 글로벌 패치 전략을 Eager로 수정, 하지만 N + 1의 문제가 발생한다.
  • JPQL 패치 조인
    - 리포지토리 메서드 증가 문제
  • 강제 초기화
    - 서비스 계층에서 프록시 초기화를 한번 시켜주는 방법이지만 프리젠테이션 계층이 서비스 계층을 침범
  • FACADE 계층 추가
  • OSIV를 사용하여 엔티티를 항상 영속성상태로 유지하는 방법
    - 영속성 컨텍스트를 뷰까지 열어주는 것, 엔티티 수정은 트랜잭션이 있는 계층에서만 동작한다.

ㅇ 트랜잭션의 낙관전락과 비관전락

  • 낙관전락 : 트랜잭션 대부분 충돌이 발생하지 않는다고 가정하고 락을 사용
  • 비관전락 : 트랜잭션 대부분 충돌이 발생한다고 가정하고 락 사용
profile
Java BackEnd Developer

0개의 댓글