JPA 기본편 정리

2024. 7. 19. 21:29BE & Infra

JPA란?

  • 자바 진영의 ORM 기술 표준.
  • ORM은 객체는 객체대로, 관계형 DB는 DB대로 설계하고 중간에 ORM 프레임워크가 매핑 역하을 수행하는 것을 의미한다.
  • JPA 는 Application과 JDBC 사이에서 동작한다.

JPA사용 이유

  • 생산성 : CRUD 작업 간결화
  • 유지보수 : Fileld만 수정하면 JPA가 자동으로 SQL 수정
  • 패러다임의 불일치 해결 : 객체지향은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등의 다양한 장점을 가지고 있지만 이를 저장할 때는 관계형 DB를 사용함.
    • 상속 : 상속 관계의 객체들은 DB로 저장할때 JOIN문이 필요하다.
    • 연관관계 : FK를 가져야함으로 SQL에 의존적임
    • 객체 그래프 탐색과 Entity 신뢰 문제 : 객체와 달리 SQL을 날린 시점에서 객체에 어느 연관관계가 존재하는 지 알 수 없어 Entity를 신뢰할 수 없음.
    • 객체 비교 : 기존의 방식이라면 같은 ID를 가진 객체를 조회해도 매번 새로운 객체를 생성해 비교가 불편함.
  • 성능
    • 1차 캐시와 동일성 보장 : 같은 트랜잭션에서는 캐싱을 통해 같은 Entity를 반환한다.
    • 트랜잭션을 지원하는 쓰기 지연 : commit할때까지 SQL을 모으다가 JDBC BATCH SQL을 이용해 모은 SQL을 한번에 전송
    • 즉시 로딩과 지연 로딩 : 지연로딩으로 실제 객체가 사용될 때 로딩이 가능함
  • 데이터 접근 추상화 벤더 독립성
    • JPA는 interface 들의 집합으로 특정 DB에 종속적이지 않음. 따라서 DB마다 JPA에 DB Dialect를 설정하면 대부분의 DB를 사용할 수 있음

JPA 구동 방식

  • 먼저 persistence.xml과 같은 파일을 조회해서 설정에 맞게 DB 구성
  • DB에 접근할 때 매번 커넥션을 생성하는 **EntityManagerFactory** 생성. 이는 각 DB당 하나만 생성해서 Appliction 전체에서 공유해야 합니다.
  • 각 커넥션 마다 Entity가 생성되어 트랙잭션을 처리한 후 소멸된다. 이는 쓰레드 간에 공유하면 안되며 각각의 커넥션 마다 생성하고 다 사용했다면 버려야 한다.

영속성 관리

JPA에서 가장 중요한 것은 영속성 Context객체와 관계형 DB 매핑이다.

영속성 Context

  • Entity를 영구 저장하는 환경
  • EntityManager를 통해 영속성에 접근한다.

영속성 생명 주기

  • 비영속(new/transient) : 영속성 Context와 전혀 관계 없는 새로운 형태
  • 영속(managed) : JPA를 통해 객체가 영구 저장(DB에 저장)된 상태
  • 준영속(detached) : 영속 상태였던 객체를 영속성 Context에서 분리한 사태
  • 삭제(removed) : 객체를 삭제한 상태

사용시 이점

  • 1차 캐시
    • em.persist(member)로 객체를 영속화 하면 EntityManager에서 관리하는 1차 캐시에 객체를 저장한다. 실제 DB에 저장되는 시점은 트랜잭션이 commit될 때이므로 같은 트랜잭션에서 객체가 변경되는 정보들을 모아서 한번에 커리를 날린다.
    • 객체를 가져오는 방법은 1차 캐시에서 조회한 후에, 캐시에 없다면 DB에 접근해서 조회한다.
  • 이 경우를 조금 더 깊게 생각한다면 두가지로 나눌 수 있다.
    • 같은 EntityManager에서 비교하는 경우 : 같은 객체로 인식
    • 다른 EntityManager에서 비교하는 경우 : 다른 객체로 인식
  • 쓰기 지연 : 쓰기 지연 저장소에 들어가 있다가, commit이 일어난다면 그때 DB에 한번에 보낸다.
  • 변경 감지
    • 영속성 Context에 관리하는 객체에 정보 변경이 생긴 경우, 1차 캐시에 저장되어 있는 스냅샷과 비교를 하고 달라진 부분에 맞게 SQL을 생성해서 쓰기 지연 SQL 저장소에 저장한다. 그리고 Commit되는 순간 Flush한다.
    • Flush 된다고 1차 캐시는 비워지지 않는다.

Entity 매핑

JPA는 DB schema 자동 생성 옵션이 존재. DB Dialect를 활용해서 DDL을 애플리케이션 실행 시점에 자동으로 생성한다.

  1. create : 기존의 테이블 삭제후 다시 생성
  2. create-drop : 종료 시점에 테이블을 삭제
  3. update : 변경 분만 반영
  4. validate : 매핑이 정상적인지만 판단. 다르다면 error

객체와 테이블 매핑

  • @Entity
    • Entity는 기본 생성자가 필수
    • final과 같은 field가 있으면 안된다.
  • @Table : Entity와 매핑할 테이블을 지정
    • name 속성을 이용해 매핑할 테이블 이름을 정할 수 있다. 기본값은 Entity이름이다.
    • catalog, schema, uniqueConstraints 같은 속성 존재

Field와 Column의 매핑

  • Entity의 Field와 DB Column을 매핑할 때 사용하는 annotation 속성이 있다.
@Entity
@Table(name = "Member")
public class Member {

	@Id
	@GeneratedValue(stategy = GenerationType.AUTO)
	private Long id;
	
	@Column(name="name", nullable = false)
	private String username;

...
}
  • @Column
    • name : 매핑할 column 이름
    • insertable, updatable : 등록 변경 가능 여부(기본값 true)
    • nullable : null값 허용 여부
    • unique : 하나의 column에 unique 제약 조건
    • columnDefinition : DB Column 정보를 직접 설정
    • length : String 타입의 문자 길이 제약 조건
    • precision / scale : BigDecimal 타입에서 표현 정도
  • @Temporal
    • 자바 날짜 타입을 매핑할 때 사용. 근래에 들어서는 LocalDate LocalDateTIme을 타입으로 하면, 최신 하이버네이트가 지원하기 때문에 annottion 생략 가능
  • @Enumerated
    • 자바 enum 타입을 매핑할 때 사용. 다만 추후 요소가 추가가 될 경우를 대비해서 DB 공간을 조금 더 차이해도 EnumType.String 사용해야 한다.
  • @Lob
    • DB의 BLOB나 CLOB 타입과 매핑한다.
  • @Transient
    • 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶은 경우에만

기본 키 매핑

  • @Id 어노테이션을 이용해 할당
  • 자동생성
    • IDENTITY : DB에 저장해야 기본 key를 알수 있음. 따라서 바로 sql이 보내진다.
    • sequence : key에 알맞는 유일한 값을 순서대로 생성한다. 이 또한 DB에 접근해야 key를 알수 있다!
    • table..

연관관계 매핑 기초

  • 객체 안에 FK를 Long으로 저장하게 된다면, 두 객체 사이에는 연관관계가 없어진다. 따라서 FK를 저장하는 것이 아닌 객체로 구성한다.
  • 연관관계의 주인이란, 비즈니스 로직 상의 상하관계와는 별도로, 단순히 테이블 구조 상에서 FK를 관리하는 Entity를 의미한다.회원 → 팀, 팀 → 회원 처럼. FK를 관리하는 것이 연관관계의 주인. 주인만이 FK를 관리하고, 주인이 아니 쪽은 읽기만 가능하다.
  • 그래서 Member의 Team Field에서 @JoinCoumn으로 주인임을 나타내고 Team Field에서는 mappedBy로 주인이 아님을 인식.
  • 만일 Team에 있는 Members에다가 member를 그냥 add함으로 역방향으로 연관관계를 주입한다면, Flush가 되어 영속화가 되면 역방향 Member의 연관관계는 사라진다. 따라서 setTeam으로 단방향으로만 Field값을 주입하는 것 대신에 모두 넣어주는 방식으로 연관관계 편의 메서드를 생성해서 설정해야 한다. setTeam 보다는 addTeam으로 작성하고 그 안에서 set과 add 모두 동작하도록 한다.
  • N:1 관계 : FK를 가지고 있는 족이 연관관계의 주인
  • 1: 1 관계 : 두 테이블 모두 FK가 위치할 수 있음. 주 테이블에 FK를 갖도록 하는 것이 릴반적.
  • N:M 관계 : 관계형 DB에서는 표현할 수 없음. 중간에 연결 테이블 해서 해결해야함.

연관관계 심화

  • 상속관계 매핑 : 관계형 DB에서는 슈퍼타입과 서브타입으로 유사하게 매핑
    • Joined 전략 : Entity들을 각각 테이블로 변환하는 방법. 값을 가져오거나 저장시 조금 복잡하고 성능이 저하될 수 있음.
    • SingleTable 전략 : 하나의 테이블로 묶어 버리는 방법, 단순화된 구조 때문에 조회 성능이 빠르지만 자식 Entity가 매핑한 Column은 모두 null 허용이고 테이블 크기가 커질 수 있다.
    • Table per class 전략 : 구현 클래스 마다 테이블을 만드는 전략. 사용하지 말자
  • @Inheritance annotation
  • DiscriminatorValue …. MappedSuperclass… 추가적인 공부가 필요해 보인다.

프록시와 연관관계 관리

프록시

프록시는 실제 클래스는 상속받아 만들어지며 겉 모양은 같다. 다만 실제 값이 필요할 때까지 DB조회를 미룰 수 있어서, 한 Entity와 연관된 다른 Entity들을 모두 가져올 필요 없을 때 프록시를 사용한다.(지연로딩)

프록시 객체가 메서드를 호출하기 위해 실제 객체의 가져야 함. → 영속성 컨텍스트를 통해 DB에 조회 → 가져온 정보로 실제 Entity 생성. → 프록시 객체가 해당 Entity를 가르키도록 설정.

프록시 객체의 특징

  • 프록시 객체는 처음 사용할 때 한번만 초기화된다.
  • 프록시 객체 초기화 시, 프록시 객체가 실제 Entity로 바뀌는 것이 아닌 참조를 통해 동작한다.
  • JPA에서는 프록시 객체 조회 후 실제 Entity를 조회하는 경우라도, 두 객체가 모두 프록시 객체를 반환받도록 한다.
    • 동일한 트랜잭션에서는 == 을 해도 동작하지만, 아니라면 == 대신 instance of를 사용하는 것이 좋다.
  • 영속성 Context에 찾고자 하는 Entity가 이미 있다면 em.getReference() 하더라도 실제 Entity가 반환된다.
  • 영속성 Context의 도움을 받을 수 없는 준영속 상태인 경우, 프록시 객체를 초기화하려하면 Exception이 발생한다.
  • 영속성 context에서 더이상 관리하지 않는 준영속 상태의 객체의 값을 가져오려 한다면, LazyInitializationException 예외가 발생한다.

즉시로딩과 지연로딩

  • 회원 정보만 필요하다면, 팀을 조회하는 것은 손해.
  • 실무에서 즉시 로딩을 쓴다면 N+1 가능성이 높음.
  • @ManyToOne, @OneToOne의 경우 디폴트가 즉시로딩이므로 지연로딩으로 설정해야 한다.

영속성 전이

  • 특정 Entity를 영속 상태로 만들 때, 연관된 Entity도 함께 영속 상태로 만들고 싶을 때 사용하는 방법.
  • 영속성 전이는 연관관계 매핑과 관련이 없고, 단지 Entity를 영속화할 때 연관된 Entity도 함께 영속화하는 편리함을 제공한다.

고아 객체

  • 부모 Entity와 연관관계가 끊어진 자식 Entity를 나타낸다. 이 경우 고아 객체를 제거하게끔 설정할 수 있다.
  • 특정 Entity만이 해당 Entity를 소유하는 경우에만 사용해야 한다. 그렇지 않다면 다른 Entity에서 예상치 못하게 추가되거나 삭제될 수 있음

값 타입

  • JPA의 데이터 타입은 크게 2가지가 존재.
  1. Entity 타입
    1. @Entity로 정의하는 객체
    2. 데이터가 변해도 식별자를 통해 지속해서 추적 가능
  2. 값 타입
    1. 단순히 값으로 사용하는 자바 기본 타입/객체
    2. 식별자가 없고 값만 존재하므로 변경시 추적 불가
    3. 값 타입을 소유한 Entity에 생명주기를 의존
    4. 기본 값 타입, Embedded, Collection 으로 분류

값 타입

  • 자바 기본
    • 자바 기본 타입은 항상 값을 복사함
    • Wrapper나 String의 경우 공유는 가능해도 불변 객체로 동작해, 한번 만들어진 객체는 데이터 수정이 불가함
  • Embedded 타입
    • 주로 기본 값 타입을 모아서, 새로운 타입을 직접 정의한 것을 의미.
    • @Embeddable을 통해 표기가 가능하다.
    → 불변 객체로 만드는 게 좋다. (생성자로만 값을 만들고 세터를 만들지 않는 것)
  • Collection
    • List, Set같은 Collection을 사용하기도 한다. 하지만 DB에는 Collection을 하나의 테이블에 저장할 수 없음.
    • 물론 이것도 저장하는 방법이 존재하지만 비추
      • 값 타입은 식별자 개념이 없음
      • 값 변경하면 추적이 어렵다
      • Collection에 변경이 일어나면 주인 Entity와 연관된 모든 데이터를 삭제하고, 값 타입 Collection에 있는 현재 값을 모두 다시 저장

JPQL

  • jpa를 이용하면 Entity 중심으로 개발할 수 있지만, DB를 검색할 때의 Query문이 문제다 → 검색할 때에도 테이블이 아닌 Entity 객체를 대상으로 검색하도록 JPQL를 사용한다.
  • jpa는 sql을 추상화한 객체 지향 쿼리 언어는 jpql을 제공.

기본 문법

  • Entity와 속성은 대소문자를 구분
  • JPQL 키워드는 대소문자를 구분하지 않는다.
  • 테이블 이름이 아닌 Entity이름을 사용한다.
  • 별칭은 필수로 사용해야 한다.
  • 변환 타입이 명확하다면 TypedQuery, 그렇지 않다면 Query

Fetch Join ⭐⭐⭐

  • N+1 문제를 해결하는 방법
  • String query2 = "select m from Member m join fetch m.team"; // fetch join문
  • SQL에서 distinct는 중복된 결과를 제거하는 명령이라면, JPA에서는 SQL에 distinct를 추가하고 Application에서 중복 Entity를 제거해줍니다.
  • 한계
    • 대상에게 별칭이 불가함. h2에서는 ㄴ몰라도 가급적 사용하지 않는 것이 좋다
    • 둘 이상의 collection은 fetch join이 불가능하다
    • collection을 Fetch Join하면 Pasing API를 사용할 수 없다

 

 

---

ref : https://velog.io/@tmdgh0221/JPA-%EA%B8%B0%EB%B3%B8%ED%8E%B8-%EC%A0%95%EB%A6%AC

  •  

'BE & Infra' 카테고리의 다른 글

RabbitMQ  (0) 2024.08.23
N+1 문제 정리  (0) 2024.08.18
WebRTC  (0) 2024.07.15
Nginx  (0) 2024.07.01