framework/spring

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

wooweee 2023. 9. 29. 20:00
728x90

스프링 데이터 JPA  소개 

  • spring Data는 interface이고 공통 기능만 존재 - crud+쿼리 , 페이징처리, 등등 존재
  • 해당 구현체로 spring data jpa, spring data mongoDB, spring data ... 등이 존재
  • 결론은 spring data JPA를 잘 사용하기 위해서는 spring, db, 하이버네이트, jpa를 먼저 확실히 알아서 그걸 조금 편하게 해주는 것이 spring data JPA라는 것임을 잊지 말자

 

1. 스프링 데이터 JPA 주요 기능

  • 스프링 데이터는 JPA를 편리하게 사용할 수 있도록 도와주는 라이브러리이다.
  • 수많은 편리한 기능을 제공하지만 가장 대표적인 기능은
    1. 공통 인터페이스 기능
    2. 쿼리 메서드 기능

 

1.1. 공통 인터페이스 기능

 

  • JpaRepository 인터페이스를 통해서 기본적인 CRUD 제공
  • 공통화 가능한 기능이 거의 모두 포함
  • * CrudRepository Interface에서 findOne() -> findById()로 변경됨

 

  • 적용 
    • JpaRepository 인터페이슬 인터페이스 상속(구현 X) 받고, 제네릭에 관리할 <엔티티, 엔티티ID(pk type)>를 주면 된다.
    • JpaRepository의 crud 사용 가능
package hello.itemservice.repository.jpa;

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

 

  • JpaRepository 인터페이슴나 상속받았지만 spring data JPA가 프록시 기술을 사용해서 구현 클래스를 만들고 만들어진 구현 clss를 @Bean으로 등록해준다.

 

1.2. 쿼리 메서드 기능

  • springDataJPA는 인터페이스에 메서드만 적어두면, 메서드 이름을 분석해서 쿼리를 자동으로 만들고 실행해주는 기능 제공
  • insert하는 경우 코드가 훨씬 간결해진다.
  • 규칙

 

// 순수 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(); 
}

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

 

package hello.itemservice.repository.jpa;

public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
    // JpaRepository<> : entity를 관리하는 repository 의미로 제너릭스에 entity, pk type이 들어간다.

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

    // 아래 메서드와 같은 기능 수행
    List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);
    
    // 쿼리 직접 실행 - jsql 사용
    @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);
}

 

2. 스프링 데이터 JPA 적용1

 

2.1. build.gradle

  • 해당 의존 관계를 넣으면 된다.
  • 이전 챕터 JPA 실습때와 동일한 의존 관계로 해당 의존 관계에 JPA, 하이버네이트, spring-data-jpa, spring-jdbc 기능이 모두 포함되어있다.
  • 하이버네이트 5.6.6 ~ 5.6.7 사용시 like 문장 사용할 때 버그성 예외가 발생하기 때문에 버전 문제가 없는 5.6.5. Final로 변경함
plugins {
    id 'org.springframework.boot' version '2.6.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

// hibernate.version 버그로 인한 version downgrade 방법
ext["hibernate.version"] = "5.6.5.Final"

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    //테스트에서 lombok 사용
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'

    // JdbcTemplate 추가
    // mybatis나 jpa 사용시, 해당 패키지 내부에 jdbcTemplate이 들어가있어서 주석처리
    // implementation 'org.springframework.boot:spring-boot-starter-jdbc'

    // MyBatis 추가 - 김영한 강사님 file을 그대로 사용한 것이여서 2.2.0으로 작성해야한다.
    // boot 3.0 이상 사용시 version을 3.0.1로 변경 해야한다.
   implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'

    // JPA 추가
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // H2 데이터베이스 추가
    runtimeOnly 'com.h2database:h2'

    //Querydsl 추가
    implementation 'com.querydsl:querydsl-jpa'
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

tasks.named('test') {
    useJUnitPlatform()
}

// Querydsl 추가, 자동 생성된 Q클래스를 gradle clean으로 제거 
clean {
    delete file('src/main/generated')
}

 

2.2. SpringDataJpaItemRepository

  • 동적 쿼리 관련된 method만 추가 정의 - 나머지 crud는 spring-data-jpa가 제공해주는 method 이용할 것
  • 추가 작성 메서드
    1. 모든 데이터 조회 (spring-data-jpa 메서드 이용) - 쿼리 메서드
    2. 이름 조회 - 쿼리 메서드
    3. 가격 조회 - 쿼리 메서드
    4. 이름 + 가격 조회 - @Query(jpql) 여기에 들어가는 params에는 @Params를 꼭 명시해야함
  • spring-data-jpa 또한 동적 쿼리에 약하기 때문에 4가지 경우의 수를 일일히 만들어서 개별 메서드로 작동하도록 함

 

  • 인터페이스
package hello.itemservice.repository.jpa;

import hello.itemservice.domain.Item;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; // 잘 확인 필요

import java.util.List;

public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
    // JpaRepository<> : entity를 관리하는 repository 의미로 제너릭스에 entity, pk type이 들어간다.

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

    // 아래 메서드와 같은 기능 수행
    List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);

    // 쿼리 직접 실행 - jsql 사용
    @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); // @Parmas 패키지는 repository꺼 사용!! - ibatis 사용하면 에러 발생!!

}

 

3. 스프링 데이터 JPA 적용2

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

  • ItemService가 SpringDataJpaItemRepository를 직접 사용하도록 코드를 고쳐도 되지만 DI를 유지하기 위해서 새 repository를 만들어서 조금 복잡하게 되었다.

  • 사용한 springData method
    1. repository.save(item)  -  em.persist(item)
    2. repository.findById(itemId).orElseThrow()   -  em.find(Item.class, itemId)
    3. 동적쿼리 빡세서 if문 나열한 검색 기능
package hello.itemservice.repository.jpa;

@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.setPrice(updateParam.getPrice());
        findItem.setQuantity(updateParam.getQuantity());
    }

    @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.findByItemNameLikeAndPriceLessThanEqual("%" + itemName + "%", maxPrice);
            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();
        }
    }
}