Post

자바 ORM 표준 JPA

영속성 컨텍스트

  • 엔티티를 영구 저장하는 환경 이라는 뜻

  • 영속성 컨텍스트는 논리적인 개념

  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근

    image

엔티티의 생명주기

  • 비영속(new/transient) - 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

    1
    
    Member member = new Member(); // 객체를 생성 (비영속)
    
  • 영속(managed) - 영속성 컨텍스트에 관리되는 상태

    1
    2
    3
    4
    5
    6
    
    Member member = new Member();
      
    EntityManager em = emf.createEntityManager(); // 엔티티 매니저 생성
    em.getTransaction.begin(); // 트랜잭션 시작 (JPA는 트랜잭션 위에서만 실행)
      
    em.persist(member); // 객체를 저장 (영속)
    
  • 준영속(detached) - 영속성 컨테스트에 저장되었다가 분리된 상태

  • 삭제(removed) - 삭제된 상태

영속성 컨텍스트의 이점

  • 1차 캐시

    • 영속성 컨텍스트는 DB에서 한번 조회한 데이터를 1차 캐시에 저장해둔다.

    • 특정 엔티티를 찾을 때 영속성 컨텍스트는 1차 캐시에 있는지 먼저 확인하고 있으면 해당 데이터를 반환, 없으면 DB에 조회 후 1차 캐시에 저장한 뒤 반환한다.

      image-20240725142227224

  • 동일성(identity) 보장

    image

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

    • 엔티티 매니저는 커밋하는 순간 데이터베이스에 쿼리를 보냄

    • 엔티티 매니저에는 쓰기 지연 SQL 저장소가 있어 insert (쓰기)를 사용하면 해당 저장소에 저장 후 1차 캐시에 저장한다.

    • 그리고 커밋 시점에 해당 insert 쿼리를 날린다.

      image

      image

  • 변경 감지 (Dirty Checking)

    • 엔티티 매니저는 변경 감지를 통해 em.update()와 같은 업데이트 메소드를 직접 호출하지 않고 update를 처리할 수 있다.

    • 처음 1차 캐시에 엔티티를 저장할 때 스냅샷을 미리 저장해둔 뒤 flush (보통 커밋) 시점에 해당 스냅샷과 비교해 update sql을 알아서 생성한 뒤 날려준다.

      image

  • 지연 로딩 (Lazy Loading)

flush

  • 영속성 컨텍스트의 변경 내용을 DB에 반영

    • 변경 감지
    • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
    • 쓰기 지연 SQL 저장소의 쿼리를 데이터 베이스에 전송
  • 영속성 컨텍스트를 플러시하는 방법

    • em.flush() : 직접 호출

    • 트랜잭션 커밋 : 자동 호출

    • JPQL 쿼리 실행 : 자동 호출

      • 이유는 조회 시 쓰기 시 1차 캐시에 있는 값들은 쓰기 지연 때문에 DB에 바로 반영이 안된다.

      • 근데 만약 한 트랜잭션 안에서 쓰기를 한 뒤 바로 조회 쿼리를 날리면 어떻게 될까?

      • 우리가 기대하는 결과는 위에서 작성한 쓰기를 포함한 결과가 나오길 원한다. 그렇기 때문에 JPA는 JPQL 쿼리 실행 시 1차 캐시에 있는 데이터와 DB 데이터를 똑같이 맞춰주기 위해 플러시를 실행해준다.

        image

  • 주의! 플러시는 영속성 컨텍스트를 비우지 않음, 영속성 컨텍스트의 변경내용을 데이터베이스와 동기화해주는 것

Entity Mapping

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리, DB 테이블과 매핑할 클래스는 @Entity 필수
  • 주의! : 기본 생성자가 필수, pulic, protected 접근제어자만 허용, 보통 protected로 캡슐화

@Table

  • 엔티티와 매핑할 테이블 지정

DB 스키마 자동 생성

  • JPA는 자동으로 DDL을 만들고 자동으로 생성해준다.

  • 각 DB에 맞는 DB 방언을 활용해 적절한 DDL을 만들어준다.

  • 보통 이렇게 만들어진 DDL은 개발에서만 사용하고 운영에는 수정한 DDL을 사용해야함.

  • 속성

    image

  • 운영 서버에서는 절대 create, create-drop, update를 사용하지 않고, 개발 초기에는 create나 update, 테스트 서버는 update나 validate, 스테이징과 운영서버는 validate나 none을 사용한다.

  • DDL 생성 기능 : DDL 생성시에만 사용되고 런타임에는 영향 X

    • 칼럼 제약조건 추가 : @Column(….)
    • 테이블 제약조건 추가 @Table(…..)

매핑 어노테이션

  • @Column : 칼럼 매핑

    image

  • @Transient

    • 필드 매핑 X, DB에 저장 X, 조회 X
    • 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용

기본 키 매핑 어노테이션

  • @Id
  • @GeneratedValue
    • IDENTITY : DB에 위임, 주로 MySQL에서 사용
      • JPA는 트랜잭션 커밋 시점에 insert SQL 실행함
      • Mysql의 auto_increment는 DB에 insert SQL 실행 후 ID 값을 알 수 있음
      • IDENTITY 전략은 em.persist() 시점에 즉시 insert SQL 실행하고 DB에서 식별자 조회
    • SEQUENCE : 데이터베이스 시퀸스 오브젝트 사용, 주로 Oracle에서 사용
    • TABLE : 키 생성용 테이블 사용
    • AUTO : 방언에 따라 자동 지정

연관관계 매핑 중 양방향 매핑에 관해

  • 양방향으로 설계시 연관관계 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌 쪽은 읽기만 가능
  • 양방향 매핑은 보통 하지 않는게 좋음
  • 양방향 매핑은 객체 그래프 탐색이나 fetchJoin을 용이하게 하기 위함인데 했을 때 순환참조나 여러 위험이 있음
  • 왠만하면 하지 말자….

상속관계 매핑

  • RDB에는 상속 관계 X

  • 슈퍼 타입과 서브 타입 관계라는 모델링 기법이 객체 상속과 유사

    image

  • 슈퍼 타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법 3가지

    • 각각 테이블로 변환 -> 조인 전략
    • 통합 테이블로 변환 -> 단일 테이블 전략
    • 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략
  • @Inheritance(strategy=InheritanceType.XXX)와 같이 사용(JOINDE, SINGLE_TABLE, TABLE_PER_CLASS)

  • 조인 전략

    image

    • 장점
      • 테이블 정규화
      • 외래 키 참조 무결성 제약조건 활용가능
      • 저장공간 효율화
    • 단점
      • 조회시 조인을 많이 사용, 성능 저하
      • 조회 쿼리가 복잡
      • 데이터 저장시 insert SQL 2번 호출
  • 단일 테이블 전략

    image

    • 장점
      • 조인이 필요 없어 조회 성능이 빠름
      • 조회 쿼리가 단순
    • 단점
      • 자식 엔티티가 매핑한 컬럼은 모두 null 허용
      • 단일 테이블에 모든 것을 저장하므로 테이블이 커짐. 상황에 따라서 조회 성능이 느려질 수 있음
  • 구현 클래스마다 테이블 전략 - 안 씀

    image

    • 장점

      • 서브 타입을 명확하게 구분해서 처리할 때 효과적

      • not null 제약조건 사용 가능

    • 단점

      • 여러 자식 테이블을 함께 조회할 때 성능이 느림 (UNION SQL 필요)
      • 자식 테이블을 통합해서 쿼리하기 어려움

@MappedSuperClass

  • 공통 매핑 정보가 필요할 때 사용

    image

  • 상속관계 매핑 X

  • 엔티티X, 테이블과 매핑 X

  • 부모 클래스를 상속받은 자식 클래스에 매핑 정보만 제공

  • 조회, 검색 불가

  • 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장

  • 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할

  • 등록일, 수정일 등 전체 엔티티에서 공통으로 적용할 정보를 모을 때 사용 (BaseEntity)

프록시

  • em을 이용해 DB에서 데이터를 가져오는 방법으로는 em.find(), em.getReference() 두 가지 방식이 있다.

    • em.find() : DB를 통해 실제 엔티티 객체 조회
    • em.getReference() : 데이터베이스 조회를 미루는 프록시 엔티티 객체 조회
  • em.getReference()와 같이 프록시 엔티티 객체는 실제 객체를 사용시 초기화를 요청한다.

  • 초기화를 요청하면 영속성 컨텍스트에서는 해당 엔티티와 같은 identity를 가진 객체를 조회하고 없다면 DB에서 조회한다.

    image

  • 특징

    • 프록시 객체는 처음 사용할 때 한 번만 초기화
    • 객체를 초기화 시 프록시 -> 실제 엔티티로 바뀌는 것이 아니라, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능해진다. (영속성 컨텍스트에 1차 캐싱이 됨)
    • 이때 주의할 점은 프록시 객체는 원본 엔티티를 상속 받았기 때문에 타입 체크시 ==을 해서는 안되고 instance of 사용해야된다.
    • 또 처음 조회 시 영속성 컨텍스트에 프록시로 조회하면 포록시가 담기고, 실제 엔티티를 조회하면 엔티티가 담긴다. 그 후 같은 트랜잭션 안이라면 처음 담긴 객체가 리턴된다.

지연로딩

  • 연관관계 매핑 시 지연로딩과 즉시로딩을 설정할 수 있다.
  • 즉시 로딩은 해당 엔티티가 로딩 시 바로 같이 로딩되고, 지연로딩은 바로 로딩되지 않는다.
  • 여기서 중요한 점은 항상 지연로딩만을 사용해야된다는 것이다.
  • 즉시 로딩 사용시 예상치 못한 SQL이 발생하고 N + 1 문제를 일으킨다.
  • 지연 로딩은 위에서 살펴본 것과 같이 연관관계 매핑이 되어있는 엔티티를 프록시로 조회한다.
  • 그렇기 때문에 연관관계에 걸려있는 객체를 사용시 로딩되어 즉시 로딩의 문제들을 해결 가능하다.

영속성 전이 - CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
  • 주의! 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음, 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공하는 것일 뿐

고아 객체

  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
  • 주의!
    • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
    • 참조하는 곳이 하나일 때 사용해야함
    • @OneToOne, @OneToMany만 가능
    • CascadeType.REMOVE처럼 동작

영속성 전이 + 고아 객체

  • CascadeType.ALL + orphanRemoval=true
  • 이와 같이 설정하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다. 보통 부모 엔티티와 자식 엔티티의 생명 주기가 똑같을 때 사용할 수 있음
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용

값 타입

  • 값타입이란 식별자가 없는 객체를 뜻함

기본 값 타입

  • ex) int, String, Integer
  • 생명 주기를 엔티티에 의존
  • 다른 객체와 공유하면 안됨

임베디드 타입

  • 새로운 값 타입을 직접 정의 가능

  • JPA는 임베디드 타입(embedded type)이라 함

  • 주로 기본 값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 함

    image

  • 사용법

    • @Embeddable : 값 타입을 정의한 곳에 표시
    • @Embedded : 값 타입 사용하는 곳에 표시
    • 기본 생성자 필수
    • 두 어노테이션 중 하나만 붙여도 사용 가능한데 보통 둘 다 붙임 (명확하게 하기 위해)
  • 장점

    • 재사용
    • 높은 응집도
    • 의미 있는 메소드를 만들 수 있음
  • 테이블 매핑

    image

    • 임베디드 타입은 엔티티의 값일 뿐, DB 테이블은 임베디드 타입을 사용하든 안 하든 상관 X
    • 하지만 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능해진다.
  • 한 엔티티에 같은 값 타임을 여러개 사용할 시

    • @AttributeOverrides, @AttributeOverride를 사용해서 컬러 명 속성을 재정의 할 수 있음

값 타입과 불변 객체

  • 값 타입은 단순하고 안전하게 다뤄야 함
  • 하나의 값 타입을 여러 엔티티에서 공유해서는 안됨 (side effect 발생)
  • 실제 인스턴스를 공유하는 대신 값을 복사해서 사용해야 함
  • 한계
    • 자바는 항상 call by value이기 때문에 참조 값을 직접 대입하는 것을 막을 수 없다.
    • 값 타입도 객체 타입이기 때문에 메모리에 있는 주소가 대입된다.
  • 해결
    • 객체 타입을 수정할 수 없게 만들어야하기 때문에 값 타입은 항상 불변 객체(immutable object)로 설계해야한다.
    • 생성자로만 값을 설정하고 setter를 만들지 않는 등
  • 값 타입의 비교
    • 값 타입은 객체이기 때문에 == 비교를 사용해서는 안된다. equals()를 사용해 동등성 비교를 해야한다.
    • 값 타입 내부에 equals() 메소드를 적절히 재정의 해야한다.

JPQL 문법

  • CASE 식

    image

    • COALESCE : 하나씩 조회해서 null이 아니면 반환
    • NULLIF : 두 값이 같으면 null 반환, 다르면 첫번째 값 반환

출처)

[자바 ORM 표준 JPA 프로그래밍 - 기본편 강의김영한 - 인프런 (inflearn.com)](https://www.inflearn.com/course/ORM-JPA-Basic)
This post is licensed under CC BY 4.0 by the author.