Spring Data JPA 핵심 개념 및 고급 활용 가이드

Repository 핵심 인터페이스와 구조

Spring Data JPA의 데이터 액세스 계층은 Repository 인터페이스를 중심으로 추상화되어 있습니다. 이 인터페이스는 도메인 엔티티 클래스와 해당 ID 타입을 제네릭 인자로 받는 마커 인터페이스로 작동합니다. 기본적인 데이터 조작이 필요하다면 CrudRepository를 상속받아 CRUD 기능을 즉시 활용할 수 있으며, 페이징과 정렬 기능이 요구될 경우 PagingAndSortingRepository를 확장하여 사용합니다.


public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
    // 페이징 및 정렬을 포함한 기본 메서드 자동 제공
}

메서드 이름 기반 쿼리 생성

Spring Data JPA의 강력한 기능 중 하나는 메서드 이름을 파싱하여 JPQL 쿼리를 자동으로 생성하는 것입니다. find...By, read...By, count...By 등의 접두사를 사용하며, And, Or, Between, LessThan 등의 키워드를 조합하여 복잡한 조건도 손쉽게 표현할 수 있습니다.


public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    
    // 다중 조건 및 대소문자 무시
    List<Employee> findByDepartmentAndRoleIgnoreCase(String department, String role);
    
    // 중복 제거 및 정렬
    List<Employee> findDistinctByStatusOrderByHireDateDesc(String status);
    
    // 결과 개수 제한
    Optional<Employee> findTopByDepartmentOrderBySalaryDesc(String department);
}

사용자 정의 Repository 구현 통합

메서드 이름 파싱이나 @Query 애너테이션으로 해결하기 어려운 복잡한 비즈니스 로직은 사용자 정의 인터페이스와 구현체를 통해 추가할 수 있습니다. 구현체 클래스명은 반드시 인터페이스명에 Impl 접미사를 붙여야 Spring Data가 이를 자동으로 감지하여 프록시에 통합합니다.


// 1. 사용자 정의 인터페이스
public interface CustomEmployeeRepository {
    void bulkUpdateSalaries(String department, BigDecimal increment);
}

// 2. 구현체 (Impl 접미사 필수)
@Repository
public class CustomEmployeeRepositoryImpl implements CustomEmployeeRepository {
    
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public void bulkUpdateSalaries(String department, BigDecimal increment) {
        String jpql = "UPDATE Employee e SET e.salary = e.salary + :inc WHERE e.department = :dept";
        entityManager.createQuery(jpql)
            .setParameter("inc", increment)
            .setParameter("dept", department)
            .executeUpdate();
    }
}

// 3. 기본 Repository와 통합
public interface EmployeeRepository extends JpaRepository<Employee, Long>, CustomEmployeeRepository {
}

인터페이스 기반 프로젝션 (Projections)

엔티티의 모든 필드 대신 특정 속성만 조회하여 네트워크 및 메모리 오버헤드를 줄이고 싶을 때 프로젝션을 사용합니다. 인터페이스를 정의하고 getter 메서드를 선언하면 Spring Data가 런타임에 동적으로 프록시 객체를 생성하여 매핑합니다. @Value 애너테이션을 사용하면 SpEL을 통해 가상 필드를 계산할 수도 있습니다.


public interface EmployeeSummary {
    String getName();
    String getDepartment();
    
    // SpEL을 활용한 Open Projection
    @Value("#{target.name + ' [' + target.department + ']'}")
    String getDisplayName();
}

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    List<EmployeeSummary> findSummaryByStatus(String status);
}

Specification을 통한 동적 쿼리 조합

검색 조건이 동적으로 변하는 복잡한 화면에서는 JpaSpecificationExecutor를 사용하여 JPA Criteria API를 객체 지향적으로 추상화한 Specification을 활용합니다. 이를 통해 재사용 가능한 predicates를 조합할 수 있습니다.


public class EmployeeSpecifications {
    
    public static Specification<Employee> hasDepartment(String dept) {
        return (root, query, cb) -> 
            (dept == null) ? cb.conjunction() : cb.equal(root.get("department"), dept);
    }

    public static Specification<Employee> hiredAfter(LocalDate date) {
        return (root, query, cb) -> 
            (date == null) ? cb.conjunction() : cb.greaterThanOrEqualTo(root.get("hireDate"), date);
    }
}

// 서비스 계층에서의 활용
List<Employee> result = employeeRepository.findAll(
    Specification.where(EmployeeSpecifications.hasDepartment("IT"))
                 .and(EmployeeSpecifications.hiredAfter(LocalDate.of(2020, 1, 1)))
);

감사(Auditing) 기능 자동화

도메인 객체의 생성 및 수정 시점과 주체를 자동으로 기록하려면 JPA 엔티티 리스너와 Spring Data의 감사 기능을 결합합니다. 엔티티 클래스에 @EntityListeners(AuditingEntityListener.class)를 추가하고, 필드에 @CreatedBy, @CreatedDate 등의 애너테이션을 적용합니다.


@Entity
@EntityListeners(AuditingEntityListener.class)
public class BoardArticle {
    
    @Id @GeneratedValue
    private Long id;

    @CreatedBy
    private String author;

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

설정 클래스에 @EnableJpaAuditing을 선언하여 기능을 활성화하며, 현재 사용자를 주입하기 위해 AuditorAware 인터페이스를 구현하여 Bean으로 등록해야 합니다.

트랜잭션 및 비관적 락(Pessimistic Lock) 관리

Spring Data JPA의 조회 메서드는 기본적으로 readOnly = true 트랜잭션으로 실행되어 성능을 최적화합니다. 하지만 데이터의 무결성을 위해 동시성을 제어해야 하는 상황에서는 @Lock 애너테이션을 사용하여 비관적 락을 걸 수 있습니다.


public interface AccountRepository extends JpaRepository<Account, Long> {
    
    // 비관적 락을 적용한 조회
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT a FROM Account a WHERE a.id = :id")
    Optional<Account> findByIdWithLock(@Param("id") Long id);
    
    // 벌크 수정 연산 (반드시 @Modifying과 @Transactional 필요)
    @Modifying(clearAutomatically = true)
    @Transactional
    @Query("UPDATE Account a SET a.balance = a.balance - :amount WHERE a.id = :id")
    int deductBalance(@Param("id") Long id, @Param("amount") BigDecimal amount);
}

태그: SpringDataJPA jpa hibernate SpringFramework ORM

8월 1일 09:45에 게시됨