/jpa-practice

자바 ORM 표준 JPA 프로그래밍 예제 작성 레포지토리

Primary LanguageJava

JPA 소개

JPA를 써야하는 이유

반복적인 CRUD 쿼리를 작성하지 않아도 된다. (Entity 필드 추가에 대해 유연한 대처가 가능하다)

객체와 관계형 데이터 베이스간의 패러다임의 불일치를 해소할 수 있다.
상속, 연관 관계, 데이터 타입, 데이터 식별 방법 등...

데이터베이스 방언에 종속되어 있지 않다.

영속성 컨텍스트

엔티티 매니저 팩토리와 엔티티 매니저

요청마다 팩토리에서 엔티티 매니저를 하나 생성하고 엔티티 매니저는 커넥션풀에서 커넥션을 하나씩 가져가서 사용한다.

영속성 컨텍스트

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

persists는 디비에 저장하는 것을 의미하는게 아니라 영속성 컨텍스트에 저장한다는 것이다.

영속성 컨텍스트는 논리적인 개념이라 눈에 보이지 않는다. 매니저를 통해 컨텍스트에 접근한다.

엔티티 매니저를 생성하면 1:1로 영속성 컨텍스트가 생성된다.

엔티티의 생명주기

비영속 : 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

영속 : 영속성 컨텍스트에서 관되는 상태 persist를 하면 이 상태로 온다.

준영속 : 영속성 컨텍스트에서 저장되었다가 분리된 상태

삭제 : 삭제

비영속

객체를 생성한 상태이다. 즉, 영속성 컨텍스트와 관련이 없다.

영속

비영속 상태의 객체를 persist를 하는 순간 영속 상태가 된 것이다.

아직 디비에 저장된 상태는 아니다.
저장이 되려면 트랜잭션이 커밋이 되어야 한다.

준영속, 삭제

엔티티를 영속성 컨텍스트에서 분리한다.

엔티티를 삭제한다.

영속성 컨텍스트의 이점

  • 1차캐시
  • 동일성 보장 (==)
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

엔티티 조회, 1차캐시

persist하는 순간 1차캐시에 저장이된다.

그 다음 조회한다면 1차캐시에서 우선적으로 찾아서 결과를 가져온다.

1차 캐시에서 없는 엔티티를 조회하려 한다면 데이터 베이스에서 조회를 해서 결과를 1차캐시에 넣고 결과를 준다.

애플리케이션에서 전체가 공유하는 캐시가 아니라 엔티티 매니저 하나가 사용하고 삭제하는 캐시이다.

영속 엔티티의 동일성 보장

1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리수준(REPEATABLE READ)을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.

엔티티를 등록할때 트랜잭션을 지원하는 쓰기 지연

트랜잭션 내에서 발생하는 INSERT 쿼리를 다 쌓아두고 commit 할때 한꺼번에 데이터베이스에 전송한다.

SQ을 저장하는 곳은 쓰기지연 SQL 저장소이다.

이때 flush를 실행하면서 SQL을 전송한다.

엔티티는 상속을 이용해서 생성하기 때문에 protect 이상의 기본생성자가 필요하다.

SQL을 모을수 있는 양은 batch.size를 조절해서 설정할 수 있다.

엔티티 수정, 변경감지 Dirty Checking

commit을 하기 직전에 flush가 실행되고 엔티티와 스냅샷(1차캐시에 저장되어 있는)을 비교한다.

변경된 부분에 대한 UPDATE 쿼리를 작성해서 쓰기 지연 저장소에 저장하고 flush를 하여 데이터 베이스에 반영한다.

플러시

영속성 컨텍스트의 변경내용을 데이터 베이스에 반영하는 작업을 의미한다.

플러시 발생

변경 감지 실행

수정된 엔티티 쓰기 지연 SQL 저장소에 등록

쓰기지연 SQL 저장소의 쿼리를 데이터베이스에 전송

영속성 컨텍스트를 플러시하는 방법

  • em.flush() - 직접 호출

  • 트랜잭션 커밋 - 플러시 자동 호출

  • JPQL 쿼리 실행 - 플러시 자동 호출 - 1차캐시를 사용하지 않기 때문일 것 같다.

플러시를 한다해서 1차 캐시를 지우는 것이 아니다.

플러시 모드는 AUTO(커밋이나 실행할때 플러시 기본값), COMMIT(커밋을 할때만 플러시)

플러시는!

영속석 컨텍스트를 비우지 않음

영속성 컨텍스트의 변경내용을 데이터 베이스와 동기화하는 작업이다

트랜잭션이라는 작업단위가 중요하다 커밋 직전에만 동기화하면 됨

준영속 상태

영속 -> 준영속

영속 ㅅ강태의 엔티티가 영속성 컨텍스트에서 분리

영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.

만드는 법

  • em.detach(E e); - 하나 초기화

  • em.clear() - 통째로 초기화

  • em.close() - 영속성 컨텍스트를 종료해버린다.

엔티티 맵핑

JPA의 내부 매커니즘과 설계적인 측면이 가장 중요한 두 요소이다.

유니크 키 제약조건 생성방법

@Table(
        uniqueConstraints = @UniqueConstraint(
                name = "UK_MEMBER_NAME", columnNames = "NAME")
)

권장하는 식별자 전략은

Long 형 + 대체키 + 키 생성전략 사용

IDENTITY 전략은 쓰기지연을 할 수 없다.

연관관계 맵핑

객체와 테이블의 연관관계의 차이점은 연관관계를 1개를 맺느냐(데이터베이스) 2개를 맺느냐(객체)의 차이가 있다.

사실상 객체의 양방향 관계는 서로 다른 단방향 2개를 연결한 것이다.

문제는 외래키를 누가 업데이트 할것인가?

연관관계의 주인만이 외래키를 관리(=변경) 할 수 있다.

주인이 아닌 쪽은 읽기만 가능하다.

mappedBy : 연관관계의 주인이 아니라는 마크를 해준다. 연결된 객체에서 사용되는 변수의 이름을 사용한다.

외래키가 있는 곳을 주인으로 정해라. 일반적으로 그냥 N쪽이 연관관계의 주인이 된다고 보면된다.

양방향 연관관계 맵핑시 주의점

연관관계의 주인에게 값을 입력해야지 (주인이 들고있는 연관객체에) 업데이트 쿼리가 발생한다.
연관관계의 주인이 아닌 객체에게 값을 주입하는 경우 업데이트 쿼리는 발생하지 않는다.

순수객체라고 생각하고 양쪽에 값을 주입해 주어야한다.

그냥 단방향으로 다 바르고 시작하고 되도록 양방향은 하지마라

다대일

일대다

@JoinColumn을 사용하지 않으면 중간에 조인 테이블이 생성되어 버린다.

관리해야하는 외래키가 다른 테이블에 위치한다는 것이 큰 단점으로 작용한다.

양방향으로 적용할때는 Many쪽에서 insertable, updatable을 false로 사용하여 읽기전용으로 사용하라.

일대다 양방향을 쓰지말고 다대일 양방향으로 사용하자.

일대일

외래키에 유니크 제약조건을 추가해주고 사용해야한다.

대상 테이블에 지연로딩으로 설정을해도 프록시 기능의 한계 때문에 강제로 즉시 로딩된다.

다대다

고급맵핑

상속관계 맵핑 : InheritanceType

DiscriminatorColumn 으로 상위 클래스의 DTYPE 컬럼을 다른이름으로 지정해줄 수 있다.

DiscriminaorrValue 로 하위 클래스에서 DTYPE에 들어갈 row 값을 정해줄 수 있다.

조인 전략 : JOINED

단일 테이블 전략 : SINGLE_TABLE >> jpa의 기본 전략

구현 클래스마다 테이블 전략 : TABLE_PER_CLASS >> 쓰지마라 >> 상위 클래스로 조회때리면 UNION 쿼리 발생해서 망한다.

상위 클래스의 구현체가 필요가 없는 경우가 보통이기 때문에 추상클래스로 만들어서 사용하는 것이 좋을 수 있다.

@MappedSuperClass

공통 매핑 정보가 필요할때 사용한다 Id라던가... 시간이라던가..

@MappedSuperClass가 있는 abstract BaseEntity.class를 상속받아서 사용하자

직접 생성해서 사용할 일이 없으니 추상클래스로 만들어서 사용하자

프록시와 연관관계

프록시

실제 클래스를 상속받아서 만들어진다.

실제 클래스와 겉 모양은 같다.

프록시 객체는 실제 객체(target)의 참조를 보관하고 있다.

프록시 객체는 실제 객체의 메소드를 위임해서 전달해준다.

프록시에 실제 값이 없는 경우 영속성 컨텍스트에게 값을 요청하는 과정을 초기화라고 부른다.

프록시의 특징

프록시는 처음 한번만 초기화 된다.

초기화 한다고 해서 실제 객체로 치환되는 것이 아니다. 프록시를 통해 접근을 할 뿐!

프록시는 원본 엔티티를 상속받는 것이기 때문에 타입 비교를 할경우 원하는 결과가 나오지 않는다. 주의할것! instance of 쓰자

프록시가 한번 조회되면 em에서 프록시 조회가 아니더라도 프록시로 조회가 되고 target이 채워진 상태로 나오게 된다.

마찬가지로 처음에 실제 객체를 조회 했다면 프록시 조회를 해도 실제 객체를 반환한다.

준영속 상태일때 (ex. em.close())라면 LazyInit Exception 이 발생한다.

즉시로딩과 지연로딩

EAGER VS LAZY

일단은 LAZY로 사용하고 필요 할 때 fetch query를 작성하도록 하자!

즉시로딩 JPQL에서 N+1 문제를 발생시킨다.

N+1 문제 : 추가 쿼리가 N번 발생한다. > join 쿼리가 안나가서!

해결? 그냥 지연로딩으로 다 깔아버린다. 그리고 fetch 쿼리!

영속성 전이(Cascade)와 고아 객체

연관관계 맵핑과는 아무런 연관관계가 없으니 주의할 것!

쓰면 안되는 케이스는 1:1관계로 관리가 되는 것이 아니라 어떠한 정보를 여기저기서 관리한다면 사용해선 안된다.

예를들어 Parent 객체가 Child를 알고 있을때 Parent만 Child를 알고 있다면 써도 되지만 그렇지 않은경우, Child를 다른 객체와 연관관계를 맺고 있다면 사용해선 안된다.

orphanRemoval = true 를 줘서 delete query를 발생시킬 수 있다.

마찬가지로 참조하는 곳이 하나일때 사용하자. 특정 엔티티가 개인 소유할때!

CascadeType.ALL + orphanRemoval = true -> 부모 엔티티로 자식 엔티티의 생명주기를 관리할 수 있다.

데이터 타입

엔티티 타입

@Entity로 정의하는 객체

데이터가 변해도 식별자로 지속해서 추적 가능

값 타입

식별자가 없고 값만 있으므로 변경시 추적 불가

기본 값 타임

자바 기본 타입, 래퍼 클래스, String

생명주기를 엔티티에 의존한다.

값 타입은 공유하면 안된다.

임베디드 타입 (= 복합 값 타입)

새로운 값 타입을 직접 정의할 수 있음

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

정보의 추상화를 가능하게 해준다.

재사용이 가능하고 응집도가 높다.
해당 값 타입만 사용하는 의미있는 메소드를 만들 수 있다.

값 타입 정의하는 곳 : @Embadable

값 타입 사용하는 곳 : @Embadded


한 엔티티에서 같은 값 타입을 사용한다면?

@AttributeOverrides + @AttributeOverride 를 사용해서 컬럼 명 속성을 재정의 할 수 있다.

name에 변수명 column=@Column("컬럼명") 으로 재정의 한다.


값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다.

객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다.

객체의 공유 참조는 피할 수 없다.

따라서

객체타입을 수정할 수 없게 불변 객체로 설계해야한다.

값 타입은 인스턴스(주소)가 달라도 그 안에 값이 같으면 같은 것으로 봐야한다.

동일성 비교 : 인스턴스의 참조 값 비교

동등성 비교 : 인스턴스의 을 비교

컬렉션 값 타입

다양한 쿼리

JPQL

표준 문법이다. 근본이다.

엔티티를 대상으로 쿼리를 작성한다.

EntityManager.find() 를 통해 조회를 한다.

특정 데이터베이스에 종속적이지 않다.

QueryDSL

Criteria가 구리니까 이 라이브러리를 사용하면된다.

컴파일 오류로 문법 오류를 잡아줄수 있고 자바코드로 쿼리를 작성할 수 있다.

동적쿼리 작성이 편리하다.

JPQL빌더의 역할을 한다.

실무에 사용하는것을 권장한다.

네이티브 SQL

직접 SQL 사용한다.

데이터베이스에 종속적인 쿼리 작성이 가능해진다.

근데이거 쓰지말고

Spring JdbcTemplate

를 사용하자

직접 Jdbc 커넥션을 사용한다.

영속성 컨텍스트를 적절한 시점에 flush 해줘야한다.

기본 문법과 쿼리 API

TypeQuery : 반환 타입이 명확 할 때

Query : 반환 타입이 명확하지 않을 때

프로젝션

SELECT 절에 조회할 대상을 지정하는 것

프로젝션의 대상 : 엔티티, 임베디드 타입, 스칼라 타입

엔티티 프로젝션을 하면 영속성 컨텍스트에서 관리를 한다.

여러값 조회시

  1. 쿼리 타입으로 조회
  2. Object[] 타입으로 조회
  3. New 명령어로 조회
    • 새로운 dto로 생성자 사용

페이징

JPA는 페이징을 setFirstResult와 setMaxResults 로 추상화하였다.