JPA(Java Persistence API) 란 무엇인가?
JPA는 JAVA 진영에서 ORM(Object-Relational Mapping) 기술의 표준으로 사용되는 인터페이스의 모음이다. JPA를 구현한 대표적인 오픈소스로는 Hibernate가 있다.
- ORM 기술에 대한 자바 진영의 API 표준 명세
- JPA의 구현체는 Hibernate, OpenJPA 와 같은 프레임워크가 존재함
- ORM(Object-Relational Mapping)은 객체 관계 맵핑 기술
장점
- SQL 이 아닌 Method로 DB를 조작할 수 있어, 개발자가 비지니스 로직을 구성하는데만 집중할 수 있다
- 재사용 및 유지보수의 편리성이 증가한다
- DBMS에 대한 종속성이 줄어든다
단점
- JPA를 제대로 사용하기까지 걸리는 시간이 오래걸린다
- 프로젝트 규모가 크고 복잡할 때 설계가 잘못된다면 속도저하 및 일관성(Consistency)이 무너진다
- 온전한 ORM 만으로 프로젝트를 완성할 수 없다
영속성 컨텍스트
- JPA 객체(Entity)의 생애주기를 관리하는 환경
- 영속성 컨텍스트에 등록된 객체는 Database에 그 상태가 영속적으로 저장.
- 영속성이란 영속성 컨텍스트에 Entity가 저장되고 있는 상태를 의미한다.
- EntityManager를 통해 entity의 영속성이 관리된다
엔티티의 생명주기
- 영속 : 영속성 컨텍스트에 의해 관리되는 상태
- 비영속 : 영속성 컨텍스트에 의해 관리되지 않는 상태(한번도 관리된적 없는 상태)
- 준영속 : 관리되었다 분리된 상태(Detached)
- 삭제 : 영속성 컨텍스트에서 삭제되고 DB에서도 삭제된 상태
영속성 컨텍스트의 장점
1. 1차 캐시
EntityManager에서 관리하는 cache table. 영속상태의 엔티티가 저장된다.
2. 동일성 보장
영속성 컨텍스트는 엔티티의 동일성을 보장한다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.print(a==b) // true
3. 변경 감지 (Dirty checking)
entity의 수정이 발생했을 때 따로 commit 하지 않아도, 영속성 컨택스트에서 변경 사항이 관리되고 DB에 반영된다.
※ 변경 감지의 흐름
- 트랙잭션을 커밋하면 entityManager의 내부에서 먼저 플러시가 호출된다.
- 엔티티와 스냅샷을 비교하여 변경된 엔티티를 찾는다.(checking)
- 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 저장한다.
- 쓰기 지연 저장소의 SQL을 플러시한다.
- 데이터베이스 트랜잭션을 커밋한다.
4. 쓰기 지연 SQL 저장소
SQL을 DB에 바로 반영하지 않고 메모리에 적재 한 뒤 나중에 flush() 호출 시 DB에 반영하기 위해 사용하는 저장소. DB에 트랜잭션을 한번씩 날리는것보다 한번에 모아서 날리는게 효율적이기 때문
5. 지연로딩(LAZY Loading)
두 엔티티가(A,B) 연관관계로 매핑이 되어있을 시 하나의 엔티티(A)를 조회하더라도 연관관계에 있는 엔티티(B)값에 접근하기 전까지 다른 엔티티를 위해 쿼리를 날리지 않는 기능
※ 플러시(flush)
영속성 컨텍스트의 쓰기 지연 SQL 저장소의 내용을 DB에 동기화하는 기능
호출 방법
1. em.flush() 직접 호출.
2. 트랜잭션 커밋시 플러시가 자동 호출.(commit : flush → commit!)
3. JPQL (Java Persistence Query Langauge) 쿼리 실행시 플러시가 자동 호출.
JPA N+1 문제
JPA를 사용할 경우 1개의 쿼리에 대해 설계 했으나 추가적으로 N 개의 쿼리가 더 발생해 총 N+1개의 쿼리를 날리게 되는 현상 의도치 않은 추가적인 쿼리에 의해 비지니스 로직이 긴 실행시간을 가지게됨
Why?
JPQL에서 메서드를 호출했을 때 연관관계를 무시하고 해당 테이블에 대한 조회를 하기 때문, 따라서 연관관계 엔티티를 조회하는 JPQL을 하나하나 생성해서 조회하기 때문이다.
- FetchType.LAZY : @OneToMany(fetch = FetchType.LAZY)
연관 관계로 지정된 엔티티를 프록시 객체 형태로 가져온 후 실제 사용할 때 해당 엔티티를 나중에 N 번 호출함
- FetchType.EAGER :@OneToMany(fetch = FetchType.EAGER)
연관 관계로 지정된 Entity를 조회할 경우 바로 추가적인 쿼리를 생성하여 조회(N번)
해결책
1. Fetch join 문을 사용해서 연관관계에 있는 Entity도 한번에 프록시 객체로 영속화하여 조회
@Query("SELECT p FROM Profile p join fetch p.user")
findAll()
public interface CommentRepository extends JpaRepository<Comment, Long> {
@Query("select c from Comment c join fetch c.board")
List<Comment> findAll();
}
2. @EntityGraph를 사용해서 연관관계에 있는 엔티티를 선언하여 한번에 조회
@Entity
@NamedEntityGraph(name = "graph.Department.employees",
attributeNodes = @NamedAttributeNode("employees"))
public class Department {
// department fields and methods
@OneToMany(fetch = FetchType.LAZY)
private Set<Employee> employees;
}
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
@EntityGraph(value = "graph.Department.employees")
Department findById(Long id);
}
[참고]
https://dev-troh.tistory.com/150
https://junhyunny.github.io/spring-boot/jpa/java-persistence-api/
'Computer Science > Database' 카테고리의 다른 글
SQL syntax의 정리 - SELECT문 (0) | 2022.12.27 |
---|---|
[DB] 정규화(Normalization)에 대해서 (0) | 2022.12.20 |