
| 서브 프로젝트명 | 내용 | 
|---|---|
| 스프링 데이터 | SQL & NoSQL 저장소 지원 프로젝트의 묶음. | 
| 스프링 데이터 Common | 여러 저장소 지원 프로젝트의 공통 기능 제공. | 
| 스프링 데이터 REST | 저장소의 데이터를 하이퍼미디어 기반 HTTP 리소스로(REST API로) 제공하는 프로젝트. | 
| 스프링 데이터 JPA | 스프링 데이터 Common이 제공하는 기능에 JPA 관련 기능 추가. | 
- 스프링 데이터는 공통기능인 common을 바탕으로 각 저장소에 따른 서브 프로젝트가 존재한다.
- 스프링 데이터 JPA는 common에 JPA 관련 기능을 추가한 것이기에 common을 먼저 공부해야한다.
1.Spring Data Common : Repository
앞 아티클에서 제네릭한 Repository를 만드는 가장 진보적인 방법이라고 했던 코드가 있다
| 1 | import org.springframework.data.jpa.repository.JpaRepository; | 
- 이 JpaRepository 인터페이스는 PagingAndSortingRepository 를 상속하고 있다 
- PagingAndSortingRepository는 CrudRepository를 상속하고 있다 
- CrudRepository는 Repository를 상속하고 있다 
- 즉 계층 구조로 치면 JpaRepository-PagingAndSortingRepository-CrudRepository-Repository로 되어 있다 
- JpaRepository 인터페이스는 Spring Data JPA에 속하는 인터페이스며 상위 3개의 인터페이스는 Spring Data Common에 속하는 인터페이스다 
- 상세 - Repository- Repository 인터페이스는 메소드가 없는 마커 인터페이스의 역할을 한다
 
- CrudRepository
 
- Repository
| 1 | /* | 
- 기능을 정의한 최상단 인터페이스로 Repository 인터페이스를 상속하고 있다
- 위에서 보다시피 기본적인 CRUD 메소드를 제공한다
- 이 CRUD 기능을 이용한 테스트 샘플을 작성해 본다테스트용 h2 의존성 추가 1 
 2
 3
 4
 5<dependency> 
 <groupId>com.h2database</groupId>
 <artifactId>h2</artifactId>
 <scope>test</scope>
 </dependency>
| 1 | package me.rkaehdaos.jpademo1; | 
- 사실 원래 PostRepository2는 JpaRepository를 상속받고 따로 작성한 코드가 하나도 없기 때문에 원래 테스트가 필요한 코드가 아니다. 어디까지나 학습용 테스트임을 감안 
- 테스트는 성공한다 
- 특이점! 쿼리를 보면 insert 쿼리가 없다는 것을 알 수 있다. 왜!? - @DataJpaTest 정의를 보면 @Transactional이 붙어 있다
- @Transactional이 붙어 있으면 테스트로 바뀐 부분이 기본적으로 롤백을 하게 된다
 이것은 스프링 프레임워크의 기본
- 하이버네이트는 필요할때 DB에 데이타를 싱크하는 성질을 가지고 있다
- 따라서 하이버네이트는 어차피 롤백하는 쿼리이기 때문에 insert 자체를 하지 않는 것
- 스프링 프레임워크와 하이버네이트의 특성의 오묘한 조합으로 이러한 결과가 나옴
- select문이 있는것은 getId()를 통해 id가 필요한 것은 알기 때문
- 꼭 insert를 하기 위해서는 테스트 메소드에 @Rollback(false)를 해주면 롤백 설정이 false가 되서 무조건 커밋이 되므로 insert문이 실행됨을 확인할 수 있다
 
- 상세 - PagingAndSortingRepository- PagingAndSortingRepository는 기본적으로 페이징 기능을 제공한다PagingAndSortingRepository.java 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49/* 
 * Copyright 2008-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 package org.springframework.data.repository;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.domain.Sort;
 /**
 * Extension of {@link CrudRepository} to provide additional methods to retrieve entities using the pagination and
 * sorting abstraction.
 *
 * @author Oliver Gierke
 * @see Sort
 * @see Pageable
 * @see Page
 */
 public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
 /**
 * Returns all entities sorted by the given options.
 *
 * @param sort
 * @return all entities sorted by the given options
 */
 Iterable<T> findAll(Sort sort);
 /**
 * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
 *
 * @param pageable
 * @return a page of entities
 */
 Page<T> findAll(Pageable pageable);
 }
- 페이징 기능을 쓸떄는 Pageable 파라미터를 주게 된다pageable테스트 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13//Pageable 테스트 
 // 리턴타입이 Page임을 주목
 // 팩토리 패턴 of 주목
 Page<Post> postPage = postRepository2.findAll(PageRequest.of(0, 10));
 //전체 element의 갯수는 하나
 assertThat(postPage.getTotalElements()).isEqualTo(1);
 //현재 page 넘버는 0
 assertThat(postPage.getNumber()).isEqualTo(0);
 //현재 사이즈는 위에서 요청한 사이즈 10개
 assertThat(postPage.getSize()).isEqualTo(10);
 //현재 페이지에 들어올 수 있는 개수 인듯
 assertThat(postPage.getNumberOfElements()).isEqualTo(1);
 
- PagingAndSortingRepository는 기본적으로 페이징 기능을 제공한다
 
- PagingAndSortingRepository
2.Spring Data Common : Repository 인터페이스 정의
- 지금까지 Repository 생성 방법은 Spring Data JPA 혹은 Spring Data Common에서 제공하는 인터페이스를 직접 상속받아 만들었다 
- 이게 좋다 사실 . 많은 기능이 한꺼번에 들어오기 때문에 
- 만약 내가 정의하고 싶은 기능만 정의해서 Repository를 생성하고 싶다면? 
- @RepositoryDefinition을 사용하면 된다 - 1 
 2
 3
 4
 5
 public interface CommentRepository{
 Comment save(Comment comment);
 List<Comment> findAll();
 }
- 도메인 클래스와 id클래스를 정해준다 
- 필요한 기능은 정의한다. 저정도는 스프링 Data에서 알아서 만들어 준다 
- 이렇게 하면 직접 정의한 기능만 들어 있는 Repository가 완성 된다 
- 대신 이렇게 되면 해당 메소드들은 개발자가 구현한 것이므로 테스트 코드가 필요하다 
- 또한 레포지토리 10개가 전부 save를 가지고 있다면? save 코드 10개? 
- 이러할때는 해당 메소드들이 구현된 @NoRepositoryBean 커스텀 인터페이스를 만들고 
 빈등록될 인터페이스에서 해당 커스텀 인터페이스를 상속받게 하면 된다
 해당 커스텀 인터페이스는 제네릭으로 타입을 처리하도록 한다- MyRepository.java 커스텀 인터페이스, 사용자 코드 정의가 들어 있다 - 1 
 2
 3
 4
 5
 6
 7- // 이 인터페이스는 빈이 될 필요가 없다 
 //Serializable 할 수 있는 Id를 사용해야 한다
 public interface MyRepository<T, Id extends Serializable> extends Repository<T, Id> {
 //필요한 메소드를 정의
 <E extends T> E save(E comment);
 List<T> findAll();
 }- 실제 빈이 되는 CommentRepository, 커스텀 인터페이스를 상속받았다 - 1 
 2
 3- public interface CommentRepository<T, Id> extends MyRepository<Comment, Long> { 
 //이러면 MyRepository에 커스텀 정의된 부분만 들어간다
 }- CommentRepositoryTest.java, CommentRepository의 테스트 클래스다 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 public class CommentRepositoryTest {
 // 빈주입
 CommentRepository commentRepository;
 
 public void crud() {
 Comment comment = new Comment();
 comment.setComment("Hello Comment");
 commentRepository.save(comment);
 List<Comment> all = commentRepository.findAll();
 assertThat(all.size()).isEqualTo(1);
 assertThat(commentRepository.count()).isEqualTo(1);
 }
 }
- 여러모로 번거롭다 그냥 원래 방법을 쓰고 이거는 이러것이 있다 정도로만 
3.Spring Data Common : Null 처리
- Spring Data 2.0부터 자바 8의 Optional을 지원 - Ex) OptionalfindById(Long id); MyRepository.java 커스텀 인터페이스에 위에 내용을 추가한다 1 
 2
 3
 4
 5
 6
 7
 public interface MyRepository<T, Id extends Serializable> extends Repository<T, Id> {
 //필요 메소드 정의
 <E extends T> E save(E comment);
 List<T> findAll();
 <E extends T>Optional<E> findById(Id id); //추가
 }Optional Test, null체크를 안해도 된다 1 
 2
 3
 4
 5
 6
 7//Optional Test 
 Optional<Comment> byId = commentRepository.findById(100l);
 assertThat(byId).isEmpty(); //Optional 객체 안이 실제로는 비어있다
 Comment comment2 = byId.orElse(new Comment()); //값이 비면 새 객체로 할 수도 있다
 assertThat(comment2).isNotNull();
 //Comment comment3 = byId.orElseThrow(()->new IllegalArgumentException()); //예외를 던질 수도 있다
 //Comment comment4 = byId.orElseThrow(IllegalArgumentException::new); //메소드 레퍼런스로 다 간결하게 가능
 
- Ex) Optional
- 콜렉션은 Null을 지원하지 않고 , 비어있는(Empty) 콜렉션을 리턴한다 
- 스프링 프레임워크 5.0 부터 지원하는 Null 어노테이션 
- @NotNullApi, @NonNull, @Nullable 
- 런타임 체크 지원 
- JSR 305 어노테이션을 메타 어노테이션으로 가지고 있음 
- 인텔리J에서 add Runtime asserttion 옵션에서 스프링 어노테이션 추가해서 사용 가능 
3.Spring Data Common : Query 만들기
- 쿼리 생성 전략 - CREATE- 메소드 이름을 분석해서 쿼리를 만드는 방법 - Spring Data가 쿼리를 만들어 준다
 
- USE_DECLARED_QUERY- 메소드 이름이 아닌 부가 정보(@Query 등)를 바탕으로 쿼리를 찾아 실행
 
- CREATE_IF_NOT_FOUND- 선언된 쿼리를 찾고 없는 경우 메소드 이름 분석해서 쿼리 생성(기본값)
 
 
- CREATE
- 쿼리 생성 전략 세팅 - @EnableJpaRepositories 에서 설정가능기본값이 CREATE_IF_NOT_FOUND이므로 밑에처럼 해줄 필요는 없다. 예시임 1 
 2
 3
 4
 5
 6
 7
 public class Jpademo1Application {
 public static void main(String[] args) {
 SpringApplication.run(Jpademo1Application.class, args);
 }
 }
 
- @EnableJpaRepositories 에서 설정가능
- 쿼리 만드는 방법 - 구성: 리턴타입 {접두어}{도입부}By{프로퍼티표현식}(조건식)[(And|Pr){프로퍼티표현식}(조건식)]{정렬조건}(매개변수)
- 리턴타입: List, T, Optional , Page , Slice , Stream 
- 접두어: Find, Get, Query, Count, …
- 도입부: Distinct, First(N), Top(N)
- 프로퍼티 표현식: Person.Address.ZipCode => find(Person)ByAddress_ZipCode(..)
- 조건식: IgnoreCase, Between, LessThan, GreaterThan, Like, Contains, …
- 정렬조건: Orderby{프로퍼티}Asc|Desc
 
| 1 | public interface CommentRepositoryextends MyRepository<Comment, Long> { | 
| 1 | 
 | 
- 계속적으로 확장이 가능하다
- 다음은 3개를 받아서 likecount 10보다 큰 2개를 오름차순으로 정리, 가장 첫번째 것의 값을 assertThat한 것이다
- 정열을 Sort를 따로 줄 수도 있지만 이처럼 메소드에 OrderBy로 처리하는 것이
 좀더 직관적인 듯 하다
| 1 | public interface CommentRepository extends MyRepository<Comment, Long> { | 
| 1 | 
 | 
- 페이지 쿼리- 페이지쿼리는 Pageable 파라미터가 필요하다고 했다
- 또한 Page<>로 받아야 페이지 정보가 유용하다Pageable 인자를 추가하고 리턴을 Page<>형태로 바꿨다 1 
 2
 3
 4
 5
 6public interface CommentRepository extends MyRepository<Comment, Long> { 
 //MyRepository에 커스텀 정의 include
 //List<Comment> findByCommentContainsIgnoreCaseAndLikeCountGreaterThanOrderByLikeCountAsc(String keyword, int likeCount);
 Page<Comment> findByCommentContainsIgnoreCase(String keyword, Pageable pageable);
 }
 
| 1 | 
 | 
- 스트리밍- 자바 8부터 있는 Stream<>으로 리턴 받을 수 있다
- Stream을 다 쓴 다음 close()하여야 한다
- 아니면 자바8의 try-with-resource를 쓰면 좋다