Skip to the content.

Spring Data JPA Repository 가이드

목적: Spring Data JPA Repository 인터페이스 컨벤션 (Command 전용)


1️⃣ 핵심 원칙

JPA Repository는 순수하게 JpaRepository만 상속

규칙:

이유:


2️⃣ Command 처리 전략 (Merge 기반)

DDD 순수성을 위한 설계 결정

아키텍처 흐름:

QueryDSL 조회 → Entity → Mapper → Domain (Application Layer에서 조립/로직 실행)
    → Domain 값 변경 → Mapper → Entity → JPA save() (Merge)

핵심 원칙:

Merge vs Dirty Checking

// ❌ Dirty Checking (영속 엔티티 직접 수정 - 우리 방식 아님)
@Transactional
void update(Long id) {
    Entity entity = entityManager.find(Entity.class, id); // 영속
    entity.setName("changed"); // 직접 수정
    // 트랜잭션 커밋 → UPDATE 1회
}

// ✅ Merge (우리 방식 - DDD 순수성 유지)
@Transactional
void update(Domain domain) {
    Entity entity = mapper.toEntity(domain); // 새 Entity 객체 (비영속)
    jpaRepository.save(entity); // Merge 동작
    // ID 존재 → SELECT 1회 + UPDATE 1회
}

Merge를 선택한 이유:

트레이드오프:


3️⃣ 성능 최적화 전략

성능 크리티컬 케이스: JdbcRepository 사용

Merge의 추가 SELECT가 성능 병목이 되는 경우:

해결책: JdbcRepository로 직접 UPDATE 쿼리

// JdbcRepository (성능 크리티컬 케이스)
@Repository
@RequiredArgsConstructor
public class OrderJdbcRepository {

    private final JdbcTemplate jdbcTemplate;

    /**
     * 성능 크리티컬: 직접 UPDATE (SELECT 없이 1회 쿼리)
     */
    public void updateStatus(Long orderId, String status) {
        jdbcTemplate.update(
            "UPDATE orders SET status = ?, updated_at = NOW() WHERE id = ?",
            status, orderId
        );
    }

    /**
     * 대량 배치 업데이트
     */
    public void batchUpdateStatus(List<Long> orderIds, String status) {
        jdbcTemplate.batchUpdate(
            "UPDATE orders SET status = ?, updated_at = NOW() WHERE id = ?",
            new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    ps.setString(1, status);
                    ps.setLong(2, orderIds.get(i));
                }
                @Override
                public int getBatchSize() {
                    return orderIds.size();
                }
            }
        );
    }
}

사용 기준

케이스 권장 방식 이유
일반 CRUD JPA Repository (Merge) DDD 순수성, 코드 간결성
대량 배치 JdbcRepository 성능 (N개 → 1개 쿼리)
고빈도 업데이트 JdbcRepository 레이턴시 최소화
단순 상태 변경 JdbcRepository 고려 SELECT 생략 가능

원칙:

기본은 JPA Merge, 성능 문제 측정 후 JdbcRepository 도입


4️⃣ 기본 템플릿

package com.company.adapter.out.persistence.order.repository;

import com.company.adapter.out.persistence.order.entity.OrderJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * OrderRepository - Order JPA Repository
 *
 * <p>Spring Data JPA Repository로서 Order Entity의 기본 CRUD를 담당합니다.</p>
 *
 * <p><strong>제공 메서드 (Command 전용):</strong></p>
 * <ul>
 *   <li>save(entity): 저장/수정 (INSERT/UPDATE)</li>
 *   <li>delete(entity): 삭제 (DELETE)</li>
 *   <li>deleteById(id): ID로 삭제</li>
 * </ul>
 *
 * <p><strong>Query 작업:</strong></p>
 * <ul>
 *   <li>모든 Query 작업은 OrderQueryDslRepository 사용</li>
 *   <li>findById(), findAll() 등도 QueryDslRepository에서</li>
 * </ul>
 *
 * @author Development Team
 * @since 1.0.0
 */
public interface OrderRepository extends JpaRepository<OrderJpaEntity, Long> {
    // ❌ Query Method 추가 금지
    // ❌ @Query 추가 금지
    // ❌ QuerydslPredicateExecutor 상속 금지
}

5️⃣ 예시

✅ 올바른 예시

// ✅ JpaRepository만 상속
public interface OrderRepository extends JpaRepository<OrderJpaEntity, Long> {
}

❌ 위반 예시

// ❌ QuerydslPredicateExecutor 상속 금지
public interface OrderRepository extends
    JpaRepository<OrderJpaEntity, Long>,
    QuerydslPredicateExecutor<OrderJpaEntity> {  // ❌
}

// ❌ Query Method 추가 금지
public interface OrderRepository extends JpaRepository<OrderJpaEntity, Long> {
    Optional<OrderJpaEntity> findByOrderNumber(String orderNumber);  // ❌
}

// ❌ @Query 추가 금지
public interface OrderRepository extends JpaRepository<OrderJpaEntity, Long> {
    @Query("SELECT o FROM OrderJpaEntity o WHERE o.status = :status")  // ❌
    List<OrderJpaEntity> findByStatus(@Param("status") OrderStatus status);
}

// ❌ Custom Repository 구현 금지
public interface OrderRepositoryCustom {  // ❌
    List<OrderJpaEntity> searchOrders(SearchOrderQuery query);
}

6️⃣ 테스트 전략

JPA Repository는 단위 테스트 불필요

이유:

결론: JPA Repository는 테스트 파일을 작성하지 않습니다.


7️⃣ 체크리스트

JPA Repository 작성 시:


8️⃣ 참고 문서


작성자: Development Team 최종 수정일: 2025-12-04 버전: 1.1.0