300===Dev Framework/Spring

Spring Data JDBC: JPA보다 가벼운 ORM의 대안 🚀

블로글러 2024. 11. 14. 12:39

안녕하세요! 오늘은 Spring Data JDBC에 대해 좀 더 깊이 있게 알아보겠습니다.
JPA(ORM) 사용이 너무 무겁고 복잡하게 느껴지셨나요? Spring Data JDBC는 훨씬 단순하면서도 기본적인 ORM 기능을 제공하여, 가벼운 프로젝트나 간단한 데이터 매핑이 필요한 경우 매우 유용한 대안이 될 수 있습니다.

 

1. Spring Data JDBC란? 🤔

Spring Data JDBC는 JPA처럼 객체와 데이터베이스를 매핑해 주지만, 영속성 컨텍스트(Persistence Context)가 없고, 프록시 객체캐싱 같은 복잡한 기능 없이 “직접적인 SQL 실행”으로 작동합니다.
쉽게 말해 “미니멀한 ORM”이라고 할 수 있으며, 아래와 같은 특징을 갖습니다.

  • 🔹 단순한 구조: JPA처럼 복잡한 설정이나 매핑 규칙을 고민하지 않아도 됩니다.
  • 🔹 명시적인 쿼리 동작: 영속성 컨텍스트가 없어 “지연 로딩(Lazy Loading)”이나 “캐싱”이 발생하지 않습니다.
  • 🔹 예측 가능한 SQL 실행: SQL이 언제 어떻게 실행되는지 명확합니다.
  • 🔹 명시적인 저장 시점: JPA처럼 트랜잭션 종료 시점이 아닌, repository.save() 호출 시점에 바로 DB에 반영됩니다.

2. 어떻게 동작하나요? 🎬

1) 기본 개념

Spring Data JDBC는 Repository 인터페이스를 통해 기본적인 CRUD를 제공하며, SQL이 거의 즉시 실행됩니다.
JPA와 달리 엔티티(Entity)를 영속성 컨텍스트에 두고 관리하지 않으므로, 엔티티 상태 변화 추적(Dirty Checking)이나 지연 로딩 같은 메커니즘이 없습니다.

예시: 프로젝트 설정

application.yml (H2 DB 예시)

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create

Maven 또는 Gradle 의존성:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jdbc</artifactId>
    <version>2.4.0</version> <!-- 예시 버전 -->
</dependency>

2) 실제 적용 예시

엔티티(Entity) 매핑

@Table("users")
public class User {

    @Id
    private Long id;
    private String name;
    private String email;

    public User() {}

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // getters and setters...
}
  • @Table("users"): 해당 클래스를 DB의 users 테이블과 매핑
  • @Id: PK(Primary Key) 컬럼임을 지정

리포지토리(Repository)

@Repository
public interface UserRepository extends CrudRepository<User, Long> {
    List<User> findByEmail(String email);
}
  • CrudRepository<T, ID> 상속: 기본적인 CRUD 메서드(save, findById, delete, ...) 제공
  • findByEmail(String email): 메서드 이름 기반 쿼리(SELECT * FROM users WHERE email = ?) 자동 생성

 

3. 주요 장점 🌟

1) 단순하고 예측 가능

Spring Data JDBC는 직관적인 CRUD 메서드와 명시적인 쿼리 실행을 제공하기 때문에, 복잡한 매핑 설정 없이도 테이블 구조를 쉽게 다룰 수 있습니다.

  • 코드 유지보수가 용이하며, SQL이 어떻게 실행되는지 예측하기 쉽습니다.
  • N+1 문제와 같은 복잡한 쿼리 이슈에서 상대적으로 자유롭습니다.

2) 가벼운 의존성

JPA는 Hibernate 같은 JPA 구현체가 필요하지만, Spring Data JDBC는 별도의 무거운 구현체 없이 JDBC만으로 동작합니다.

  • 프로젝트 규모가 작거나, 복잡한 엔티티 매핑이 필요 없는 경우 부담이 훨씬 적습니다.

3) 명시적인 저장 시점

JPA에서는 엔티티 변경사항이 트랜잭션 종료 시점에 자동으로 반영되지만, Spring Data JDBC는 save() 호출 시점에 SQL이 즉시 실행됩니다.

  • 이는 “영속성 컨텍스트”가 없기 때문에 가능한 것으로, 데이터 변경 흐름을 직접 제어하기에 유리합니다.

 

4. 주의할 점 ⚠️

1) 복잡한 연관관계는 비권장

Spring Data JDBC에서는 1:N, N:1 정도의 간단한 관계 매핑이 주로 사용됩니다.

  • 복잡한 계층 구조나 다대다 매핑, 양방향 매핑 등을 빈번히 사용해야 한다면 JPA가 더 적합합니다.

2) 대규모 데이터, 페이징 처리

Spring Data JDBC는 자동 지연 로딩 기능이 없으므로, 대규모 데이터를 한 번에 가져올 시 성능 문제가 발생할 수 있습니다.

  • 필요하다면 SQL 또는 Spring Data JDBC의 PagingAndSortingRepository를 활용하고, 직접 페이징 쿼리를 작성하는 방식을 고려해야 합니다.

3) 부분 업데이트/변경 감지 기능 부재

JPA처럼 ‘Dirty Checking’ 기능이 없어, 엔티티 일부만 변경하고 싶어도 전체 엔티티를 새로 업데이트해야 할 수 있습니다.

  • 이는 사용상의 편의성보다는 명시적이고 확실한 데이터 처리를 선호하는 상황에서 장점이 되기도 합니다.

 

5. 실제 사용 예시 📱

아래는 OrderOrderItem 간의 1:N 관계 예시입니다.

@Table("orders")
public class Order {

    @Id
    private Long id;
    private String orderNumber;

    // 1:N 관계
    @MappedCollection(idColumn = "order_id")
    private List<OrderItem> items = new ArrayList<>();

    public Order() {}

    public Order(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    // getters and setters...
}

@Table("order_items")
public class OrderItem {

    @Id
    private Long id;
    private String productName;
    private int quantity;

    // getters and setters...
}
public interface OrderRepository extends CrudRepository<Order, Long> {

    // orderNumber로 주문 검색
    List<Order> findByOrderNumber(String orderNumber);
}

사용 방법 예시

@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Transactional
    public Order createOrder(Order order) {
        // 필요 시점에 바로 save => SQL 즉시 실행
        return orderRepository.save(order);
    }

    @Transactional
    public List<Order> searchByOrderNumber(String orderNumber) {
        return orderRepository.findByOrderNumber(orderNumber);
    }
}
  • 생성: repository.save()를 호출하면 즉시 INSERT SQL 실행
  • 조회: repository.findByOrderNumber(...) → 직접 쿼리 생성
  • 수정: 수정 후 save() 재호출 → UPDATE SQL 실행
  • 삭제: repository.delete(...) → DELETE SQL 실행

 

6. 마치며 🎁

정리하자면, Spring Data JDBC가벼운 ORM으로서, 단순하고 예측 가능한 방식으로 SQL을 실행하고 싶을 때 적합합니다.

  • 규모가 큰 프로젝트에서 복잡한 엔티티 매핑, 지연 로딩, 캐싱이 필요한 경우라면 JPA를 고려해야 합니다.
  • 그러나 가벼운 데이터 처리나 복잡하지 않은 1:N 관계 중심의 모델이라면, Spring Data JDBC로 충분히 높은 생산성과 직관적인 코드를 얻을 수 있습니다.

 

이 기술을 사용하면, “간단한 데이터 매핑과 예측 가능한 SQL 실행”이 동시에 가능!

 

참고 자료 및 출처

728x90