[Spring] Spring Boot + JPA(Hibernate)

김대현·2024년 10월 30일

📚 공부_Spring

목록 보기
7/8

목차
1. JPA 인터페이스란?
2. 영속성 컨텍스트 (Persistence Context)
3. Spring Boot에서 JPA(Hibernate) 인터페이스 사용하기
4. 마무리

1. JPA 인터페이스란?

(출처 : Murphy)

앞서 작성한 [Spring] Spring Boot + MyBatis에서 언급했듯 기존 Legacy JDBC나, Spring Boot에서 제공하는 JdbcDaoSupport은 의존성 관리와 객체 생성이 번거로운 문제가 있었다. 이러한 문제를 해결하기 위해 MyBatis와 JPA 인터페이스를 사용하여 DB에 연결하는 방법이 대안으로 사용되고 있다.

JPA는 Java Persistence API의 약어로, 자바 객체와 데이터베이스 간의 매핑을 위한 API 집합이다. 즉, JPA는 실행 가능한 모듈이 아닌 ORM(Object Relational Mapping) 기술 표준으로서의 Java Interface 모음이다.

JPA 2.1 표준 명세를 구현한 3가지 구현체로는 Hibernate, EclipseLink, DataNucleus 가 있으며 이 중 Hibernate가 가장 대중적이다.

JPA는 MyBatis와 비교해 배우기 어려운 점이 있지만 ORM으로서 자동으로 객체 매핑을 해주고, dialect라는 강력한 기능을 통해 여러 RDBMS의 언어에 맞게 자동으로 Query를 변형시켜주며 JPA에서 제공하는 method 뿐만 아니라 JPQL이나 권장하지 않지만 Native SQL을 작성함으로 디테일하게 데이터에 접근할 수 있다.

또한 한국을 포함한 아시아권에서는 MyBatis를 선호하는 경향이 있으나 전세계적으로는 JPA의 선호도가 더 높은 편이다. 아래의 자료가 위의 사실을 반증할 객관적인 자료는 아니지만 Google Trends에서 근 1년간 전세계의 JPA, MyBatis의 검색트랜드를 검색해보면 JPA의 검색량이 앞도적으로 높다.
(MyBatis에 비해 난이도가 높은것도 한 몫 했을것 같다)

Google Trends에서 검색해본 2024년 JPA와 MyBatis 검색량

💡 다시 공부해보는 ORM 개념

ORM (Object Relational Mapping)

  • ORM은 객체(Object)와 관계형 데이터베이스 (RDBMS)를 매핑하여 데이터베이스 테이블을 객체지향적으로 사용하기 위한 기술이다. SQL의 작성없이 매핑하는 설정만으로 DB 테이블 내의 데이터를 객체로 전달 받을 수 있다.
  • 예) JPA(인터페이스, 표준명세서), Hibernate, Entity Framework, Django ORM 등...

2. 영속성 컨텍스트 (Persistence Context)

영속성 컨텍스트란 JPA에서 애플리케이션과 데이터베이스 사이에서 객체의 상태를 관리하고 데이터베이스와의 상호작용을 최적화하는 메모리 영역을 말한다. 영속성 컨텍스트는 엔티티를 관리하기 위한 1차 캐시(1st Level Cache)와 쓰기 지연(Write-Behind) 메커니즘을 포함하고 있다.

즉, JPA를 이해하기 위해서는 영속성 컨텍스트를 이해해야 하고, 영속성 컨텍스트를 이해하기 위해서는 엔티티, 1차 캐시, 쓰기 지연 개념을 이해해야 한다.

✅ 1차 캐시

Entity의 관리 구조 (출처 : 채마스의 개발창고)

1차 캐시를 이야기하기 전에, JPA가 데이터베이스(DB)로 전송하는 엔티티(Entity)를 어떻게 관리하는지에 대해 이야기하고 싶다.

❗JPA에서의 엔티티(Entity) 관리

엔티티는 '현실 세계의 객체, 어떠한 대상, 개체'를 의미하며, 실제로 독립체라는 뜻을 가지고 있으며 이 엔티티를 DB에 Java JDBC를 통해 전달하게 되면, 해당 데이터가 저장되거나, 이를 기반으로 수정, 삭제, 출력이 가능하게 된다.

JPA에서는 이 엔티티를 EntityManager 인터페이스로 관리하게 되는데, EntityManager를 통해 엔티티의 생명주기가 관리된다.

EntityManger 인터페이스로 관리되는 Entity 생명주기 (출처 : cham.log)

❗엔티티의 생명주기 4가지

생명주기설명
비영속(new/transient) 영속성 컨텍스트와 전혀 관계가 없는 객체를 생성만 한 상태
영속(managed) 영속성 컨텍스트에 저장이 된 상태
해당 엔티티가 영속성 컨텍스트에 의해 관리되는 상태
준영속(detached) 영속성 컨텍스트에 저장되었다가 분리된 상태
영속성 컨텍스트에서 지운 상태
삭제(removed) 실제 DB 삭제를 요청한 상태

또한 EntityManager는 EntityManagerFactory 인터페이스를 통해 생성되며, 이때 EntityManagerFactory 인터페이스는 팩토리 패턴(Factory Pattern)이기에 하나의 EntityManagerFactory 객체만 생성된다. 그리고 생성된 EntityManagerFactory 객체를 통해 여러 개의 EntityManager 객체가 생성할 수 있다.

❗1차 캐시의 구조와 동작원리

1차 캐시의 구조와 동작원리 (출처 : cham.log)

1차 캐시는 영속성 컨텍스트 내부에서 엔티티(Entity)를 보관하는 저장소이다. 1차 캐시는 엔티티의 정보를 키(Key)와 값(Value)으로 가지고 있다. (키: 엔티티의 @Id 어노테이션을 작성한 멤버 → 일반적으로 테이블의 PK, 값: 엔티티)

JPA를 통해 쿼리를 실행하게 되면 바로 DB에 접근하는 것이 아니라 1차 캐시에 접근하게 되는데, 이때 1차 캐시에 해당 쿼리의 결과가 있을 경우 해당 결과에 해당하는 객체를 반환한다. 그러나 쿼리 결과가 1차 캐시에 없을 경우, JPA는 Dialect를 통해 RDBMS에 맞는 쿼리문으로 변경한 후 DB에서 실행한다. 그리고 반환된 데이터를 1차 캐시에 먼저 적재한 후 해당 데이터를 가져온다.

✅ 쓰기 지연 SQL 저장소

쓰기 지연 SQL 저장소가 없다면 매번 요청 시마다 DB에 직접 연결하고 해제하는 과정이 필요할 것이다. 이 과정은 시간적으로 매우 비효율적이며, 그렇기에 JPA는 영속성 컨텍스트가 엔티티의 상태 변화를 추적하고, flush() 명령이 호출되면 변경된 내용을 데이터베이스에 반영하는 쓰기 지연 SQL 저장소를 포함하고 있다.

쓰기 지연 SQL 저장소에 SQL 쿼리가 저장되는 시점은 1차 캐시에 엔티티가 저장되거나 상태가 변경될 때이며, 그때 해당 엔티티를 분석하여 설정한 Dialect에 맞게 SQL 쿼리가 생성되어 쓰기 지연 SQL 저장소에 저장된다.

이후 쓰기 지연 SQL 저장소에 저장된 쿼리들은 Transaction.commit() 또는 entityManager.flush()가 실행되거나 JPQL 쿼리를 실행할 때 영속성 컨텍스트의 상태에 따라 Flush가 발생하여 해당 쿼리를 데이터베이스에 보내준다.

3. Spring Boot에서 JPA(Hibernate) 인터페이스 사용하기

1) application.properties 설정

#mariadb server connect

spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://127.0.0.1:포트번호/테이블명
spring.datasource.username=아이디
spring.datasource.password=패스워드

# jpa

#hibernate를 통해 실제 실행되는 쿼리를 표시할
spring.jpa.properties.hibernate.show_sql=true
#hibernate를 통해 표시되는 쿼리를 정렬함
spring.jpa.properties.hibernate.format_sql=true
#hibernate를 통해 표시되는 쿼리 내부에 주석을 추가함
spring.jpa.properties.hibernate.use_sql_comments=true
#hibernate 를통해 SQL을 디버깅 할 수 있음
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql=trace
#JPA Dialect 설정
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

2) Repository Interfcae 설정

package pack.model;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface JikwonRepository extends JpaRepository<Jikwon, Integer>{

영속성 컨텍스트에서 사용될 엔티티는 Repository 인터페이스를 통해 영속성 컨텍스트 내부에 저장할 수 있으며, 구현된 JpaRepository 인터페이스의 추상 메소드를 사용하거나 JPQL을 통해 엔티티를 저장할 수 있다.

❓JpaRepository는 뭐야?

JpaRepository Interface의 상속구조

우리가 사용하는 JpaRepository는 다양한 인터페이스를 상속받아 여러 기능을 구현할 수 있는 추상 메소드를 포함하고 있다. Spring Boot는 이러한 추상 메소드를 미리 구현해 놓았다. JpaRepository에 들어가는 generic type은 첫 번째로 엔티티 클래스의 타입, 두 번째로 해당 엔티티의 Primary Key 타입이다. 예를 들어, JpaRepository<BoardEntity, Integer>와 같이 사용할 수 있다.

CrudRepository에는 데이터베이스의 CRUD 작업과 관련된 여러 메소드가 포함되어 있으며, 대표적으로 입력 및 수정에는 save(), 검색에는 findById(), findAll(), 삭제에는 deleteById() 또는 delete() 메소드가 있다.
Interface CrudRepository<T,ID> - Spring API

PagingAndSortingRepository에는 페이징과 정렬과 관련된 기능의 메소드가 있는데 해당 메소드는 추후 다른 글로 자세히 기록하겠다.
Interface PagingAndSortingRepository<T,ID> - Spring API

❓JPQL는 뭐야?

JPQL은 Java Persistence Query Language의 약어로, 엔티티 객체를 대상으로 하는 쿼리 언어이다. JPQL은 SQL과 유사한 문법을 사용하지만, 데이터베이스 테이블 대신 엔티티와 속성을 대상으로 하며, 객체 지향 프로그래밍과 잘 통합된다.

JPQL이 실행될 때 1차 캐시가 아닌 데이터베이스에 직접 접근하여 실행되고, 조회된 결과는 영속성 컨텍스트에 저장된다. 이때 영속성 컨텍스트에 동일한 식별자가 있을 경우, 데이터베이스에서 조회한 데이터를 버리고 영속성 컨텍스트의 데이터를 사용한다.

변경이 발생하는 INSERT, DELETE, UPDATE 쿼리의 경우, JPA가 자동으로 1차 캐시를 비우지 않기 때문에, @Modifying 어노테이션을 추가해야만 1차 캐시를 비워줌으로써 변경된 데이터를 가져올 수 있게 된다.

Repository Interface에서 @Query어노테이션을 통해 아래와 같이 입력 가능하다.


import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface GogekRepository extends JpaRepository<Gogek, Integer>{

	@Query("SELECT j FROM Gogek AS g JOIN g.jikwon AS j")
	public List<Jikwon> selectJikwonWithGogek();

JPQL은 Native SQL Query과 유사하지만, 실제로 DB에서 사용되는 쿼리는 아니다. (@Query 어노테이션의 nativeQuery=true 속성을 통해 Native Query로 사용할 수 있지만, 이는 특정 데이터베이스에 종속적이기 때문에 이식성이 떨어지며, JPA의 강점을 잃어버리므로 권장하지 않는다.)

4. 마무리

  • JPA는 정말 끝이 없는 주제인 것 같다. JPA의 개론 정리만 벌써 3일째 하고 있는데, 아직도 다 작성하지 못한것 같다.
  • 그러나 이 과정을 통해 처음 배웠을 때 이해가 되지 않았던 부분들이 많이 해결되었다.
  • 깊이 있는 탐구를 통해 이해하지 못했던 것이 이해되는 순간은 정말 즐겁다.
profile
안녕하세요. 날마다 성장하는 김대현입니다 :)

0개의 댓글