공부/Spring

데이터 접근 기술 - 스프링 데이터 JPA

Stair 2025. 6. 5. 20:39
반응형

스프링 데이터 JPA 주요 기능

스프링 데이터 JPA는 JPA를 편리하게 사용할 수 있도록 도와주는 라이브러리이다.

수 많은 편리한 기능을 제공하지만 가장 대표적인 기능은 다음과 같다.

 

- 공통 인터페이스 기능

- 쿼리 메서드 기능

 

공통 인터페이스 기능

- JpaRepository 인터페이스를 통해서 기본적인 CRUD 기능을 제공한다.

- 공통화 가능한 기능이 거의 모두 포함되어 있다.

- CurdRepository에서 findOne -> findById()로 변경되었다.

 

JpaRepository 사용법

public interface ItemRepository extends JpaRepository<Item, Long> {
}

- JpaRepository 인터페이스를 인터페이스 상속받고, 제네릭에 관리할 <엔티티, 엔티티ID>를 주면 된다.

- 그러면 JpaRepository가 제공하는 기본 CRUD 기능을 모두 사용할 수 있다.

 

- JpaRepository 인터페이스만 상속받으면 스프링 데이터 JPA가 프록시 기술을 사용해서 구현 클래스를 만들어준다. 그리고 만든 구현 클래스의 인스턴스를 만들어서 스프링 빈으로 등록한다.

- 따라서 개발자는 구현 클래스 없이 인터페이스만 만들면 기본 CRUD 기능을 사용할 수 있다.

 

 

 

쿼리 메서드 기능

스프링 데이터 JPA는 인터페이스에 메서드만 적어두면, 메서드 이름을 분석해서 쿼리를 자동으로 만들고 실행해주는 기능을 제공한다.

 

순수 JPA 리포지토리

public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
    return em.createQuery("select m from Member m where m.username = :username 
            and m.age > :age")
            .setParameter("username", username)
            .setParameter("age", age) .getResultList();
}

순수 JPA를 사용하면 직접 JPQL을 작성하고, 파라미터도 직접 바인딩 해야한다.

 

스프링 데이터 JPA

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}

- 스프링 데이터 JPA는 메서드 이름을 분석해서 필요한 JPQL을 만들고 실행해준다. 물론 JPQL은 JPA가 SQL로 번역해서 실행한다.

- 그냥 아무 이름이나 사용하는 것은 아니고 다음과 같은 규칙을 따라야 한다.

 

스프링 데이터 JPA가 제공하는 쿼리 메서드 기능

- 조회: find…By , read…By , query…By , get…By
    - 예:) findHelloBy 처럼 ...에 식별하기 위한 내용(설명)이 들어가도 된다.
- COUNT: count…By 반환타입 long
- EXISTS: exists…By 반환타입 boolean
- 삭제: delete…By , remove…By 반환타입 long
- DISTINCT: findDistinct , findMemberDistinctBy
- LIMIT: findFirst3 , findFirst , findTop , findTop3

 

public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
    //쿼리 메서드 기능
    List<Item> findByItemNameLike(String itemName);
    //쿼리 직접 실행
    @Query("select i from Item i where i.itemName like :itemName and i.price 
            <= :price") List<Item> findItems(@Param("itemName") String itemName, @Param("price") 
            Integer price);
}

- 쿼리 메서드 기능 대신에 직접 JPQL을 사용하고 싶을 때는 @Query와 함께 JPQL을 작성하면 된다. 이때는 메서드 이름으로 실행하는 규칙은 무시된다.

- 참고로 스프링 데이터 JPA는 JPQL 뿐만 아니라 JPA의 네이티브 쿼리 기능도 지원하는데, JPQL 대신에 SQL을 직접 작성할 수 있다.

 

*중요

스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 도구이다. 따라서 JPA 자체를 잘 이해하는 것이 가장 중요하다.

 

 

 

스프링 데이터 JPA 적용1

스프링 데이터 JPA는 spring-boot-starter-data-jpa라는 라이브러리를 넣어주면 된다.

//JPA, 스프링 데이터 JPA 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

그런데 이미 앞에서 JPA를 설정하면서 spring-boot-starter-data-jpa 라이브러리를 넣어주었다.

여기에는 JPA, 하이버네이트, 스프링 데이터 JPA(spring-data-jpa), 그리고 스프링 JDBC 관련 기능도 모두 포함되어 있다.

따라서 스프링 데이터 JPA가 이미 추가되어 있으므로 별도의 라이브러리 설정은 하지 않아도 된다.(만약 없다면 추가해주자)

public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {

    List<Item> findByItemNameLike(String itemName);

    List<Item> findByPriceLessThanEqual(Integer price);

    //쿼리 메서드
    List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);

    //쿼리 직접 실행
    @Query("select i from Item i where i.itemName like :itemName and i.price <= :price")
    List<Item> findItems(@Param("itemName") String itemName, @Param("price") Integer price);



}

- 스프링 데이터 JPA가 제공하는 JpaRepository 인터페이스를 상속받으면 기본적인 CRUD 기능을 사용할 수 있다.

- 그런데 이름으로 검색하거나, 가격으로 검색하는 기능은 공통으로 제공할 수 있는 기능이 아니다. 따라서쿼리 메서드 기능을 사용하거나 @Query를 사용해서 직접 쿼리를 실행하면 된다.

 

여기서는 데이터를 조건에 따라 4가지로 분류해서 검색한다.

- 모든 데이터 조회

- 이름 조회

- 가격 조회

- 이름 + 가격 조회

 

동적 쿼리를 사용하면 좋겠지만, 스프링 데이터 JPA는 동적 쿼리에 약하다. 이번에는 직접 4가지 상황을 스프링 데이터 JPA로 구현해보자.

그리고 이 문제는 이후 Querydsl에서 동적 쿼리로 깔끔하게 해결한다.

 

findAll()

코드에는 보이지 않지만 JpaRepository 공통 인터페이스가 제공하는 기능이다.

모든 Item을 조회한다.

다음과 같은 JPQL이 실행된다.

select i from Item i

 

findByItemNameLike()

이름 조건만 검색했을 때 사용하는 쿼리 메서드이다

다음과 같은 JPQL이 실행된다.

select i from Item i where i.name like ?

 

findByPriceLessThanEqual()

가격 조건만 검색했을 때 사용하는 쿼리 메서드이다.

다음과 같은 JPQL이 실행된다.

select i from Item i where i.price <= ?

 

findByItemNameLikeAndPriceLessThanEqual()

이름과 가격 조건을 검색했을 때 사용하는 쿼리 메서드이다.

다음과 같은 JPQL이 실행된다.

select i from Item i where i.itemName like ? and i.price <= ?

 

findItem()

메서드 이름으로 쿼리를 실행하는 기능은 다음과 같은 단점이 있다.

1. 조건이 많으면 메서드 이름이 너무 길어진다.

2. 조인 같은 복잡한 조건을 사용할 수 없다.

 

메서드 이름으로 쿼리를 실행하는 기능은 간단한 경우에는 매우 유용하지만, 복잡해지면 직접 JPQL 쿼리를 작성하는 것이 좋다.

 

- 쿼리를 직접 실행하려면 @Query 애노테이션을 사용하면 된다.

- 메서드 이름으로 쿼리를 실행할 때는 파라미터를 순서대로 입력하면 되지만, 쿼리를 직접 실행할때는 파라미터를 명시적으로 바인딩 해야한다.

- 파라미터 바인딩은 @Param("itemName") 애노테이션을 사용하고, 애노테이션 값에 파라미터 이름을 주면 된다.

 

 

 

스프링 데이터 JPA 적용2

@Repository
@Transactional
@RequiredArgsConstructor
public class JpaItemRepositoryV2 implements ItemRepository {

    private final SpringDataJpaItemRepository repository;

    @Override
    public Item save(Item item) {
        return repository.save(item);
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        Item findItem = repository.findById(itemId).orElseThrow();
        findItem.setItemName(updateParam.getItemName());
        findItem.setQuantity(updateParam.getQuantity());
        findItem.setPrice(updateParam.getPrice());
    }

    @Override
    public Optional<Item> findById(Long id) {
        return repository.findById(id);
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();

        if (StringUtils.hasText(itemName) && maxPrice != null) {
            return repository.findItems("%" + itemName + "%", maxPrice);
        } else if (StringUtils.hasText(itemName)) {
            return repository.findByItemNameLike("%" + itemName +"%");
        } else if (maxPrice != null) {
            return repository.findByPriceLessThanEqual(maxPrice);
        } else {
            return repository.findAll();
        }

    }
}

 

의존관계와 구조

- ItemService는 ItemRepository에 의존하기 때문에 ItemService에서 SpringDataJpaItemRepository를 그대로 사용할 수 없다.

- 물론 ItemService가 SpringDataJpaItemRepository를 직접 사용하도록 코드를 코치면 되겠지만, 우리는 ItemService 코드의 변경 없이 ItemService가 ItemRepository에 대한 의존을 유지하면서 DI를 통해 구현 기술을 변경하고 싶다.

 

조금 복잡하지만, 새로운 리포지토리를 만들어 이 문제를 해결한다.

- JpaItemRepositoryV2는 ItemRepository를 구현한다. 그리고 SpringDataJpaItemRepository를 사용한다.

 

- 런타임의 객체 의존관계는 다음과 같이 동작한다.

- itemService -> jpaItemRepositoryV2 -> springDataJpaItemRepository(프록시 객체)

 

이러호게 중간에서 JpaItemRepository가 어댑터 역할을 해주었기때문에 ItemService가 사용하는 itemRepository 인터페이스를 그대로 유지할 수 있고, 클라이언트인 ItemService의 코드를 변경하지 않아도 되는 장점이 있다.

 

기능을 개발하였으니 설정하고, 실행하여보자.

@Configuration
@RequiredArgsConstructor
public class SpringDataJpaConfig {

    private final SpringDataJpaItemRepository springDataJpaItemRepository;

    @Bean
    public ItemService itemService() {
        return new ItemServiceV1(itemRepository());
    }

    @Bean
    public ItemRepository itemRepository() {
        return new JpaItemRepositoryV2(springDataJpaItemRepository);
    }
}
@Import(SpringDataJpaConfig.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {

}

 

 

정리

스프링 데이터 JPA의 대표적인 기능을 확인하였다. 스프링 데이터 JPA는 이 외에도 정말 수 많은 편리한 기능을 제공한다. 심지어 우리가 어렵게 사용하는 페이징을 위한 기능들도 제공한다. 스프링 데이터 JPA는 단순히 편리함을 넘어서 많은 개발자들이 똑같은 코드로 중복 개발하는 부분을 개선해준다.

반응형

'공부 > Spring' 카테고리의 다른 글

데이터 접근 기술 - 활용 방안  (0) 2025.06.10
데이터 접근 기술 - Querydsl  (2) 2025.06.09
데이터 접근 기술 - JPA  (2) 2025.06.05
데이터 접근 기술 - MyBatis  (1) 2025.06.04
데이터 접근 기술 - 테스트  (1) 2025.06.02