스프링 데이터 JPA
12.1 스프링 데이터 JPA 소개
스프링 데이터 JPA
> 프레임워크에서 JPA를 편리하게 사용할수 있도록 지원하는 프로젝트이다.
> 데이터 접근 계층을 개발할 때 지루하게 반복되는 CRUD 문제를 공통 인터페이스로 제공
> 인터페이스만 작성하면 JPA가 어플리케이션 실행 시점에 구현 객체를 생성해서 주입해줌
> 직접 작성한 메소드, 스프링 데이터 jpa가 메소드 이름을 분석해서 jpql을 실행해준다.
12.1.1 스프링 데이터 프로젝트
- 스프링 데이터 프로젝트 : 다양한 데이터 저장소 접근을 추상화하하여 개발 편의를 제공하는 프로젝트
- 스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트중 하나이다.
12.2 스프링 데이터 JPA 설정
1) mavan 라이브러리 추가
> spring-data-commons 에 의존함으로 함께 다운로드 받아진다.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.9.6.RELEASE</version>
</dependency>
2) base-package 의 하위 패키지를 검색하도록 설정.
- base-package 에서 JPA 인터페이스를 구현한 클래스를 동적으로 생성후, 스프릉 빈으로 등록해준다.
<jpa:repositories base-package="jpabook.jpashop.repository" />
12.3 공통 인터페이스 기능 제공
스프링 데이터 프로젝트
public interface Repository<T, ID> {}
public interface CrudRepository<T, ID> extends Repository<T, ID> {}
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {}
스프링 데이터 JPA 프로젝트
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>{}
#JpaRepositry 인터페이스 상속시, 사용할수 있는 메소드 (SimpleJpaRepository 가 구현해줌)
- save : 새로운 엔티티 저장, 이미 있으면 수정
- delete : 엔티티 삭제, EntityManager.remove() 호출
- findOne : 엔티티 하나 조회, EntityManager.find()
- getOne : 엔티티 프록시 조회, EntityManager.getReference() - 영
- findAll : 모든 엔티티 조회, 정렬/페이징 기능 제공
12.4 쿼리 메소드 기능 제공
스프링 데이터 JPA가 제공하는 쿼리 메소드 기능 3가지
1. 메소드 이름으로 쿼리생성
> JPA에서 제공하는 쿼리 생성 기능이 있다.(예약어 같이 조건절을 줄수 있음)
ex) findByAgesLessThan -------------------- where x.age < ?
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByName(String name);
}
/* 결과 */
select
member0_.MEMBER_ID as MEMBER_I1_4_,
member0_.city as city2_4_,
member0_.street as street3_4_,
member0_.zipcode as zipcode4_4_,
member0_.name as name5_4_
from
Member member0_
where
member0_.name=?
2. 메소드 이름으로 JPA NamedQuery 호출
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.name = :name"
)
public class Member {}
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByName(String name);
List<Member> findByUsername(@Param("name") String name);
}
- JPA는 선언한 '도메인 클래스.+메소드 이름' 으로 naemd쿼리를 찾는다.
/* named HQL query Member.findByUsername */ select
member0_.MEMBER_ID as MEMBER_I1_4_,
member0_.city as city2_4_,
member0_.street as street3_4_,
member0_.zipcode as zipcode4_4_,
member0_.name as name5_4_
from
Member member0_
where
member0_.name=?
3. @Query 어노테이션 인터페이스 쿼리 직접 정의
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByName(String name);
List<Member> findByUsername(@Param("name") String name);
//jpql 파라미터 바인딩 1부터 시작(위치기반 바인딩)
@Query(value = "select m from Member m where m.name = ?1")
Member findByUserNameJpql(String name);
//jpql 파라미터 바인딩 native 쿼리, 0부터 시작(위치기반 바인딩)
@Query(value = "SELECT * FROM MEMBER HWERE USERNAME = ?0", nativeQuery = true)
Member findByUserNameNative(String name);
//jpql 파라미터 바인딩
@Query(value = "select m from Member m where m.name = :name")
Member fineByUserNameParamBinding(String name);
}
4. 파라미터 바인딩
스프링 데이터 jpa는 위치기반 파라미터 바인딩, 이름 기반 바인딩 모두 지원한다.(3번 참조)
@Query(value = "select m from Member m where m.name = :name")
Member fineByUserNameParamBinding(String name);
5. 벌크성 수정 쿼리
- @Modifying : 벌크성 수정, 삭제 쿼리 사용하는 어노테이션
@Modifying
//@Modifying(clearAutomatically = true) 영속성 컨텍스트 초기화 default 는 false;
@Query(value = "update Member m set m.name= :name") //member 로 사용하면 native로 인식함
int bulkUpdate(@Param("name") String name);
6. 반환 타입
스프링 데이터 JPA는 유연한 반환 타입 지원
- 컬렉션
> 결과 O : 컬렉션에 담아옴
> 결과 X : 빈 컬렉션 반환(empty)
List<Member> findByName(String name);
- 단건
> 결과 O : 단건 반환
> 결과 X : null 반환
> 결과 2개이상 : 오류 발생(non unique exception)
Member findByEmail(String email);
7. 페이징/정렬
스프링 데이터 JPA는 페이징과 정렬 기능을 사용할수 있도록 sort, Pageable 이라는 파라미터를 제공
//repo 정의
public interface MemberRepository extends JpaRepository<Member, Long> {
Page<Member> findByNameStartingWith(String name, Pageable pageable);
}
public List<Member> selectPageMemberList(){
/**
페이징 조건 명시
> 10건
> 이름 내림차순
> 이름이 '김'으로 시작하는 회원
**/
PageRequest pageRequest = new PageRequest(0,10,new Sort(Sort.Direction.DESC,"name"));
Page<Member> result = memberRepository.findByNameStartingWith("김", pageRequest);
List<Member> members = result.getContent(); //조회된 데이터
int totalPages = result.getTotalPages(); //전체페이지 수
boolean hasNextPage = result.hasNext(); //다음 페이지 존재 여부
return members;
}
8. 힌트
- @QueryHints 어노테이션을 이용하여, JPA쿼리 힌트를 사용한다.
> 실제 SQL힌트가 아닌 JPA구현체에 제공하는 힌트다.
public interface MemberRepository extends JpaRepository<Member, Long> {
/**
forCounting = true : 추가로 호출하는 페이징도 쿼리힌트를 적용할지 설정
readonly : 읽기전용으로, 영속성컨텍스트에 스냅샷을 저장하지 않는다.
**/
@QueryHints(value = {
@QueryHint(name = "org.hibernate.readOnly",value = "true")
}, forCounting = true)
Page<Member> findByName(String name, Pageable pageable);
}
9. lock
- JPA에서 제공하는 락
- @Lock(LockModeType.PESSIMISTIC_WRITE)
> SELECT FOR ~ UPDATE
> 일반적인 옵션. 데이터베이스에 쓰기 락
> 다른 트랜잭션에서 읽기도 쓰기도 못함. (배타적 잠금)
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockMemberByName(String name);
12.5 명세
- JPA는 JpaSpecificationExecutor 상속받아 다양한 검색조건을 조립해서 새로운 검색조건을 쉽게 만들 수 있다.
> where(), and(), or(), not() 메소드를 제공
> JPA Criteria로 이개념을 사용할 수 있도록 지원
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order>, CustomOrderRepository {
}
//service, toSpecification 메소드 호출!
public List<Order> findOrders(OrderSearch orderSearch) {
return orderRepository.findAll(orderSearch.toSpecification()); // Specification 사용
}
//OrderSearch.java
public Specifications<Order> toSpecification() {
return where(memberNameLike(memberName))
.and(orderStatusEq(orderStatus));
}
public class OrderSpec {
public static Specification<Order> memberNameLike(final String memberName) {
return new Specification<Order>() {
public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (StringUtils.isEmpty(memberName)) return null;
Join<Order, Member> m = root.join("member", JoinType.INNER); //회원과 조인
return builder.like(m.<String>get("name"), "%" + memberName + "%");
}
};
}
public static Specification<Order> orderStatusEq(final OrderStatus orderStatus) {
return new Specification<Order>() {
public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (orderStatus == null) return null;
return builder.equal(root.get("status"), orderStatus);
}
};
}
}
12.6 사용자 정의 리포지토리 구현
- JpaRepository 인터페이스에서 제공하는 않는 경우 사용자가 직접 레포지토리를 구현할수 있는 방법을 제공한다.
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}
public interface MemberRepositoryCustom {
public List<Member> findMemberCustom();
}
public class MemberRepositoryImpl implements MemberRepositoryCustom {
@Override
public List<Member> findMemberCustom() {
//querydsl 등 구현 하여 return
return new ArrayList<Member>();
}
}
12.7 WEB 확장
- 스프링 데이터 프로젝트는 스프링 MVC에서 사용할수 있는 기능을 제공한다.
1) 도메인 클래스 컨버터 기능
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration"></bean>
@RequestMapping(value = "member/memberUpdateForm",method = RequestMethod.POST)
@ResponseBody
public String memberUpdateForm(@RequestParam("id") Long id, Model model){
Member member = memberRepository.findOne(id);
return member.toString();
}
@RequestMapping(value = "member/memberUpdateForm",method = RequestMethod.POST)
@ResponseBody
public String memberUpdateForm(@RequestParam("id") Member member,Model model){
return member.toString();
}
- 넘어온 파라미터 id를 바로 Member 에 넣어줌
2) 페이징/정렬 기능
- 요청파라미터
> page (현재 페이지) , size (한 페이지 건수), sort (정렬조건)
> default 로 page 0, size : 20 이다.
12.8 스프링 데이터 JPA가 사용하는 구현체
- 스프링 데이터 JPA 공통 인터페이스는 SimpleJpaRepository 클래스가 구현함.
@Repository
@Transactional(
readOnly = true
)
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
....
@Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
}
12.10 스프링 데이터 JPA와 QueryDSL 통합
- QueryDsl 사용 2가지 방법, 두가지 인터페이스 상속
1) QueryDslPredicateExecutor : 기능에 한계가 있다.(join, fetch 사용 불가)
2) QueryDslRepositorySupport : QueryDsL의 모든기능을 사용가능
- 레파지토리
public interface OrderRepository extends JpaRepository<Order, Long>, CustomOrderRepository {
}
- 사용자 커스텀 레파지토리
public interface CustomOrderRepository {
public List<Order> search(OrderSearch orderSearch);
}
- 사용자 레파지토리 구현 상세
public class OrderRepositoryImpl extends QueryDslRepositorySupport implements CustomOrderRepository {
public OrderRepositoryImpl() {
super(Order.class);
}
@Override
public List<Order> search(OrderSearch orderSearch) {
QOrder order = QOrder.order;
QMember member = QMember.member;
JPQLQuery query = from(order);
if (StringUtils.hasText(orderSearch.getMemberName())) {
query.leftJoin(order.member, member)
.where(member.name.contains(orderSearch.getMemberName()));
}
if (orderSearch.getOrderStatus() != null) {
query.where(order.status.eq(orderSearch.getOrderStatus()));
}
return query.list(order);
}
}