ORM, JPA, Hibernate, JPQL, QueryDSL 총 정리

hyelim·2023년 7월 1일
1
post-thumbnail

프로젝트를 진행하다가, 정적쿼리를 문자열로 생성하는 JPQL 대신 DTO를 활용해서 필요한 데이터를 자바 코드로 동적으로 조회하는 QueryDSL이 현재 많이 사용된다고 들었다.

이에 전자의 기법은 런타임 에러가 발생해 프로그램에 치명적인 위험을 가한다고 하는데, 이를 보완하기 위해 나온 QueryDSL의 장점에 대해 알아보자!

먼저 그 이전에 ORM과 이러한 객체 관계 매핑(ORM)을 활용하여 데이터베이스의 데이터를 객체로 조회하고 조작하는 방식을 JPA(Java Persistence API)를 알아보자

ORM 프레임워크란

ORM 프레임워크는 Object-Relational Mapping(객체-관계 매핑)을 지원하는 소프트웨어 도구나 라이브러리

ORM 프레임워크는 객체 지향 프로그래밍 언어(예: 자바)와 관계형 데이터베이스 간의 매핑 작업을 자동화하고, 객체와 테이블 간의 변환을 처리하여 개발자가 별도의 SQL 문을 작성하지 않고도 데이터베이스를 조작할 수 있도록 도와준다

ORM 프레임워크의 기능

  1. 객체와 테이블 매핑

    ORM 프레임워크는 개발자가 정의한 객체와 데이터베이스의 테이블 간의 매핑을 처리한다

    객체와 테이블 간의 필드(속성) 매핑, 상속 구조 매핑, 연관 관계(관계형 데이터베이스의 테이블 간의 관계) 매핑 등을 자동으로 처리함

  2. CRUD 작업 자동화

    ORM 프레임워크는 객체를 데이터베이스에 삽입(Create), 조회(Read), 수정(Update), 삭제(Delete)하는 작업을 자동으로 처리합니다. 개발자는 ORM 프레임워크의 API를 사용하여 간단한 메소드 호출로 데이터베이스 조작을 수행할 수 있다

  3. 데이터베이스 독립성

    ORM 프레임워크는 데이터베이스의 종류나 벤더에 상관없이 동일한 코드로 작업할 수 있도록 데이터베이스 독립성을 제공한다

    ORM 프레임워크는 각 데이터베이스에 맞는 SQL을 자동으로 생성하므로 개발자는 특정 데이터베이스에 종속되지 않고 다양한 데이터베이스를 지원할 수 있다

  4. 성능 최적화

    ORM 프레임워크는 다양한 성능 최적화 기능을 제공하여 데이터베이스 조회 성능을 향상시킬 수 있다

    캐시 기능, 지연 로딩(Lazy Loading), 배치 처리 등을 통해 데이터베이스 액세스를 최적화할 수 있다

이처럼 ORM 프레임워크를 사용하면 개발자는 객체 지향적인 방식으로 데이터베이스를 다룰 수 있으며, SQL 쿼리 작성 및 관리의 번거로움을 줄일 수 있다고 한다

ORM 방식의 데이터베이스 액세스를 위한 접근 방법, JPA

JPA(Java Persistence API)는 ORM(Object-Relational Mapping)을 위한 자바 표준 인터페이스이다.
JPA는 객체와 관계형 데이터베이스 간의 매핑을 처리하고, 데이터베이스에 대한 CRUD(Create, Read, Update, Delete) 작업을 추상화한 API이다

JPA 스펙은 인터페이스로만 정의되어 있으며, 실제로 JPA를 구현한 ORM 프레임워크들이 있다. 이러한 ORM 프레임워크들은 JPA 스펙을 구현하여 개발자가 JPA를 사용할 수 있도록 도와준다 → 그 예시가 Hibernate이다

아래는 Hibernate를 사용하여 JPA를 활용하는 예시이다

JPA를 구현한 ORM 프레임워크 Hibernate를 사용하여 JPA를 활용하는 예시

  1. 의존성 설치

    // Hibernate Core
    implementation 'org.hibernate:hibernate-core:5.5.7.Final'
    
    // JPA API
    implementation 'jakarta.persistence:jakarta.persistence-api:2.2.3'
  1. EntityManagerFactory 생성

    import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
    
    @Configuration
    public class JpaConfig {
        
        @Bean
        public LocalEntityManagerFactoryBean entityManagerFactory() {
            LocalEntityManagerFactoryBean factoryBean = new LocalEntityManagerFactoryBean();
            factoryBean.setPersistenceUnitName("myPersistenceUnit"); // Persistence Unit 이름 설정
            return factoryBean;
        }
        
        // ...
    }

    JPA를 사용하기 위해 EntityManagerFactory를 생성해야 한다.

    Hibernate를 사용하는 경우 LocalEntityManagerFactoryBean을 사용하여 EntityManagerFactory를 생성한다

  2. EntityManagerFactor 사용

    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;
    
    @Repository
    public class UserRepository {
        
        @PersistenceContext
        private EntityManager entityManager;
        
        public User findById(Long id) {
            return entityManager.find(User.class, id);
        }
        
        public void save(User user) {
            entityManager.persist(user);
        }
        
        // ...
    }

    EntityManager는 JPA에서 엔티티를 관리하고 데이터베이스 작업을 수행하는 핵심 인터페이스

    Hibernate는 EntityManager 인터페이스를 구현하고 있으며, 이를 사용하여 데이터베이스 작업을 수행할 수 있다

    Hibernate는 JPA를 준수하면서도 추가적인 기능과 성능 향상을 제공하므로, JPA를 사용할 때 Hibernate는 일반적으로 많이 선택되는 ORM 프레임워크이다

Spring Data JPA

EntityManager를 개발하면서 직접 다룰 일은 흔치 않다.

DB에 접근할 필요가 있는 대부분의 상황에서는 Repository를 정의하여 사용한다.

이 Repository가 Spring Data JPA의 핵심이다

Spring Data JPA는 Spring에서 제공하는 모듈 중 하나로, 개발자가 JPA를 더 쉽고 편하게 사용할 수 있도록 도와준다. 이는 JPA를 한 단계 추상화시킨 Repository라는 인터페이스를 제공함으로써 이루어진다

사용자가 Repository인터페이스에 정해진 규칙대로 메소드를 입력하면, Spring이 알아서 해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.

JPA, Hibernate, Spring Data JPA를 혼동하지 말고 사용하자

JPA의 단점

JPA repository interface 는 Entity를 직접적으로 호출하는 findById()나 save()와 같은 기본적인 인터페이스가 아니면 가독성이 떨어진다.
코드를 작성하는 당사자는 가이드대로 메소드명만 만들면 되니 편리할지 몰라도 추가적인 조건이 둘만 넘어가도 이 쿼리를 호출하는 목적이 직관적으로 파악 되지 않는다.
메소드명을 통한 코드 자동 생성은 분명 좋은 기능이지만, 카멜케이스로만 이루어진 장황한 이름을 보고 있노라면 일반적인 코드보다 가독성이 떨어질 때가 많다

ORM 방식의 단점

ORM 방식의 JPA의 단점을 위에서 서술 했듯이,

ORM은 분명 객체지향을 지키기 위한 획기적인 방식이지만 장점만 존재하지 않는다. ORM 만으로 기존 SQL이 처리한 모든 문제를 해결할 수 없기에 경우에 따라 native query를 쓰지 않을 수 없다

이에 맞게 JPQL, QueryDSL등 다양한 기술이 존재한다.

JPQL, QueryDSL

JPQL 사용성을 높이기 위해 확장한 라이브러리, QueryDSL

QueryDSL는 JPQL 빌더로 동적 쿼리를 메소드로 구조화하여 관리할 수 있도록 돕는 쿼리빌더 라이브러리인데. 자체적인 빌드를 통해 생성한 Qclass 덕분에 IDE 자동완성 기능을 십분 활용 가능하다고 한다.
즉, JPQL 사용성을 높이기 위해 확장한 라이브러리가 QueryDSL 인 것이다

QueryDSL

  1. 정적 타입 지원

    QueryDSL은 자바 코드로 쿼리를 작성하므로 컴파일 시점에서 타입 검사를 받을 수 있다. 이는 타입 안정성을 제공하며, IDE의 지원을 받아 코드 작성 시 타입 체크와 자동 완성 기능을 활용할 수 있습니다.

    이처럼 어플리케이션 로딩 시점에 에러를 감지하지 못하고 런타임 에러가 발생할 시 디버깅 어려움, 실행 시점의 예외 처리, 시스템 안정성 문제, 테스트와 유지보수의 어려움, 성능 저하등의 문제가 있다고 한다

  2. 객체 지향적인 쿼리 작성

    QueryDSL은 자바 코드를 사용하여 객체 지향적인 방식으로 쿼리를 작성할 수 있다.

    엔티티 객체와 그들의 속성을 활용하여 쿼리를 작성하며, 컴파일러가 쿼리 문법과 객체의 매핑 오류를 잡아준다

  3. 동적 쿼리 작성의 용이성

    QueryDSL은 동적인 쿼리 작성에 용이하다.

    조건문, 정렬, 페이징 등 다양한 동적인 요구사항을 자바 코드로 처리할 수 있다. 이를 통해 유연한 쿼리 작성이 가능하며, 복잡한 조건들을 쉽게 추가하거나 제거할 수 있다고 한다.

  4. 타입 안정성과 오류 검출

    QueryDSL은 코드로 쿼리를 작성하므로 오타나 문법 오류 등을 컴파일 시점에서 확인할 수 있다.

    이는 런타임에 발생할 수 있는 오류를 사전에 방지할 수 있다.

  5. 코드 가독성과 유지 보수성

    QueryDSL을 사용하면 쿼리를 자바 코드로 작성하기 때문에 쿼리문을 문자열로 작성하는 것보다 코드 가독성이 좋아진다

    또한, 코드 기반으로 쿼리를 작성하므로 변경이 필요할 때 해당 코드를 직접 수정하여 유지 보수가 용이합니다.

이처럼 QueryDSL은 자바코드로 쿼리를 만들기 때문에 컴파일 시점에서 오류를 잡을 수 있고, 메서드 형식으로 작성하기 때문에 IDE 코드 어시스턴트의 도움도 받을 수 있다. 특히 현업에서 여러 번의 Join과 동적쿼리를 작성할 때, 그 진가를 보여준다고 한다.

결론

위 내용에 근거해 이번 프로젝트에서도 여러 테이블의 조회가 필요하기 때문에 QueryDSL을 쓰는 편이 좋은 것 같다

https://tecoble.techcourse.co.kr/post/2021-08-08-basic-querydsl/
https://dico.me/java/articles/290/ko
https://jojoldu.tistory.com/372

profile
기록용

0개의 댓글