스프링이 제공하는 프로젝트이며 CRUD를 공통 인터페이스로 해결해준다.
구현체는 스프링 데이터 JPA가 생성해서 주입해줌으로 개발자가 직접 JPA 인터페이스를 구현하지 않아도 된다.
public interface MemberRepository extends JpaRepository<Member, Long> {}
JpaRepository 를 상속받고 사용할 수 있는 주요 메서드
T = 엔티티
ID = 엔티티의 식별자 타입
S = 엔티티와 그 자식
save(S) , 새로운 엔티티 저장하고 이미 있으면 수정
delete(T), 엔티티 하나 삭제
findOne(ID) 엔티티 하나 조회
getOne(Id) 엔티티를 프록시로 조회
findAll(...) 모든 엔티티 조회, 정렬이나 페이징 조건을 파라미터로 제공 가능 .
save()는 엔티티에 식별값이 없으면 새로운 엔티티로 판단해서 persist를 호출하고, 없다면 merge를 호출한다.
public interface MemberRepository extends Repository<Member ,Long> {
List<Member> findByEmailAndName(String email, String name);
}
findByEmailAndName메서드를 실행하면 스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL 을 생성하고 실행한다.
select m from Member m where m.email =?1 and m.name =?2
스프링 데이터 JPA 공식 문서가 제공하는 표의 규칙에 맞게 메소드 이름을 지으면 스프링 데이터 JPA가 알아서 JPQL 을 생성해준다.
키워드 | 예 | JQPL예 |
---|---|---|
And | findByLastnameAndFirstname | ...where x.lastname =?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | ...where x.lastname =?1 or x.firstname = ?2 |
Is, Equals | findByFirstname , findByFirstnamels, findByFirstnameEqauls | ...where x.firstname = ?1 |
Between | findByStartDateBetween | ...where x.startDate between 1? and 2? |
LessThan | findByAgeLessThan | ...where x.age < ?1 |
GreaterThan | findByAgeGreaterThan | ... where x.age >1 |
LessThanEqual | findByAgeLEssThanEqual | ...where x.age <= ?1 |
After | findByStartDateAfter | ...where x.startDate > ?1 |
Before | findByStartDateBefore | ...where x.startDate < ?1 |
IsNull | findByAgeIsNull | ...where x.age is null |
IsNotNull, NotNull | findByAge(is)NotNull | ...where x.age is not null |
Like | findByFirstnameLike | ...where x.firstname like = ?1 |
NotLike | findByFirstnameNotLike | ...where x.firstname like not = ?1 |
StartingWith | findByFirstnameStartingWith | ...where x.firstname like = ?1 (parameter bound with prepended %) |
EndingWith | findByFirstnameEndingWith | ...where x.firstname like = ?1 (parameter bound with prepended %) |
OrderBy | findByAgeOrderByLastnameDesc | ...where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | ...where x.lastname <> ?1 |
In | findByAgeIn(Collection age) | ...where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | ...where x.age not in ?1 |
True | findByActiveTrue | ...where x.active = true |
False | findByActiveFalse | ...where x.active = false |
메소드 이름으로 JPA NamedQuery 를 호출하는 기능을 제공함.
// @NamedQuery 에노테이션으로 네임드 쿼리 정의
@Entity
@NamedQuery(
name="Member.findByUsername"
query="select m from Member m where m.username = :username
)
public class Member {...}
// 스프링 데이터 JPA에서 네임드 쿼리 사용
public interface MemberRepository extends JpaRepository<Member, Long> { // 여기 선언한 Member 도메인 클래스
List<Member> findByUsername(@Param("username") String username );
}
스프링 데이터 JPA는 선언한 "도메인 클래스 + .(점) . 메소드이름" 으로 NamedQuery를 찾아서 실행한다.
위의 예제는 Member.findByUsername
@Param는 이름 기반 파라미터를 바인딩할 때 사용하는 애노테이션
레포지토리에 메소드 직접 정의하려면 @Query 애노테이션 사용.
이름없는 네임드 쿼리라고도 불린다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = ?1")
Member findByusername(String username);
// 네이티브 SQL 사용하려면 nativeQuery= true를 사용한다. 참고로 네이티브 SQL은 위치기반 파라미터가 0부터 시작한다.
@Query(value ="SELECT * FROM MEMBER WHERE USERNAME = ?0 , nativeQuery= true) ;
Member findByUsernameForNative(@Param("username") String username );
}
스프링 데이터 JPA는 위치기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원한다.
기본값은 위치기반 파라미터 바인딩이다
이름 기반 파라미터 반인딩을 하려면
매개변수에 @Param 애노테이션을 붙여준다.
@Query("select m from Member m where m.username = :username")
Member findByUsernameForNative(@Param("username") String username );
@Modifying
@Query("update Product p set p.price = p.price * 1.1 where p.stockAmount < :stockAmount")
int bulkPriceUp(@Param("stockAmount") String stockAmount);
벌크성 수정 ,삭제 쿼리는 @Modifying 애노테이션을 사용하면 된다.
벌크성 쿼리를 실행 후 영속성 컨텍스트를 초기화 하고 싶으면 @Modifying(clearAutomatically = true) 옵션을 지정해주면된다.
스프링 데이터 JPA 는 유연한 반환타입 지원, 결과가 한 건 이상이면 컬렉션 인터페이스 사용하고, 단건이면 반한 타입을 지정.
만약 조히 결과가 없으면 컬렉션은 빈 컬렉션 반환, 단건은 null,
단 단건 조회 시 2개 이상 나오면 NonUniqueResultException 발생.
스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬 기능을 사용할 수 있도록 2가지 특별한 파라미터를 제공
Sort 형 파라미터 : 정렬기능
Pageable 형 파라미터 : 페이징기능( 내부 sort 포함)
Pageable 을 사용하면 반환타입으로 List or Page 타입을 받는다.
Page는 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출한다.
Page<Member> findByName(String name , Pageable pageable); // count 쿼리 사용
List<Member> findByName(String name , Pageable pageable); // count 쿼리 x
List<Member> findByName(String name , Sort sort);
검색조건 : 이름이 김으로 시작하는 회원
정렬조건 : 이름 내림차순
페이징 조건 : 첫번째 페이지, 페이지당 10건// 인터페이스 정의
public interface MemberRepository extends JpaRepository<Member , Long> {
Page findByNameStartWith(String name , Pageable pageable);
}
// 사용
PageRequest pageRequest = new PageRequest(0,10, new Sort(Direction.DESC, "name"));
Page result = memberRepository.findByNameStartWith("김" ,pageRequest);
List members = result.getContent(); // 조회된 데이터
int totalPage = result.getTotalPages(); // 전체 페이지 수
boolean hasNextPage = result.hasNextPage();
## 힌트
```java
@QueryHints(value ={ @QueryHint(name = "org.hibernate.readOnly", value="true")}, forCounting = true)
Page<Member> findByName(String name , Pageable pageable);
forCounting 속성은 반환 타입으로 Page 인터페이스를 적용하면 추가로 호출하는 페이징을 위한 카운트 쿼리에도 쿼리 힌트를 적용할지를
설정하는 옵션이다.
책 도메인 주도 설계는 명세라는 개념을 소개한다.
스프링 데이터 JPA 는 JPA Criteria로 이 개념을 사용할 수 있도록 지원한다.
명세를 이해하기 위한 핵심단어는 술어(predicate)인데 이것은 단순히 참이나 거짓으로 평가된다.
이것은 ANO , OR 같은 연산자로 조합할 수 있다.
예로 데이터를 검색하기 위한 제약 조건 하나하나를 술어라 할 수 있다.
스프링 데이터 JPA는 술어를 Sepcification 클래스로 정의함.
Sepcification는 컴포지트 패턴으로 구성되어 여러 Sepcification을 조립할 수 있다.
따라서 다양한 검색조건을 조립해 새로운 검색조건을 만들 수 있다.
명세기능을 사용하라면 JpaSepcificationExcutor 인터페이스를 상속 받으면 된다.
public interface JpaSpecificationExecutor <T> {
T findOne(Sepcification <T> spec);
List<T> findAll(Sepcification<T> spec);
Page<T> findAll(Sepcification<T> spec, Pageable pageable);
List<T> findAll(Sepcification<T> spec, Sort sort);
long count(Sepcification<T> secp);
public interface OrderRepository extends JpaRepository<Order,Long> , JpaSpecificationExecutor<Order> {
}
JpaSpecificationExecutor 의 메소드들은 Sepcification을 파라미터로 받아 검색조건으로 사용한다.
public List<Order> findOrders(String name) {
List<Order> result = orderRepository.findAll(
where(memberName(name)).and(isOrderStatus())
);
return result;
)
public interface MemberRepositoryCustom {
public List<Member> findMemberCustom();
}
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
public List<Member> findMemberCustom() {...}
}
public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
}
편리한 기능 제공
1. 식별자로 도메인 클래스를 바로 바인딩해주는 도메인 클래스 컨버퍼,
2. 페이징과 정렬 기능
HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아 바인딩 해준다.
ex /member/updade?id=1
// 도메인 클래스 ( 컨버터 기능 사용 x )
@Controller
public class MemberController {
@AutoWired
MemberRepository memberRepository;
@PostMapping("/member/updade")
public class memberUpdate(@RequestParam("id") Long id, Model model) {
Member member = memberRepository.findOne(id);
...
}
}
// 도메인 클래스 ( 컨버터 기능 사용)
@Controller
public class MemberController {
@AutoWired
MemberRepository memberRepository;
@PostMapping("/member/updade")
public class memberUpdate(@RequestParam("id") Member member, Model model) {
soutv(member.getName());
}
}
단 여기서 넘어온 엔티티를 수정해도 실제 데이터에서는 변경되지 않음
@RequestMapping(value ="/members")
public String list(Pageable pageable , Model model){
Page<Member> page = memberService.findMember(pageable);
model.addAttribute("members", page.getContent());
return "member/memberList";
}
파라미터로 Pageable을 받은 것을 확인 할 수 잇다. Pageable은 다음 요청 파라미터 정보로 만들어진다.
page : 현재 페이지 0부터 시작
size ; 한 페이지에 노출할 데이터 수
sort: 정렬 조인을 정의한다
ex) /members?page=0&size=20&sort=name,desc&sort=address.city
사용할 페이지 정보가 둘 이상이라면 @Qualifier 애노ㅔ이션을 사용한다.
public String list(
@Qualifier("member") Pageable memberPageable,
@Qualifier("order") Pageable orderPageable), ...
{접두사명}_ 로 구분함
ex /members?member_page=0&order_page=1
기본값
page=0, size= 20
변경 시
@GetMapping("/members_page")
public String list(@PageableDefault(size=12, sort="name", direction=Sort.Direction.DESC) Pageable pageable){
...
}
스프링 데이터 JPA는 2가지 방법로 QueryDSL를 지원한다.
1. QueryDslPredicateExecutor
2. QueryDslRepositorySupport
public interface ItemRepository extends JpaRepository<Item, Long>, QueryDslPredicateExecutor<Item> {}
이제 상품 레파지로티에서 QueryDSL를 사용할 수 있다.
// QueryDSL 사용예제
QItem item = Qitem.item;
Iterable<Item> result = itemRepository.findAll(
item.name.contains("장난감").and(item.price.between(1000,2000))
);
하지만 QueryDslPredicateExecutor는 스프링 데이터 jpa 에서 편리하게 QueryDSL 을 사영할 수 있지만 한계가 있다. join, fetch 를 상용할 수 없다.
따라서 JPAQuery를 직접 사용하거나 QueryDslRepositorySupport를 사용해야한다.
QueryDSL의 모든 기능을 사용하려면 JPAQuery 객체를 직접 생성해서 사용하면 된다. 이때 QueryDslRepositorySupport를를 상속 받아 사용하면 편리하게 QueryDSL를 사용할 수 있다.
public interface CustomOrderRepository {
public List<Order> search(OrderSearch orderSearch);
}
public class CustomOrderRepositoryImpl extends QueryDslRepositorySupport implements CustomOrderRepository {
public CustomOrderRepositoryImpl (){
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.staus.eq(orderSearch.getOrderStatus()));
}
return query.lisr(order);
}
}