JPA 기본편 정리
2024. 7. 19. 21:29ㆍBE & 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을 애플리케이션 실행 시점에 자동으로 생성한다.
- create : 기존의 테이블 삭제후 다시 생성
- create-drop : 종료 시점에 테이블을 삭제
- update : 변경 분만 반영
- 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가지가 존재.
- Entity 타입
- @Entity로 정의하는 객체
- 데이터가 변해도 식별자를 통해 지속해서 추적 가능
- 값 타입
- 단순히 값으로 사용하는 자바 기본 타입/객체
- 식별자가 없고 값만 존재하므로 변경시 추적 불가
- 값 타입을 소유한 Entity에 생명주기를 의존
- 기본 값 타입, 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 |