BackEnd/CS

[CS] 자바의 영속성 (JDBC, SQLMapper, ORM) 정리

세모 2026. 3. 23. 02:36

JDBC ( Java Database Connectivity ) API

자바 애플리케이션이 데이터베이스와 상호작용하여 데이터를 영구적으로 저장(Persistence)할 수 있도록 돕는 가장 기초적인 API이다.

다음과 같은 특징을 가진다.

  • 범용성
    • 관계형 DB뿐만 아니라 스프레드시트, 플랫 파일 등 다양한 데이터 소스에 접근할 수 있는 공통 기반을 제공한다.
  • 구조
    • Java SE 8 이상에 기본 포함된 패키지로 구성되어 있다.
      • java.sql   -> 핵심 JDBC API ( 커넥션, SQL 실행 등)
      • javax.sql -> 데이터 소스 관리, 커넥션 풀, 분산 트랜잭션 등 확장 기능을 담당 
  • 중계자
    • 특정 DB를 사용하려면 해당 DB 제조사가 제공하는 JDBC 드라이버가 반드시 필요하다.
    • 이는 JDBC라는 표준 인터페이스를 각 DB에 맞게 실제 구현한 것.

Entity Bean의 등장

Java EE 컴포넌트인 EJB(Enterprise JavaBeans)라는 거대한 기술 규격 안에 데이터베이스의 영구적인 데이터를 자바 객체로 표현하고 관리하는 서버측 컴포넌트이며, 다음과 같은 특징을 가진다.

  • 영속성 관리
    • 데이터베이스 테이블의 한 행(Row)을 자바 객체로 매핑하여, 데이터 조작을 자동화하거나 컨테이너를 통해 처리한다.
  • 유형
    • CMP ( Container Managed Persistence )
      • 컨테이너가 데이터베이스 연동 코드를 자동적으로 생성 및 관리한다.
    • BMP ( Bean Managed Persistence )
      • 개발자가 직접 JDBC 코드를 작성하여 영속성을 관리한다.
  • 주요 기능
    • 데이터베이스 테이블과 매핑하여 CRUD( 생성, 조회, 수정, 삭제) 작업을 수행하며, 복잡한 비즈니스 로직을 포함할 수 있다.

초기 EJB 기술의 일부로, XML 설정의 복잡성과 성능 오버헤드로 인해 EJB와 같이 사장된 기술이며 EJB의 복잡한 설정과 침투적인 아키텍처에 반발하여, 가벼운 POJO기반의 Spring(컨테이너 역할)과 Hibernate(영속성 역할)가 대안으로 등장했다.


Hibernate의 등장

EJB Entity Bean은 상속과 구현이 강제되어 코드가 특정 기술에 종속되었고, 테스트가 불가능에 가까워 하이버네이트가 등장하였다.

하이버네이트는 자바 언어를 위한 객체 관계 매핑 프레임워크로 객체 지향 도메인 모델을 관계형 데이터베이스로 매핑하기 위한 프레임워크를 제공한다.

  • ORM의 대중화와 패러다임 불일치 해소
    • 객체지향 언어와 관계형 DB 사이의 근본적인 구조 차이를 해결하기 위해 Hibernate가 나왔다.
    • Entity Manager를 활용하여 SQL문을 직접 작성하지 않아도 객체를 DB에 자동 매핑해주는 개념을 정립
    • 따라서 특정 DB에 종속되지 않고 객체 지향 언어의 장점을 키워줌

JPA의 등장

하이버네이트가 등장하고 자바에서는 하이버네이트의 창시자를 영입해서 JPA 표준 스펙을 설계하여, ORM 기술을 어떻게 구현해야 하는지 정의한 표준 인터페이스입니다.

하이버네이트라는 특정 오픈 소스 기술에만 의존하지 않도록, 공통의 규칙을 만든 것이다.

표준화가 되며 다음과 같은 이점을 주었다.

  • 유지보수 용이성
    • 내부 구현체(Hibernate 등)를 교체하더라도 애플리케이션 코드를 크게 수정할 필요가 없습니다. (벤더 독립성)
  • 생산성 향상
    • 개발자는 데이터베이스의 상세한 쿼리보다 비즈니스 로직(객체)에 집중이 가능해짐.

여기서 말하는 JPA와 흔히 사용하는 스프링 데이터 JPA와 다르다.

  • JPA
    • 표준 인터페이스이며 EntityManager로 직접 다룬다.
    • JPQL로 객체 기반 쿼리를 작성
  • Spring Data JPA
    • JPA에서 EntityManager라는 것을 직접 다루지 않고, 스프링이 대신 관리해주는 한 단계 더 추상화한 개념
    • Repository 인터페이스만 만들면 EntityManager를 스프링이 접근하고 사용해주며, 쿼리를 자동으로 생성해준다.

JDBC, SQLMapper, ORM

그렇다면 들어왔던 JDBC, JPA, ORM이 뭔지 정확하게 뭔지를 알아보자.

  • JDBC API
    • Java언어의 Database 연결 표준 인터페이스
    • 자바 어플리케이션이 JDBC API를 사용하게 되면 JDBC DriverManager가 DB에 맞는 Diver를 사용해 DB에 접근하는 방식
    • 실질적인 동작 순서는 다음과 같다
      1. DriverManager를 이용해 DB의 Connection 인스턴스를 얻음
      2. Connection을 통해서 Statement를 얻음
      3. Statement를 이용해 ResultSet을 얻음
      4. ResultSet을 통해 데이터 조회 및 원하는 동작을 함
    • 단점
      • 커넥션 관리를 직접해야한다.
      • try-catch를 직접해야함.
      • SQL을 실행할때마다 Connection -> PreparedStatement -> ResultSet -> 닫기의 패턴이 매번 반복된다.
      • ResultSet에서 객체로 변환할때 일일이 객체에 넣어줘야 하는점.
      • SQL에 의존성을 가지기에 객체 필드 하나가 바뀌면 많은 수정이 발생함.
  • SQLMapper
    • 개발자가 직접 SQL 쿼리문을 작성하고, 쿼리와 객체의 필드를 매핑하여 데이터를 조회/ 저장하는 프레임워크
    • 특징
      • 직접 SQL을 제어하여 복잡한 쿼리나 튜닝이 필요한 쿼리 작성에 유리하다.
      • 쿼리 결과(ResultSet)를 객체로 자동 변환을 해준다.
      • DB와의 커넥션을 자동적으로 열고 닫아준다.
    • 스프링 JDBC(JdbcTemplate), MyBatis 존재
      • 스프링 JDBC (JdbcTemplate)
        • JDBC 사용 시 불편함 개선에 집중되었다.
        • 템플릿 콜백 패턴으로 중복 코드를 개선 해줬다.
        • 다양한 메서드와 함수형 인터페이스를 제공한다.
        • 한계점
          • 자바 코드 안에 SQL이 문자열로 들어가기 때문에, 쿼리가 길 경우 가독성이 떨어지고 오타를 찾기 힘들다.
      • MyBatis
        • JDBC를 사용할때 String으로 직접 작성하던 쿼리문을 분리하는데 집중되어 있다.
        • SQL을 XML이나 어노테이션으로 분리하여 SQL 코드의 유지보수성을 향상시킴
        • SqlSession을 통해 mapper XMLv파일에 정의된 SQL을 실행한다.
        • JdbcTemplate과 달리 동적 쿼리를 쉽게 작성이 가능하다.
          • <if>, <where>, <foreach>같은 전용 태그를 사용하여 동적 쿼리 작성에 도움을 준다.
  • ORM ( Object-Relational Mapping )
    • SQL에 의존적인 개발에 대한 문제점(패러다임의 불일치)을 개선하여 객체지향적인 개념을 활용한 방식
    • 패러다임의 불일치
      • 연관 관계(객체 참조), 상속을 표현하기 힘들어져 객체 지향 언어를 사용하지 못하는 현상
    • Entity Manager와 영속성 컨텍스트(Persistence Context)라는 개념을 사용
      • 엔티티 매니저 (Entity Manager)
        • 영속성 컨텍스트에 접근하여 엔티티를 저장, 수정, 조회하는 객체(관리자)이다.
        • 개발자는 해당 매니저를 호출해서 persist(), find() 같은 메서드 호출시 매니저가 영속성 컨텍스트라는 공간에 엔티티를 넣고 빼며 관리한다.
      • 영속성 컨텍스트 (Persistence Context)
        • 엔티티를 영구 저장하는 환경이자 논리적인 공간
        • 1차캐시라는 내부저장소이며, DB에 가기 전에 엔티티들이 머무는 가상의 공간이며 논리적인 영역
        • 트랜잭션의 마지막 Commit, Rollback이 일어날 경우 해당 저장소의 내용에 적용한다.
      • 해당 개념을 사용하는 이유
        • Caching
          • 엔티티 매니저를 통해 데이터 조회 시, 영속성 컨텍스트에 이미 데이터가 있다면 DB접근 없이 값을 반환할 수 있다. 덕분에 성능이 향상되고, 같은 식별자로 조회된 객체는 항상 같은 객체임을 보장한다.
        • Write-behind
          • 엔티티 매니저로 persist()를 호출해도 즉시 DB에 INSERT쿼리를 날리지 않고 영속성 컨텍스트라는 공간에 쿼리를 모아두고 한번에 트랜잭션이 끝나는 순간 commit()을 통해 한번에 보내게되는데, 이는 DB의 Disk I/O의 대한 병목을 해소하는 역할을 한다.
        • Dirty Checking
          • 영속성 컨텍스트는 처음 들어온 엔티티의 상태를 스냅샷으로 기억하며 나중에 엔티티 값이 변경되면 매니저가 이를 감지해 자동으로 UPDATE 쿼리를 만들어준다.
    • JPA(인터페이스), Hibernate(구현체)가 존재하며, 한번 더 추상화한 Spring Data JDBC와 Spring Data JPA가 존재한다.
    • Spring Data JDBC와 Spring Data JPA의 핵심 개념
      • Repository의 개념을 도입하여 자료구조의 Collection처럼 사용하게 하자는 목표를 가짐
        • DDD구조 즉 Domain-Driven Design방식을 목표로한다.
      • Spring Data JDBC
        • Spring Data JDBC는 SQLMapper와 ORM을 섞은 개념으로 SQLMapper에 가깝다고 볼 수 있다.
        • ORM의 장점인 @Table, @Id 같은 어노테이션으로 객체와 테이블을 매핑하고, 단순한 CRUD 쿼리를 자동을 생성해주는 편리함을 가진다.
        • 하지만 세션이나 1차 캐시가 없기 때문에, 데이터를 조회하면 매번 SQL이 실행되고, 수정 후에는 반드시 .save()를 호출해야 DB에 반영된다 즉, Dirty Checking이 존재하지 않는다.
        • 또한 SQL에 여전히 의존적이다
        • 강력한 JPA가 있지만 Spring Data JDBC를 사용하는 이유
          • JPA는 지연로딩이나 영속성 컨텍스트 때문에 SQL문의 이동에 대한 예측이 불가능하지만, Spring Data JDBC를 사용시 즉시 SQL문이 실행되어 동작이 명확하다.
          • 영속성 컨텍스트라는 거대한 개념을 배우지않아도, 반복적인 JDBC API 코드(커넥션관리, 결과매핑)를 줄이면서, 객체 지향적으로 개발하고 싶을때 최선의 선택지이다.
      • Spring Data JPA
        • Spring Data JPA는 EntityManager와 PersistenceContext의 개념과 Caching, Write-behind, Dirty Checking을 개발자가 직접하는 것이 아닌, 스프링이 자동으로 진행하도록 한다.
        • 또한 쿼리문을 직접 작성하지 않고 JpaRepository를 상속받는 interface를 만드는 것으로 기본적인 메소드를 제공하고, 추가적으로 사용자정의파인더를 사용이 가능하며, 어노테이션을 활용해 쿼리문을 작성하는 것도 가능하다.

ORM의 주의점

객체가 필드가 되는 상황에서는 FK(외래키)를 사용하면서 문제가 발생할 수 있다.

N+1문제란?

쿼리 1번으로 N개의 데이터를 가져왔는데, 연관된 데이터를 참조하기 위해 N번의 추가적인 쿼리를 발생하는 현상을 말한다.

  • 발생원인
    • 지연로딩(Lazy Loading) 상태의 연관 엔티티를 반복문에서 개별 접근할 때 발생
    • 주로 다대일, 다대다, 일대다 상황에서 발생하는 문제다.
  • 상황예시
    • 게시글과 댓글이 일대다의 관계라는 상황을 예시로 들어보자
      1. 모든 게시글을 조회한다. -> 1번의 쿼리 (결과가 게시물이 10개라는 가정)
      2. 루프를 돌며 각 게시글의 댓글 내용을  출력
        • 이때, 지연로딩때문에 모든 게시물에 대한 모든 댓글을 가져오는 쿼리가 게시물당 한번씩 실행됨. -> N번의 추가쿼리
  • 해결방안
    • 외래키 사용될 경우 기본적으로 FetchType.LAZY를 적용하며, 해결방안 적용이 권장됨
    • Fetch Join
      • JPQL에서 Join fetch 키워드를 사용하여, 처음부터 연관된 엔티티를 한번의 JOIN 쿼리로 다 가져오는 방식
    • Entity Graph
      • @EntityGraph를 통해 쿼리 시점에 함께 조회할 연관 관계를 명시하는 방법.
    • Batch Size 설정
      • IN절을 사용하여 게시글과 댓글을 한번에 묶어서 가져오는 방법.
  • 참고사항
    • FetchType 비교
      • EAGER
        • 항상 함께 사용되는게 확실한 경우 고려해볼 수 있다.
        • 하지만 예측하기 어려운 쿼리가 발생할 수 있으므로 기본적으로 LAZY와 필요한곳에서 위와 같은 해결방안을 사용한다
      • LAZY
        • 실제 사용 시점에서 쿼리가 나가지만 잘못 사용할 경우 N+1이 발생할 가능성이 존재한다.
        • 따라서 LAZY를 기본으로 사용하되 해결 방안을 적용하는 것이 권장된다.

정리

그동안 비슷한 언어들로 힘들게 이해했던 내용을 보기 편하도록 알아보자

발전의 타임테이블

이 타임 테이블을 보면 ORM의 대중화가 먼저 나온것이 이상하게 보일 수 있지만 그때 당시는 표준으로 JDBC API였으며, Hibernate는 표준 밖의 오픈소스 선택지였다. 이후 자바 진영이 Hibernate의 개념을 흡수해 JPA라는 표준을 만들어서 ORM이 자바의 공식 표준이 된것이다.

'BackEnd > CS' 카테고리의 다른 글

[CS] DBMS 개념와 동작 원리  (0) 2026.03.24
[CS] 쓰레드와 JVM의 쓰레드관리  (0) 2026.03.20
[CS] Java Virtual Machine  (0) 2026.03.20