반응형

처음 Spring에 입문했을 때, Spring Data JPA를 바로 사용하게 되면서 Repository에서 구현 클래스 없이 Interface만으로 개발을 할 수 있었다. 공부를 하면서 JPA, ORM, Hibernate, JDBC와 같은 개념들이 등장했고, 이들이 어떻게 구현되고 있는지, 위 사진을 이해하는 것이 목표이다.

 

가장먼저 ORM을 알아야 하지만, 이해를 돕기위해 RDB를 먼저 짚고 넘어가자.

 

 

 

0. RDB와 OOP


관계형 데이터베이스는 테이블, 행, 열의 정보를 구조화하여 데이터 간의 관계를 설정할 수 있는 데이터베이스 모델이다.

 

전반적으로 관계형 데이터베이스는 강력하고 효율적인 데이터 저장 및 검색 기능을 제공하지만 복잡한 객체 지향 개념을 모델링(상속, 다형성, 레퍼런스, 오브젝트)하는 데 적합하지 않다.

 

우리는 객체 지향 언어(Java)를 사용하고 있는데 RDB는 객체 지향을 모델링하는데 적합하지 않다?

 

이 말은 관계형 데이터베이스와 객체 지향 언어가 서로 다른 개념을 기반으로 설계되었기 때문에 직접적인 매핑이 어렵다는 의미이다.

 

객체 지향 프로그래밍(OOP) 관계형 데이터베이스
(RDB)
불일치 문제
객체(Object) 테이블(Table) 객체는 상태와 행동을 가지지만, 테이블은 행동 없이 데이터(값)만 저장
필드(Field) 컬럼(Column) 기본적으로 매핑 가능하지만, 복잡한 구조(예: 리스트, 객체 포함)는 변환이 필요
객체 참조(Reference) 외래 키(Foreign Key) 객체의 참조는 포인터 개념이지만, RDB에서는 FK로 조인해야 함
상속(Inheritance) 테이블에는 직접적인 개념 없음 OOP의 상속 구조를 테이블에 표현하려면 별도 설계(싱글 테이블 전략, 조인 전략 등)가 필요
다형성(Polymorphism) SQL에서 지원 X SQL에서 다형적 관계를 표현하려면 테이블 구조를 맞춰야 함 (지원 X)

 

 

결론적으로 왜 Java + RDB 조합이 흔한가?

 

Java와 RDB는 서로 다른 패러다임이지만, 기업 환경에서 가장 안정적이고 확장성이 뛰어난 조합이기 때문이다.

1️⃣ RDB는 표준 데이터 저장 방식이고, Java는 기업 시스템에서 가장 많이 쓰이는 언어

2️⃣ ORM을 통해 객체-관계 불일치 문제를 해결할 수 있음

3️⃣ Java의 성능, 안정성, 멀티쓰레딩RDB의 강력한 무결성, 확장성이 만나 기업 시스템에 최적화됨

4️⃣ Spring, Hibernate, MyBatis 같은 프레임워크가 Java + RDB 조합을 더욱 강력하게 지원

 

결국, 현실적인 이유로 Java와 RDB는 궁합이 잘 맞는 조합이 되었다.

 

따라서 Java와 RDB의 패러다임 차이(객체-관계 불일치 문제)를 해결하기 위해 ORM이 등장했다.

 

 

 

1. ORM


ORM(Object Relational Mapping)의 핵심은 ‘객체와 데이터베이스 테이블을 매핑하여, SQL을 직접 작성하지 않고 객체를 통해 데이터를 조작할 수 있도록 하는 기술’이다. 즉, 내가 코드 상에서 생성한 객체가 DB상에 어떤 테이블과 연결이 된다는 것을 의미한다. 이렇게 되면 내가 객체를 조작함으로써 DB를 조작할 수 있게 된다.

 

우리는 JPA에서 DB에 대한 접근을 시도할 때 직접 sql 쿼리문을 만들지 않는다. 다만 객체를 이용한 메소드를 통해 이를 관리할 뿐이다. 예를 들어, SELECT * FROM user 대신 userRepository.findAll() 같은 메서드를 호출하면, ORM이 내부적으로 SQL을 생성하여 실행해 준다.

 

ORM을 사용하면 객체와 테이블 간의 매핑을 통해 객체를 조작하는 것만으로 자동으로 SQL이 생성되며, 개발자는 비즈니스 로직에 집중할 수 있어 생산성이 매우 높아진다.

 

ORM 프레임워크 종류

  • JAVA : JPA, Hibernate, EclipseLink, DataNucleus, Ebean 등

 

 

위 사진을 보면 Data Access Layer들이 곧 Service Layer와 DataBase사이를 연동해주고 있다. JPA, Hibernate은 Java의 ORM 프레임워크인 셈이다. 그렇다면 JDBC API는 ORM인가?

 

 

 

2. JDBC


JDBC(Java Database Connectivity)는 ORM이 아닌, 자바에서 데이터베이스에 연결하고 작업을 수행하기 위한 표준 인터페이스이다.

 

Java는 DBMS의 종류에 상관없이 JDBC API를 통해 데이터베이스 작업을 처리할 수 있다. JDBC를 사용하면 데이터베이스에 접근하여 SQL을 실행하고, 데이터를 삽입(INSERT), 조회(SELECT), 수정(UPDATE), 삭제(DELETE)하는 CRUD 작업을 효율적으로 수행할 수 있다.

 

과거에는 MySQL, Oracle, PostgreSQL 등 각 DBMS마다 다른 방식의 SQL을 사용해야 했기 때문에 개발이 번거로웠다.

이러한 문제를 해결하기 위해 JDBC는 메서드와 전역 변수를 하나의 표준 문법으로 통일하여, DBMS에 관계없이 일관된 방식으로 데이터베이스를 다룰 수 있도록 했다.

 

JDBC 핵심: DBMS의 종류에 상관없이 하나의 JDBC API를 이용하여 일관된 문법으로 데이터베이스 작업을 처리할 수 있다.

JDBC Architecture

JDBC는 Java 애플리케이션과 다양한 DBMS를 연결하는 계층적인 구조로 이루어져 있다.

 

 

JDBC 아키텍처 구성 요소

1️⃣ JDBC API (Application Programming Interface)

  • 개발자가 직접 사용하는 JDBC 인터페이스
  • Connection, Statement, ResultSet 등의 클래스를 제공하여 데이터베이스와 통신

2️⃣ JDBC Driver Manager (드라이버 관리자)

  • 애플리케이션과 각 DBMS용 드라이버를 연결하는 역할
  • 여러 개의 JDBC 드라이버를 관리하며, 적절한 드라이버를 선택하여 연결을 수행

3️⃣ JDBC Driver (JDBC 드라이버)

  • 각 DBMS(MySQL, Oracle 등)에서 제공하는 JDBC 구현체
  • JDBC API를 사용해 작성된 코드가 각 DBMS에 맞는 SQL로 변환되도록 지원

4️⃣ DBMS (Database Management System)

  • MySQL, Oracle, PostgreSQL, MS SQL Server 등의 데이터베이스
  • JDBC 드라이버를 통해 SQL을 실행하고 데이터를 반환

 

 

JDBC와 ORM의 차이점

구분 JDBC ORM (JPA, Hibernate)
SQL 작성 여부 직접 SQL 작성 필요 SQL 자동 생성
데이터 조작 방식 SQL을 이용한 절차적 접근 객체를 이용한 선언적 접근
생산성 SQL을 직접 작성해야 하므로 상대적으로 낮음 자동화된 SQL 처리로 생산성이 높음
유연성 SQL을 직접 제어 가능 ORM 설정에 따라 자동 최적화됨
러닝 커브 쉽고 직관적 ORM 개념을 익혀야 함

 

🚀 JDBC는 ORM의 기반이 되는 기술이며, ORM은 JDBC를 활용하여 더 높은 수준의 데이터 접근 방식을 제공하는 기술이다.

 

 

그럼 또 다시 아래 그림을 보자, 이제 Service, Data Aceess, DB와의 연관 관계를 어느정도 알았다.

 

그러면 JPA와 Hibernate 는 무엇이고 어떻게 객체를 DB와 연동하는 것인가?

 

 

 

 

3. JPA


JPA(Java Persistence API)는 Hibernate 기반의 자바 ORM 기술 표준으로, 자바 애플리케이션에서 관계형 데이터베이스를 다루는 방식을 정의한 인터페이스이다. JPA는 어떻게 동작할까?

JPA는 영속성 컨텍스트(EntityManager)를 통해 Entity를 관리한다. 사용자가 Entity에 대한 CRUD 작업을 수행하면, JPA는 해당 Entity와 매핑된 테이블에 대한 SQL 쿼리를 자동 생성하여, 적절한 시점에 JDBC API를 통해 데이터베이스에 반영한다.

 

 

이해를 돕기 위해 영속성 컨텍스트 개념을 먼저 살펴보자.

영속성 컨텍스트(Persistence Context)란 엔티티를 저장하고 관리하는 논리적 메모리 공간이다.

 

왜 영속성 컨텍스트를 사용할까?

 

데이터베이스와의 통신 횟수를 줄이고, 성능을 최적화하기 위해 사용한다. 마치 메모리 캐시를 활용하여 중복 연산을 줄이는 것과 같은 개념이다. 이를 통해 불필요한 쿼리 실행을 최소화하고, 성능을 향상시킬 수 있다.

 

영속성 컨텍스트를 활용하면 얻을 수 있는 주요 이점은 다음과 같다.

 

 

1️⃣ 1차 캐시 (First Level Cache)

JPA는 엔티티를 조회할 때 1차 캐시에 저장해두고, 이후 동일한 엔티티를 조회할 경우 DB가 아닌 1차 캐시에서 가져온다.

java

// A. 캐시 없이 매번 DB 조회하는 경우
User user1 = entityManager.find(User.class, 1L); // DB에서 조회
User user2 = entityManager.find(User.class, 1L); // DB에서 다시 조회 (비효율적)

// B. JPA 1차 캐시 활용
User user1 = entityManager.find(User.class, 1L); // DB에서 조회 후 1차 캐시에 저장
User user2 = entityManager.find(User.class, 1L); // 1차 캐시에서 조회 (DB 쿼리 발생 X)

 

 

 

2️⃣ 쓰기 지연 (Write Behind, Write Delay)

빈번한 쓰기 작업을 하게 되면 context switching이 여러번 발생할 것이다. 따라서 이러한 Context Switching에 대한 overhead를 줄여주는게 쓰기 지연이다.

 

즉, 쓰기를 원하는 entity를 저장해 두었다가 한번에 처리한다는 것을 의미한다.

 

JPA는 INSERT/UPDATE/DELETE 쿼리를 즉시 실행하지 않고, 트랜잭션을 커밋하는 시점에 한꺼번에 처리한다. JPA는 여러 개의 변경 작업을 모아뒀다가 한 번에 처리하여 성능을 최적화한다.

java

EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin(); // 트랜잭션 시작

entityManager.persist(memberA);
entityManager.persist(memberB);
entityManager.persist(memberC);

// INSERT SQL이 즉시 실행되지 않음

transaction.commit(); // 트랜잭션을 커밋하는 순간, 한꺼번에 INSERT 실행

 

 

 

3️⃣ 변경 감지 (Dirty Checking)

JPA는 엔티티의 변경 사항을 자동으로 감지하고, 트랜잭션이 커밋될 때 변경된 부분만 UPDATE 쿼리를 실행한다. 이를 위해 JPA는 영속성 컨텍스트(1차 캐시)에서 엔티티의 스냅샷(초기 상태)을 유지하며, 트랜잭션이 끝날 때 변경 여부를 비교한다.

 

이는 다음과 같은 장점이 있다.

  • 자동 변경 감지: 직접 UPDATE SQL을 작성하지 않아도 자동으로 변경 사항이 반영됨
  • 성능 최적화: 변경된 필드만 UPDATE 실행 (전체 컬럼 업데이트 방지)
  • 객체 중심 개발 가능: 데이터 변경을 신경 쓰지 않고 객체 필드만 수정하면 자동 반영
  • 트랜잭션 기반 처리: 하나의 트랜잭션 내에서 변경 사항을 모아두었다가 한 번에 처리

java

// A. Dirty Checking 사용 (자동 감지)
User user = entityManager.find(User.class, 1L);
user.setName("New Name"); // 자동 변경 감지
transaction.commit(); // 변경 감지 후 UPDATE 실행

// B. 명시적인 UPDATE 사용
User user = entityManager.find(User.class, 1L);
user.setName("New Name");

entityManager.createQuery("UPDATE User u SET u.name = :name WHERE u.id = :id")
    .setParameter("name", "New Name")
    .setParameter("id", 1L)
    .executeUpdate(); // 명시적으로 UPDATE 쿼리 실행

 

 

 

영속성 컨텍스트의 주요 개념을 살펴보았으니, 이제 이를 관리하는 핵심 객체인 EntityManagerFactoryEntityManager에 대해 알아보자.

 

JPA에서 EntityManagerFactory는 영속성 컨텍스트를 생성하고 관리하는 역할을 하며, EntityManager는 개별 트랜잭션 단위에서 엔티티의 생명주기를 관리하는 중요한 객체이다.

 

그렇다면 EntityManager는 언제, 어떻게 생성되고 사용될까?

 

 

EnitityManagerFactory ➡ WAS가 시작되는 시점에 만들어지고 각 트랜잭션에 대해 새로운 EntityManager를 생성하고 관리하는 Object이다. Transaction 단위를 수행할 때마다 생성한다. 즉, 고객의 요청이 올 때마다 사용했다가 닫는다. 따라서 Thread당 생성이 되고 이에 따라 Thread당 공유가 되지 않는다.

 

 

EntityManager ➡ Entity를 생명주기를 관리하는 context로 Persistence Context역할을 수행해준다. 우리가 Entity를 관리한다는 것을 EntityManger가 Entity에 대한 생명주기를 관리한다는 것을 의미할 것이다. 마치 메모리에서 데이터를 언제 올리고 어떻게 내릴지에 관해 관리하는 것처럼 말이다. 이러한 Entity에 대한 Life Cycle을 정리하면 (비영속, 영속, 준영속, 삭제)가 있다.

 

 

❓그러면 JPA는 모두 트랜잭션일 때 사용이 가능한가?

A: 아니다. Spring에서 트랜잭션 없이 JPA를 사용할 수 있지만 일반적으로 데이터 일관성과 무결성을 보장하기위해 트랜잭션을 활성화 하는 것이 좋다.

 

❓그러면 거의 모든 CRUD 서비스 단에서는 트랜잭션을 활성화 해야겠네?

A: 맞다. 대부분 CRUD 작업을 수행할 때 데이터 일관성 무결성을 보장하기 위해 트랜잭션이 필요하다. 하지만 Read 와 같이 읽기만 하는 경우 @Transactional(readOnly = true)를 설정하여 조회용으로 가져온 Entity의 예상치 못한 수정을 방지할 수 있다. 또한, readOnly = true를 설정하게 되면 JPA는 해당 트랜잭션 내에서 조회하는 Entity는 조회용임을 인식하고 변경 감지를 위한 Snapshot을 따로 보관하지 않으므로 메모리가 절약되는 성능상 이점 역시 존재한다.

 

 

JPA는 영속성 컨텍스트인 EntityManager를 통해 Entity를 관리하고 이러한 Entity가 DB와 매핑되어 사용자가 Entity에 대한 CRUD를 실행을 했을 때 Entity와 관련된 테이블에 대한 적절한 SQL 쿼리문을 생성하고 이를 관리하였다가 필요시 JDBC API를 통해 DB에 날리게 된다.

 

다시 위 문장을 보고 정리를 하자면,

  • JPA는 영속성 컨테스트를 사용하여 db와의 통신을 효율적으로 관리한다.
  • 이러한 영속성 컨텍스트에 대한 JPA상에서 구현체를 EntityManger라고 한다.
  • 이러한 EntityManger는 Entity에 대한 생명주기를 관리하고 db와의 연결정보를 저장해둔다.
  • Entity에 대한 CRUD는 연결정보를 바탕으로 JPA가 자동으로 생성해준다.
  • 이러한 쿼리문은 EntityManger가 관리를 하다가 필요시 처리하게 된다.

 

JPA는 애플리케이션과 JDBC 사이에서 동작한다. JPA는 데이터베이스와 객체를 매핑하는 기술일 뿐, 내부적으로는 데이터베이스와의 통신을 위해 JDBC를 사용한다. 개발자가 JPA를 사용하면, JPA 내부에서 JDBC API를 사용하여 SQL을 호출하여 DB와 통신한다.

 

 

 

또한, JPA도 JDBC와 마찬가지로 인터페이스이기 때문에 구현체가 필요하고, 그 구현체 중 하나가 Hibernate이다.

 

 

 

지금까지의 내용을 그림을 통해 다시 정리를 하고 Hibernate로 넘어가보자.

 

잠깐 그러면 Spring Data JPA는 뭐지 ?

 

 

 

4. Spring Data JPA


Spring Data JPA는 JPA를 더 쉽게 사용하기 위해 JPA 위에 구축된 상위 프레임워크로 데이터 접근 계층을 더욱 간편하게 구성할 수 있도록 돕는 기술이다.

 

1️⃣ Repository 인터페이스를 활용한 쿼리 자동 생성

  • Spring Data JPA는 Repository의 메소드를 통해 쿼리를 날릴 수 있다.
  • 이는 JPA를 한 단계 추상화시킨 Repository라는 인터페이스를 제공함으로써 이루어진다.
  • 사용자가 Repository 인터페이스에 정해진 규칙대로 메소드를 입력하면, Spring이 알아서 해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.

2️⃣ 복잡한 쿼리 작성 지원

  • 단순한 Method 기반 쿼리 생성 외에도 @Query, QueryDSL를 사용하여 SQL을 구현할 수 있다.

java

interface UserRepository extends JpaRepository<User, Long> {
    // 1. Method
    List<User> findByName(String name);  // SELECT * FROM user WHERE name = ?

    // 2. @Query
    @Query("SELECT u FROM User u WHERE u.email = :email")
    User findByEmail(@Param("email") String email);
}

 

간단하게 설명하면

  • 메서드명 규칙을 기반으로 JPA 쿼리를 자동 생성하여 코드량을 줄이고 생산성을 향상시키는 프레임워크
  • 추가적인 SQL 작성이 필요한 경우 @Query 애너테이션 또는 Querydsl을 사용하여 해결 가능

 

 

 

5. Hibernate


Hibernate는 Java 기반의 ORM 프레임워크이며, JPA의 구현체 중 하나이다.

 

즉, JPA의 인터페이스를 직접 구현하며, 내부적으로 JDBC API를 활용하여 동작한다.

 

JPAHibernate는 마치 Java의 Interface와 이를 구현하는 class의 관계와 유사하다.

JPA는 규칙(인터페이스)을 정의하고, Hibernate는 이를 구현(Implementation)한 라이브러리이다.

JPA와 Hibernate의 상속 및 구현 관계

 

JPA의 핵심인EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서는 각각 SessionFactory, Session, Transaction으로 상속받고 각각 Impl로 구현하고 있음을 확인할 수 있다.

 

“Hibernate는 JPA의 구현체이다”로부터 도출되는 중요한 결론 중 하나는 JPA를 사용하기 위해서 반드시 Hibernate를 사용할 필요가 없다는 것이다.

 

Hibernate의 작동 방식이 마음에 들지 않는다면 언제든지 DataNucleus, EclipseLink 등 다른 JPA 구현체를 사용해도 되고, 심지어 본인이 직접 JPA를 구현해서 사용할 수도 있다.

 

다만 그렇게 하지 않는 이유는 단지 Hibernate가 굉장히 성숙한 라이브러리이기 때문일 뿐이다.(오랜 기간 검증된 안정적인 라이브러리, JPA의 표준을 기반으로 동작)

JPA, Hibernate Architecture

 

쉽게 얘기하면 Hibernate는 JPA에서 메서드로 요청된 정보들을 JDBC가 잘 알아들을 수 있게 한번 더 표준화 작업을 해주고 JDBC에게 넘겨준다.

 

스프링 부트 프로젝트의 External Liberies에 hibernate-core 을 찾아보면 구경할 수 있다.

 

이후 실무에서 JPA를 쓰지않으면 어떻게할까 궁금하여 찾아보았다.

2019.3.3 - "현재 저는 회사에서 각기 다른 환경을 가지고 있는 세 시스템을 동시에 다루고있습니다."

  • 유통시스템 : 마이플랫폼 + Spring framerwork(java) - 옛 DI구조 + mybatis + oracle
  • SCM시스템 : JSP,js,css + Spring framework(java) - 어노테이션구조 + mybatis + oracle
  • 자판기시스템 : 넥사크로 + Spring framework(java) + hibernate + postegreSql

 

전세계적으로는 hibernate가 가장 많았고, 유난히 동아시아 3국에서 mybatis 점유율이 높았고 JPA의 점유율도 점점 상승하고 있는 것 같다. 

 

JPA가 처음 나온 16~17년도 당시에는 JPA와 mybatis를 혼용해서 쓰는 경우가 많았지만, 현재는 JPA + QueryDSL을 많이 쓰는 추세이다. 아무래도 mybatis 같은경우는 직접 XML로 쿼리를 작성을 해야하는데 오류나 실수로인한 에러를 통해 서비스가 장애를 일으킬 수 있기 때문에 SI 쪽이 아니라면 JPA와 mybatis, hibernate와 같은걸 같이 사용하지 않는 추세인 것 같다. 


+

그렇게 Spring Data JPA과 QueryDSL을 공부하고

SI 회사에 입사해서 JSP,js,css + Spring framework(java) + mybatis + oracle을 하게되었다

+ Recent posts