JPA의 기본 동작 요약

출처: https://myhyem.tistory.com/24

EntityManager

  • JPA에서 엔터티를 저장, 조회, 수정, 삭제하는 등의 데이터베이스와의 상호작용을 관리하는 인터페이스입니다.
  • EntityManager를 통해 JPQL을 사용하여 쿼리를 실행하고, 엔터티 간의 관계에서 지연 로딩과 즉시 로딩을 관리합니다.
  • 캐싱 기능을 통해 쿼리 및 엔터티 결과를 캐싱하고, flush 메서드를 사용하여 영속성 컨텍스트의 변경을 데이터베이스에 동기화할 수 있습니다.
  • 엔터티의 생명주기를 관리합니다. 엔터티의 영속화, 준영속화, 삭제 등의 상태 변화를 추적하고 업데이트합니다.
  • EntityManagerFactory를 사용하여 EntityManager를 생성하며, EntityManagerFactory 애플리케이션의 생명주기 동안 공유되어야 합니다.

EntityManagerFactory  

  • EntityManagerFactory는 JPA에서 EntityManager를 생성하기 위한 인터페이스입니다.
  • 일반적으로 애플리케이션이 시작될 때 EntityManagerFactory를 생성하고, 애플리케이션이 종료될 때 닫습니다.
  • 데이터베이스 연결을 설정하고, 엔터티 매핑 정보를 로드하며, EntityManager을 생성하며, 다수의 데이터베이스와의 연결 및 다양한 엔터티 매핑 설정을 포함할 수 있습니다.
  • 애플리케이션 전체에서 공유되므로, 모든 엔터티 매니저가 이를 통해 생성되고 관리됩니다.

자세한 내용은 JPA EntityManager의 Thread-safe 이해하기 을 참고해주세요.


Spring boot JPA

출처 : https://velog.io/@adam2/JPA%EB%8A%94-%EB%8F%84%EB%8D%B0%EC%B2%B4-%EB%AD%98%EA%B9%8C-orm-%EC%98%81%EC%86%8D%EC%84%B1-hibernate-spring-data-jpa

Spring Data JPA가 제공하는 JpaRepository 인터페이스를 활용하면 Spring Data JPA의 강력한 기능을 활용할 수 있습니다. JpaRepository에 정의된 메소드 네이밍 규칙을 따르면, 쿼리를 직접 작성하지 않고도 해당 메소드에 대한 쿼리가 자동으로 생성됩니다. 

 

JpaRepository 

  • Spring Data JPA 프레임워크에서 제공하는 인터페이스
  • CRUD(Create, Read, Update, Delete) 기능을 제공하며, 개발자가 간단한 메소드 선언만으로도 데이터베이스 작업을 수행   
  • 데이터를 페이지로 나누거나 정렬하여 반환할 수 있는 기능을 제공(Paging, 정렬 기능 제공)

쿼리 생성 과정 및 명명 규칙

  1. 메소드 이름 분석
    • JpaRepository에서 제공하는 메소드 명명 규칙에 따라 메소드의 이름을 분석합니다.
    • 메소드 이름은 해당 동작을 나타내는 동사와 조회 대상을 지정하는 명사 또는 속성 이름
    • 메소드 이름을 분석하여 쿼리의 조건절을 결정하고, 필요에 따라 정렬(order by)이나 제한(limit) 조건을 추가합니다.
  2. 메소드 이름 해석
    • 메소드 이름을 해석하여 쿼리의 조회 대상이 되는 엔터티 클래스와 해당 속성을 식별합니다.
    • JPQL 쿼리의 FROM 절과 WHERE 절을 생성하는 데 필요한 정보를 얻습니다.
    • 예를 들어, 메소드 이름에 "findById"가 포함되어 있다면 해당 ID를 가진 엔터티를 조회하는 동작을 수행합니다.
  3. 쿼리 생성
    • 메소드 이름을 해석하여 적절한 JPQL(Querydsl) 또는 네이티브 SQL 쿼리를 생성합니다.
    • 생성된 JPQL 쿼리는 조회 대상 엔터티와 해당 속성을 기반으로 WHERE 절에 적절한 조건을 추가합니다.
    • 메소드의 반환 타입과 매개변수 등을 고려하여 적절한 쿼리를 생성합니다.
  4. 실행
    • 생성된 JPQL 쿼리는 Spring Data JPA에 의해 데이터베이스로 전송되어 실행됩니다.
    • 데이터베이스는 JPQL 쿼리를 해석하여 해당하는 엔터티를 조회하고, 결과를 반환합니다.

위와 같은 순서를 바탕으로 쿼리를 생성 및 실행하게 됩니다.

  • ex) boolean existsByUserId(String userId);
    •  "existsByUserId"는 특정 조건을 만족하는 엔터티가 존재하는지 확인하는 동작을 수행합니다. 해당하는 "userId" 속성을 가진 엔터티가 존재하는지 여부를 확인합니다.

JpaRepository 인터페이스를 통해 개발자는 자동으로 생성되는 쿼리 뿐만 아니라 직접 쿼리를 작성하여 사용할 수 있습니다. 이때는 @Query 어노테이션을 사용하여 직접 SQL 쿼리를 작성(JPQL)할 수 있습니다. 이를 통해 개발자는 자유롭게 원하는 쿼리를 작성하고 실행할 수 있습니다.

 

JPQL (Java Persistence Query Language)

  • JPA에서 사용되는 객체 지향 쿼리 언어
  • 데이터베이스 테이블이 아닌 엔티티 객체에 대한 쿼리로 작성
  • JPA 구현체(예: Hibernate, EclipseLink)가 데이터베이스 쿼리로 변환하고 실행

ex) 특정 가격 이상의 상품 조회

//Java
@Query("SELECT p FROM Product p WHERE p.price >= :price")
List<Product> findProductsAbovePrice(@Param("price") double price);


@Query 어노테이션을 사용할 때는 데이터베이스의 컬럼명이 아닌 해당 엔터티 클래스에서 선언한 필드명을 사용해야 합니다.


지연 로딩 (Lazy Loading)

  • 연관된 엔터티를 실제로 사용할 때까지 데이터베이스에서 로딩을 지연시키는 전략
  • 특징
    • 연관된 엔터티는 프록시 객체로 대체되어 실제 사용 시에 데이터베이스에서 로딩이 발생함
    • 최초 쿼리 실행 시 연관된 엔터티의 데이터를 로드하지 않고, 프록시 객체만을 가져옴
  • 장점
    • 최초 쿼리의 성능이 향상됨
    • 실제로 필요한 시점에 데이터를 로딩하므로 효율적인 리소스 관리 가능
  • 단점
    • 연관된 엔터티를 사용할 때마다 추가적인 데이터베이스 쿼리가 발생할 수 있음
    • 프록시 객체를 다루는 부가적인 복잡성이 발생할 수 있음
  • ex) 게시글(Post)과 댓글(Comment)이 일대다(One-to-Many) 관계일 때
//Java
@Entity
public class Post {
    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
    private List<Comment> comments;
}

@Entity
public class Comment {
    @ManyToOne
    @JoinColumn(name = "post_id")
    private Post post;
}
  • fetch = FetchType.LAZY는 댓글 리스트가 실제로 사용될 때까지 로딩을 지연시키는 설정
  • 과정
    1. 어플리케이션이 실행되면서 Post 엔터티를 조회하는 쿼리가 실행됩니다.
    2. 이때, Post 엔터티의 comments 필드는 fetch = FetchType.LAZY 설정으로 인해 초기에는 댓글 데이터를 로딩하지 않습니다.
    3. 어플리케이션이 댓글 데이터가 필요한 시점에 comments 필드에 접근하는 순간, JPA는 추가로 댓글 데이터를 가져오기 위한 쿼리를 실행합니다.
  • 이로써 댓글 데이터는 필요한 시점에 로딩되고, 불필요한 데이터를 미리 로딩하지 않아 성능이 향상됩니다.

즉시 로딩 (Eager Loading)

  • 연관된 엔터티를 즉시 로딩하여, 최초 쿼리 실행 시에 모든 데이터를 함께 가져오는 전략
  • 특징
    • 최초 쿼리 실행 시에 연관된 엔터티의 데이터까지 모두 로딩됨
    • 데이터베이스에서 조인을 사용하여 관련된 엔터티를 모두 가져옴
  • 장점
    • 사용 시 추가적인 데이터베이스 쿼리가 발생하지 않아 성능 향상 가능
    • 사용할 때마다 로딩이 필요 없어 프록시 객체를 다룰 필요가 없음
  • 단점
    • 최초 쿼리에서 불필요한 데이터를 로딩할 수 있어 성능 저하 가능
    • 모든 연관된 엔터티를 항상 로딩하므로, 메모리 소모가 크고 효율이 감소할 수 있음
  • ex) 게시글(Post)과 댓글(Comment)이 일대다(One-to-Many) 관계일 때
//Java
@Entity
public class Post {
    @OneToMany(mappedBy = "post", fetch = FetchType.EAGER)
    private List<Comment> comments;
}

@Entity
public class Comment {
    @ManyToOne
    @JoinColumn(name = "post_id")
    private Post post;
}
  • fetch = FetchType.EAGER는 댓글 리스트를 게시글과 함께 즉시 로딩하도록 설정
  • 최초 쿼리 실행 시 게시글과 연관된 모든 댓글을 함께 가져옵니다.
  • 과정
    1. 어플리케이션이 실행되면서 Post 엔터티를 조회하는 쿼리가 실행됩니다.
    2. 이때, Post 엔터티의 comments 필드는 fetch = FetchType.EAGER 설정으로 인해 초기에는 댓글 데이터도 함께 로딩됩니다.
  • 어플리케이션이 Post 엔터티를 조회하는 시점에 댓글 데이터도 함께 가져오기 때문에, 추가로 댓글 데이터를 가져오는 쿼리가 필요하지 않습니다.
  • 다만 즉시 로딩은 관계된 엔터티를 한 번에 로딩하기 때문에 성능 저하의 가능성이 있습니다.

출처 : https://myhyem.tistory.com/24

영속성 컨텍스트(Persistence Context)

  • EntityManger로 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리
  • 엔티티를 데이터베이스에 저장(persist), 업데이트(merge), 삭제(remove)할 수 있습니다.
  • 영속성 컨텍스트는 관리하는 엔티티의 변화를 추적하고, 한 트랜잭션 내에서 변화가 일어나면 엔티티에 마킹
  • 트랜잭션이 끝나는 시점에 마킹한 것을 DB에 반영됩니다.
  • 쓰기 지연을 함으로써 영속성 컨텍스트가 중간에서 쿼리들을 모았다가 한 번에 반영해 주니 DB와 네트워크 통신의 리소스 감소가 있습니다.

ex) 쓰기 지연

//Java
//JPA EntityManager를 사용하여 사용자를 로드합니다.
EntityManager entityManager = getEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();

User user = entityManager.find(User.class, 1L);

//사용자의 필드를 변경합니다.
user.setUsername("newUsername");
user.setEmail("newEmail@example.com");

//트랜잭션을 커밋하면 쓰기 지연이 반영됩니다.
transaction.commit();

출처 : https://daram.tistory.com/265

쓰기 지연 과정

  1. memberA의 insert 쿼리를 임시 저장소에 저장(DB에는 insert하지 않음)
  2. memberA 1차 캐시에 저장
  3. memberB의 insert 쿼리를 임시 저장소에 저장(DB에는 insert하지 않음)
  4. memberB 1차 캐시에 저장
  5. commit시 DB에 memberA, memberB insert
  6. 데이터베이스 트랜잭션을 커밋

1차 캐시(First-Level Cache)

  • EntityManager가 관리하는 영속성 컨텍스트 내부에 존재하며 메모리 내의 첫 번째 캐시
  • 엔티티 객체의 상태를 저장하고 쿼리 결과를 캐시
  • 데이터베이스 접근 횟수를 줄이면 애플리케이션 성능을 획기적으로 개선할 수 있습니다.
  • 이 캐시를 통해 같은 엔티티에 대한 반복적인 데이터베이스 조회를 피하고, 성능을 향상할 수 있습니다.
  • 트랜잭션을 시작하고 종료할 때까지 1차 캐시가 유효합니다.

출처 :https://junghyungil.tistory.com/203

 

  • 동작 원리
    1. 조회 시 1차 캐시에 데이터가 이미 있는지 확인하고, 데이터가 있으면 가져온다. (비교는 PK로 한다.)
    2. 1차 캐시에 데이터가 없다면, 데이터베이스에 데이터를 요청한다.
    3. 데이터베이스에서 받은 데이터를 다음에 재사용할 수 있도록 1차 캐시에 저장한다.

2차 캐시 (Second-Level Cache)

  • 여러 영속성 컨텍스트 간에 공유되는 캐시로, 애플리케이션 전반에서 엔티티를 공유하고 재사용
    • *참조: 영속성 컨텍스트 개수는 Entity 개수에 따라 할당됨
  • 데이터베이스 쿼리 결과나 엔티티를 메모리에 캐시 하여 데이터베이스 조회 비용을 줄이고 성능을 향상
  • 2차 캐시를 사용할 때 캐시 일관성과 동기화에 주의
  • 여러 트랜잭션 간에 엔티티를 공유하고 데이터베이스 조회를 줄이는 데 사용

출처 :https://junghyungil.tistory.com/203

2차 캐시는 언제 사용하는지?

  • 다수의 엔티티 매니저 사용 시: 여러 개의 엔티티 매니저가 동일한 데이터에 접근할 때, 2차 캐시를 사용하여 성능을 향상시킬 수 있습니다.
  • 큰 규모의 읽기 작업이 필요한 경우: 읽기 작업이 많은 환경에서 2차 캐시를 사용하면 데이터베이스 부하를 줄일 수 있습니다.
  • 데이터의 변경이 적은 경우: 데이터가 자주 변경되지 않고 읽기 중심의 환경에서 2차 캐시를 사용하면 성능 향상을 기대할 수 있습니다.

반대로 2차 캐시를 사용하지 말아야 하는 경우

  • 데이터 변경이 자주 발생하는 경우: 데이터가 자주 변경되는 환경에서는 2차 캐시를 사용하면 캐시의 일관성 유지가 어려울 수 있습니다.
  • 엔티티 간 연관관계가 복잡한 경우: 연관된 엔티티들 간의 복잡한 관계가 있을 때 2차 캐시를 사용하면 캐시의 관리가 어려울 수 있습니다.

결과

  • JPA를 도입하여 쿼리의 작성이 없어 빠른 개발이 가능하였습니다. 그러나 MyBatis를 사용했을 때는 매번 SQL을 작성하는 번거로움이 있었습니다. 이렇게 쿼리 작성의 부담이 감소함으로써 개발자는 프로젝트에 집중할 수 있게 되었습니다.
  • CRUD(Create, Read, Update, Delete) 작업을 자동화하여 개발 생산성을 크게 향상시켰으며 매서드 규칙에 맞추어 새로운 기능을 빠르게 개발할 수 있었습니다.
  • JPA을 도입함으로써 Auction 서버 프로젝트는 다양한 데이터베이스와의 호환성을 제공할 수 있으며, 확장성과 유연성을 높일 수 있게 되었습니다.
  • 또한, JPA를 사용하면 객체 간의 관계를 쉽게 관리하고 데이터베이스와의 불일치를 해결할 수 있습니다.

출처

'JAVA & Spring > Spring' 카테고리의 다른 글

JPA EntityManager의 Thread-safe 이해하기  (0) 2024.06.02
JPA 구성요소와 기본 이해  (0) 2023.09.18
[Spring]DI(의존성 주입)  (0) 2022.04.14

+ Recent posts