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.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하는 경우 코드가 훨씬 간결해진다.
- 규칙
- 조회 : find...By, read...By, query...By, get...By
- 카운트 : count...By | returnType : long
- 존재여부 : exists...By | returnType : boolean
- 삭제 : delete...By, remove...By | returnType : long
- Distinct : findDistinct, findMemberDistinctBy
- Limit : findFirst3, findFirst, findTop, findTop3
- 추가 조건 공식 문서
- 쿼리 메서드 기능 대신에 직접 JPQL을 사용하고 싶을 때는 @Query와 함께 JPQL을 작성하면 된다. 이 때 메서드 이름으로 실행하는 규칙은 무시된다.
- JPA의 네이티브 쿼리 기능도 지원하는데, JPQL 대신에 SQL을 직접 작성할 수 있다.
// 순수 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 이용할 것
- 추가 작성 메서드
- 모든 데이터 조회 (spring-data-jpa 메서드 이용) - 쿼리 메서드
- 이름 조회 - 쿼리 메서드
- 가격 조회 - 쿼리 메서드
- 이름 + 가격 조회 - @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
- repository.save(item) - em.persist(item)
- repository.findById(itemId).orElseThrow() - em.find(Item.class, itemId)
- 동적쿼리 빡세서 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();
}
}
}