
JPA로 토이 프로젝트를 진행해보면서 헷갈련던 것들을 정리해보았다!!
자바 ORM(Object RelationShip Mappping: 객체와 관계형 DB 매핑)기술에 대한 API 표준 명세.
개발자가 CRUD문을 작성할 때 자바 객체를 애플리케이션과 DB 사이에서 넣고 뺄 때 매핑을 반복해야 하는 문제가 발생하여, 객체는 OOP적으로, DB는 DB대로 설계 후 ORM이 중간에서 2개를 매핑하는 역할을 한다.
commit하면 영속성 컨텍스트에 있는 엔티티가 데이터베이스에 반영됨.-EntityManager는 실제 데이터베이스 작업이 필요할 때 커넥션을 요청.
-persist(), find(), merge(), remove() 등의 작업이 호출되면 커넥션 풀에서 커넥션을 가져온다.
-작업이 끝나면 커넥션은 커넥션 풀로 반환
-트랜잭션이 없는경우 작업이 끝나는 즉시 반환
-Entity Manager에 의해 관리되며 JPA의 모든 동작이 이 안에서 이루어진다.
-Map으로 되어 있다.
-Key는 엔티티의 PK, Value에 엔티티 객체 자체가 저장된다.
영속성 컨텍스트는 엔티티를 식별자 값으로 구분한다. 때문에 영속 상태는 식별자 값이 반드시 있어야 한다.(없을 경우 예외발생)
영속성 컨텍스트의 1차 캐시를 보완하기 위해 제공되는 전역적인 캐시
1차 캐시는 EntityManager 단위에서만 작동하지만 2차 캐시는 애플리케이션 범위에서 공유, 동일한 엔티티를 여러 EntityManager에서 재사용할 수 있도록 한다.
Hibernate에서는 @cache 어노테이션으로 지원한다.
읽기전용, 읽기-쓰기(데이터 변경시 캐시 갱신), 비관적 잠금(데이터 변경시 캐시 무효화, 갱신은 안한다.)
장점: 성능 향상, 트래픽 감소
단점: 데이터가 자주 변하면 일관성 유지 어려움, 캐시가 커지면 메모리 사용량 증가
JpaRepository<Post, Long>를 상속하여 사용하며, 메서드 이름을 통해 간단하게 쿼리를 작성할 수 있다. JPA 구현체인 Hibernate가 자동으로 생성해준다.
객체지향 쿼리 언어로, 엔티티 객체와 매핑된 속성을 기준으로 SQL과 유사한 쿼리를 작성
SQL과 유사하지만 DB테이블이 아니라 엔티티를 대상으로 작업한다.(JPQL은 데이터 베이스 테이블을 전혀 알지 못한다.)
📌 JPA는 JPQL을 분석해서 SQL을 생성할 때 글로벌 페치 전략을 참고하지 않고 오직 JPQL 자체만 사용한다.
@Query("SELECT p FROM Post p WHERE p.title = :title")
List<Post> findPostsByTitle(@Param("title") String title);
타입 세이프한 동적 쿼리를 작성할 수 있는 도구이다. 쿼리를 코드로 작성하며, 컴파일시점에 문법 오류 체크가 가능하다. 복잡한 동적 쿼리를 작성할 때 적합. 단점이 없다.
public List<Post> findPosts(String author, String category) {
QPost post = QPost.post;
BooleanBuilder builder = new BooleanBuilder();
if (author != null) {
builder.and(post.author.eq(author));
}
if (category != null) {
builder.and(post.category.eq(category));
}
return queryFactory
.selectFrom(post)
.where(builder)
.fetch();
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "user") // 연관관계의 주인은 Order의 user 필드
private List<Order> orders = new ArrayList<>();
}
Order)은 하나의 사용자(User)와 연결됨.@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id") // 외래 키 컬럼 지정
private User user;
}
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 때문에 보통 다대다 관계사이에 연결 테이블을 추가하여 이를 해소한다.
슈퍼타입, 서브타입의 관계로 나타낸다.
장점
단점
장점
단점
단순하게 코드의 중복을 없애기 위해서 공통되는 필드값을 따로 빼주어 상속받아 사용하는 전략.
상속관계와다르게 부모테이블을 생성할 수도 없고 부모 엔티티를 직접 조회할 수 없다.
식별관계는 부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 관계이고 비식별관계는 부모 테이블의 기본 키를 자식 테이블의 FK로만 사용하고, 기본 키를 따로 가지는 관계입니다.
식별관계는 복합 키를 위한 식별자 클래스를 만들어야 하므로 비식별관계를 사용하는것이 편리하다. (부모의 PK를 받는 것이 일반적이지만, 단일 키를 별도로 추가하는 방식도 있다.)
복합키의 사용여부에 대해서는 찬반의견이 갈리는 것 같다. 내 생각으로는 적어도 JPA에서는 아래 두가지 이유 때문에 단일 키를 사용하는게 좋은 방법인것 같다.
복합키의 장점으로는 데이터 무결성 보장이라는 아주 큰 장점이 있지만 JPA는 PK + @UniqueConstraint 로 해결이 가능하다.
실제 엔티티를 대신하는 가짜 객체라고 생각하면 된다.
JPA는 엔티티를 조회할 떄 프록시 객체를 생성, 프록시 객체는 실제 엔티티를 대신하여 동작하며, 필요한 시점에 DB에서 데이터를 가져와 채운다.
*** 보통 지연로딩을 위해 프록시 객체를 사용하는 것이 기본설정이며, 즉시 로딩 설정이 되어 있거나 프록시 생성 비활성화하면 프록시 객체를 생성하지 않는다.
*** 프록시 객체가 필요한이유
사용예시) @ManyToOne(fetch = FetchType.LAZY)
준영속 상태의 문제점은 지연로딩이 되지 않는다는 점이다. 때문에 detach()이후 변경사항을 flush를 사용하려고 하면 예외가 발생한다.
엔티티가 처음 조회될 때 즉시 모든 연관된 엔티티를 가져오는 것이 아니라, 실제 해당 데이터가 필요할 때 가져오는 방식
프록시 객체의 활용
JPA는 연관된 엔티티를 즉시 로드하는 것이 아니라 프록시 객체로 감싸서 반환한다. 프록시 객체는 실제 데이터는 없고 특정 필드에 접근할 때 쿼리를 실행하여 데이터를 가져온다.
엔티티를 미리 로딩해두는 방법에도 어디서 로딩하느냐로 3가지로 나뉜다.
OSIV의 핵심은 영속성 컨텍스트를 HTTP 요청의 시작부터 응답 생성 시점까지 유지하는것이 핵심이다.
이 패턴의 주된 목적은 지연로딩 문제를 해결하기 위함이지만 성능 저하를 일으킬 수 있기 때문에
이 패턴은 DB 커넥션을 오래 점유하게 되어, 고성능을 요구하는 애플리케이션에서는 문제가 될 수 있다.
데이터베이스 커넥션을 오래 점유할 수 있으므로, 커넥션 풀 설정에 주의해야 한다.
참고
자바 ORM 표준 JPA 프로그래밍
OSIV 파트: https://f-lab.kr/insight/understanding-osiv-in-spring-boot?gad_source=1&gclid=CjwKCAiAnpy9BhAkEiwA-P8N4oL46EkNfkmTjuDPbg529W9WY1j4ACjH56tqLfbk5McdsKPJDEISYRoC4skQAvD_BwE