데이터 접근계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있다
스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트 중 하나이며 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자 편의를 제공하고 지루하게 반복하는 데이터 접근 코드도 줄여줌
// gradle
org.springframework.boot:spring-boot-starter-data-jpa
// JavaConfig 설정
@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig {}
어플리케이션을 실행할 때 basePackage에 있는 리포지토리 인터페이스들을 찾아서 해당 인터페이스를 구현한 클래스를 동적으로 생성한 다음 스프링 빈으로 등록
간단한 CRUD 기능을 공통으로 처리하는 JpaRepository
인터페이스를 제공
public interface MemberRepository extends JpaRepository<Member, Long> {}
메소드만 선언하면 해당 메소드의 이름으로 적절한 JPQL 쿼리를 생성해서 실행한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByEmailAndName(String email, String name);
}
// 실행 SQL
SELECT m FROM Member m where m.email = ?1 and m.name = ?2
참고 자료: http://docs.spring.io/spring-data/jpa/docs/1.8.0.RELEASE/reference/
쿼리에 이름을 부여해서 사용하는 방법
// 정의
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username")
public class Member {...}
// 사용
public class MemberRepository {
public List<Member> findByUsername (String username) {
...
List<Member resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "종욱")
.getResultList();
}
}
// 정의 된 네임드 쿼리 사용
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(@Param("username") String username);
}
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = ?1")
Member findByUsername(String username);
}
// 네이티브 쿼리의 경우 아래와 같으며 파라미터 바인딩이 0부터 시작
@Query("SELECT * FROM MEMBER WHERE USERNAME = ?0")
위치기반, 이름기반 파라미터 바인당 모두 지원
가독성과 유지보수를 위해 이름기반 파라미터 바인딩 사용 권장
int bulkPriceUp(String stockAmount) {
...
String qlString =
"update Product p set p.price = p.price * 1.1 where
p.stockAmount < :stockAmount";
int resultCount = em.createQuery(qlString)
.setParameter("stockAmount", stockAmount)
.executeUpdate();
}
@Modifying
@Query("update Product p set p.price = p.price * 1.1 where p.stockAmount < :stockAmount")
int bulkPriceUp(@Param("stockAmount") String stockAmount);
벌크성 쿼리 실행 후 영속성 컨텍스트를 초기화 하고싶다면
@Modifying(clearAutomatically = true)
를 해주면 된다.
단건조회는 예외를 무시하고 null
을 반환한다.
org.springframework.data.domain.Sort
: 정렬
org.springframework.data.domain.Pageable
: 페이징
// count 쿼리를 추가로 호출
Page<Member> findByName(String name, Pageable pageable);
// count 쿼리 미호출
List<Member> findByName(String name, Pageable pageable);
List<Member> findByName(String name, Sort sort);
@QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly", value = "true")},
forCounting = true)
Page<Member> findByName(String name, Pageable pageable);
JPA에 제공하는 힌트
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByName(String name);
JpaSpecificationExcutor
의 메소드들은 Specification
을 파라미터로 받아서 검색 조건으로 사용
public List<Order> findOrders(String name) {
List<Order> result = orderRepository.findAll(
where(memberName(name)).and(isOrderStatus())
);
return result;
}
Specifications
는 명세들을 조립할 수 있도록 도와주는 클래스
where(), or(), and(), not() 메소드를 제공
public interface MemberRepositoryExtension {
public List<Member> findMemberExtension();
}
public class MemberRepositoryImpl implements MemberRepositoryExtension {
@Override
public List<Member> findMemberExtension() {
...
}
}
public interface MemberRepository extends JpaRepository<Member, Long>
,MemberRepositoryExtension {};
위와 같이 레포지토리 인터페이스에서 사용자 정의 인터페이스를 상속받으면 된다
org.springframework.boot:spring-boot-starter-web
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
public class WebAppConfig {
...
}
설정을 완료하면 도메인 클래스 컨버터와 페이징과 정렬을 위한 HandlerMethodArgumentResolver
가 스프링 빈으로 등록된다
도메인 클래스 컨버터는 HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩해준다.
@RequestMapping("member/memberUpdateForm") // 아래와 같이 'id'로 멤버를 바로 찾아줌
public String memberUpdateForm(@RequestParam("id") **Member member**, Model model) {
...
}
컨버터가 중간에 동작해서 아이디를 회원 엔티티 객체로 변환해서 넘겨준다.
와.. 개꿀
// 사용해야 할 페이징 정보가 둘 이상
public String list (
@Qualifier("member") Pageable memberPageable,
@Qualifier("order") Pageable orderPageable
)
// Pageable 의 기본값 변경
@RequestMapping(value = "/members_page", method = RequestMethod.GET)
public String list(@PageableDefault(size = 12, sort = "name",
direction = Sort.Direction.DESC) Pageable pageable) {
...
}
2가지 방법으로 QueryDSL을 지원
org.springframework.data.querydsl.QueryDslPredicateExcutor
org.springframework.data.querydsl.QueryDslRepositorySupport
public interface ItemRepository extends JpaRepository<Item, Long>,
QueryDslPredicateExecutor<Item> { }
위 QueryDslPredicateExecutor
는 편리하게 QueryDSL을 사용할 수 있지만 기능에 한계가 존재
join, fetch등을 사용할 수 없다 (JPQL에서 이야기하는 묵시적 조인은 사용가능)
이를 사용하려면 JPAQuery 혹은 QueryDslRepositorySupport
를 사용해야 한다.
package org.springframework.data.jpa.repository.support;
@Repository
public abstract class QueryDslRepositorySupport {
// 엔티티 매니저 반환
protected EntityManager getEntityManager() {
return entityManager;
}
// from 절 반환
protected JPQLQuery from(EntityPath<?> ... paths) {
return querydsl.createQuery(paths);
}
// QueryDSL delete 절 바환
protected DeleteClause<JPADeleteClause> delete(EntityPath<?> path) {
return new JPADeleteClause(entityManager, path);
}
// QueryDSL update 절 반환
protected UpdateClause<JPAUpdateClause> update(EntityPath<?> path) {
return new JPAUpdateClause(entityManager, path);
}
// 스프링 데이터 JPA가 제공하는 Querydsl을 편하게 사용하도록 돕는 헬퍼 객체 반환
protected Querydsl getQuerydsl() {
return this.querydsl;
}
}