♻️ JPA & DB 성능 최적화

 

 

1. 기존 구조: MyBatis로 직접 SQL Mapper 작성

 

초기 프로젝트에서는 직접 SQL을 작성하고 이를 DAO로 매핑하는 방식으로 데이터를 처리했다.(MyBatis 사용)

이 파트가 리팩토링하면서 가장 먼저 바꾼 부분 중 하나여서 첫 게시물감으로 골라 보았다.
이유는 명확했다. 쿼리 제어가 세밀해서 익숙하고 안정적이긴 했지만, 기존 SQL 매퍼 방식에는 몇 가지 한계가 있었기 때문이었다.

  • 도메인 객체와 SQL이 강하게 결합 → 비즈니스 로직과 혼재
  • 쿼리 수정 시 객체-쿼리 간 동기화 필요
  • 복잡도 증가  테스트/유지보수 어려움

물론 이런 매퍼 방식에도 세밀한 통제가 가능하며 동적 쿼리를 쓸 수 있다는 장점이 있었고, 덕분에 MyBatis를 쓰며 SQL문 작성에 익숙해져서 SQLD 시험도 수월하게 치를 정도는 되었다. 그래도 이번에 리팩토링할 프로젝트에는 MyBatis로 다룰만큼의 복잡한 통계 쿼리나 동적 쿼리가 따로 존재하지 않았기에 최종적으로 JPA로의 마이그레이션을 선택했다.

 

2. JPA란 무엇인가?

 

JPAJava 진영의 ORM 기술을 표준화한 인터페이스(API)이다.

자바 객체를 DB에 CRUD할 수 있도록 도와주는 표준 ORM 프레임워크로, JPA 자체는 인터페이스이기 때문에 구현체로는 주로 Hibernate를 많이 사용한다. 나도 그랬고.

그럼 여기서 ORM은 무엇이냐? 하면, ORM은 Object Relational Mapping, 즉 객체(Object)와 관계형 데이터베이스(Relational Database)를 자동으로 매핑(Mapping)해주는 기술이다.

SQL을 일일이 쓰지 않아도 객체 중심으로 DB를 조작할 수 있게 하는 놀라운 도구이며, 처음 이걸 접했을 땐 이렇게 전체 줄에 형광펜칠을 할만큼 놀랐다.

그런 도구가 있는데 내가 한땀한땀 SQL문을 500줄씩 작성하고 있었다고...?

User user = new User("Min", 25);
entityManager.persist(user);
// entityManager.persist() = 「INSERT INTO user~」 SQL문이 실행되는 것과 같은 효과!

 

위 예시처럼 JPA를 쓰면 Java 코드만으로 DB를 조작할 수 있다. 자바 객체와 DB 테이블 간의 패러다임 불일치를 알아서 해결해주니, 효자 중의 효자가 따로 없다.

간단한 어노테이션만으로 기본 키를 설정하거나 자동 생성해 주고(@Id, @GeneratedValue),

테이블 간 관계 매핑을 하거나(@OneToMany, @ManyToOne) 더티 체킹을 사용해 변경을 감지하고 자동으로 Update문을 실행하는 등 JPA를 사용하면 온갖 DB CRUD 관련 잡일이 혁신적으로 줄어든다.

심지어, 원할 때는 SQL처럼 JPQL이라는 쿼리 언어를 사용해서 직접 쿼리를 날릴 수도 있음!

 

3. 리팩토링 배경: JPA 기반 구조로 전환

표 출처: LG유플러스 기술블로그 진보은님 「JPA 경험기」 (https://techblog.uplus.co.kr/jpa-%EA%B2%BD%ED%97%98%EA%B8%B0-6e50497f56fd)

 

이런 성능 좋은 도구가 존재한다는 걸 알았는데 쓰지 않고 배길까?

프로젝트 전반적인 리팩토링을 위해 STS(Spring Tool Suite)에서 IntelliJ로 프로젝트를 옮겨가면서, 초기 설정 시 큼직한 프로젝트 구성요소를 많이 변경했다. (JSP Thymeleaf, 로컬DB AWS 등)

JPA도 이때 적용해서 테스트코드 작성 때까지 내내 잘 써먹었다. ORM 개념을 알아두니 추후 Mysoly에서 인턴십을 진행하며 Sequelize의 개념을 쉽게 이해할 수 있어 도움이 많이 되었다.

리팩토링 시, JPA를 적용하며 내가 얻을 수 있을 거라 예상했던 장점들은 아래와 같았다.

  • Java 진영의 공식 ORM 표준 기술 스택 변경 또는 확장 시 이식성 좋음
  • XML 관리 필요 X, 테스트코드 작성 용이 유지보수성 향상
  • 쿼리 자동 생성 → SQL 중복 및 작성량 감소, 생산성 증가
  • 지연 로딩, fetch join, dirty checking 등을 지원 성능 최적화 쉬움

 

4. 적용 과정

(1) 엔티티 정의

  • @Entity, @ManyToOne, @OneToMany 등 연관 관계 정리
  • @Id, @GeneratedValue 등 기본 키 관리용 어노테이션 추가
  • 지연 로딩(FetchType.LAZY) 설정

(2) Repository 전환

//예시: PostRepository

package com.nbara.seoulbara.repository;

import com.nbara.seoulbara.domain.Post;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface PostRepository extends JpaRepository<Post, Long> {
    // JPARepository가 기본제공하지 않는 메서드 만들어서 추가
    default List<Post> findAllPostsSorted() {
        return findAll(Sort.by(Sort.Order.desc("postId")));
    }
}

(3) Service 계층 변경

  • @Transactional 적용
  • Controller와 Repository간 역할 분리 Service만이 Repository와 직접 소통

 

5. 성능 최적화 전략

 

MyBatis만 사용할 때는 딱히 불편한 줄 몰랐는데, JPA를 적용하고 나니 달라지는 게 많았다.

반복 코드를 작성하지 않아도 되고, 오타를 신경쓸 일이 줄어서 매우 좋았다.

  대응 방법
SQL 쿼리 작성 필요 JPA 적용 후 필요한 쿼리만 따로 작성
전체 데이터 조회 JPQL로 필요한 컬럼만 조회
복잡 쿼리 필요 @Query, Querydsl 활용
대량 데이터 처리 Infinite Scroll 전략 활용

 

6. 효과

  • 가독성 & 유지보수성 UP
  • 비즈니스 로직 명확히 분리
  • 확장 대비 구조 설계 완료

불릿 포인트 형식으로 작성하면 눈에 쉽게 들어오니 위와 같이 작성했지만, 사실 이런 것보다는 체감 생산성이 엄청나게 늘었다.

한두 글자 잘못 쓴 SQL문을 찾아 디버깅하거나, 반복적이고 단순한 쿼리 작성에 오랜 시간을 빼앗기는 일이 아예 사라졌기 때문이다.

 

  리팩토링 요약

 

진작 적용할걸.

그래도 나중에 통계 쿼리나 동적 쿼리 쓸 일도 있을 수 있고, 걷는 법을 알아야 뛸 수 있으니 이전에 MyBatis와 SQL쿼리 활용법을 배웠다고 생각해야겠다.

그리고 좀 찾아보니, 한국은 전자정부 프레임워크 쓰는 데가 많으니 레거시도 MyBatis인 경우가 많다고 한다. 모든 배움에 헛됨은 없느니...

JPA를 통해 ORM을 접하고 나니, 이후 다른 ORM을 적용할 때도 비슷한 구조가 많아 좋았다.

항목 리팩토링 전 리팩토링 후
DB 접근 방식 MyBatis + SQL Mapper 직접 작성 JPA + Repository 구조
데이터 흐름 Controller → Service → Mapper(DAO) → DB Controller → Service → Repository → DB
코드 복잡도 SQL 반복 + 필드 매핑 복잡 Entity 중심 구조, 재사용성 ↑
성능 대응 직접 튜닝 필요 지연로딩 전략, 쿼리 최적화 활용

 

 

+ Recent posts