© 2008-2015 The original authors.

 
이 문서의 복사본은 당신 자신을 위해서나, 다른 사람들에게 배포를 위해 만들어질 수 있습니다. 복사에 대해 어떤 비용을 청구하지 않아야 하며, 프린트되거나 전자적으로 배포될 때에도 Copyright 문구를 포함해야 합니다.
목차

서문

1.프로젝트 메타데이터

2. 의존성들

스프링 데이터 모듈들의 각자 다른 시작으로 인하여, 모듈들의 대부분은 각자 다른 메이저, 마이너 버젼 넘버를 가지고 있습니다. 가장 쉬운 호환성이 맞는 것을 찾는 방법은 Spring Data Release Train BOM에서 저희가 정의한 호환성 버젼을 찾는 것입니다. 메이븐 프로젝트에서 당신은 이 의존성 <dependencyManagement />섹션을 당신의 폼 부분에 선언할 수 있습니다.

예제 1. Spring Data release train BOM 사용하기
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-releasetrain</artifactId>
      <version>${release-train}</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  <dependencies>
</dependencyManagement>

현재 릴리즈 트레인 버젼은 Fowler-BUILD-SNAPSHOT입니다. 트레인 이름은 알파벳 오름차순이며 현재 가능한 버젼은 여기에 나와있습니다. 버젼이름은 다음 패턴을 따르고-${name}-${release} 릴리즈는 다음의 하나를 따르게 됩니다:

  • BUILD-SNAPSHOT - 현재 스냅샷

  • M1M2 etc. - 마일스톤

  • RC1RC2 etc. - 릴리즈 후보

  • RELEASE - GA 릴리즈

  • SR1SR2 etc. - 서비스 릴리즈

Spring Data examples repository에서 BOM을 사용하는 작업 예제를 발견할 수가 있습니다. .

저 자리의 <dependencies />블록에 버젼을 사용하지 않고 스프링 데이터 모듈을 선언했습니다. (번역하면서 애매했던 부분임. )

예제 2. 스프링 데이터 모듈 의존성 선언
<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>

 

2.1. 스프링 부트에서의 의존성 관리

스프링 부트는 이미 매우 최근의 스프링 데이터 모듈을 선택했습니다. 그럼에도 불구하고 새로운 버젼을 사용하고 싶은 경우를 위하여 간단히 spring-data-releasetrain.version 프로퍼티를, trainname 과 당신이 사용하고자 하는 iteration에 설정해주세요.

 

2.2. 스프링 프레임 워크

현재 스프링 데이터 모듈의 버젼은 스프링 프레임워크 4.0.9 릴리즈 이상을 필요로 합니다. 저 마이너 버젼보다 좀 더 오래된 버그픽스 버젼과 함께 작업될 수 있을 것이지만, 최근의 버젼을 사용하는 것을 강력히 추천드립니다.

 

3. 스프링 데이터 리파지토리들과 작업하기

스프링 데이터 레파지토리 추상화의 목표는, 다양한 영속화 저장소를 위한 데이터 액세스 레이어를 구현하는 데 있어 필요한, 상당한양의 보일러 플레이한 코드들을 줄이는 데 있습니다

 

스프링 데이터 리파지토리 문서와 당신의 모듈

이 챕터는 스프링 데이터 리파지토리들의 인터페이스와 핵심개념을 설명합니다. 이 챕터의 정보는 스프링데이터 공통모듈에서 나왔으며, JPA모듈을 위한 설정과 코드 샘플을 사용해봅니다. XML 네임스페이스 선언과 당신이 사용할 특별한 모듈들같은 확장되는 타입들에 적응해봅시다. (의역) Namespace reference 는 repository API를 지원하는 모든 스프링데이터모듈쪽에서 XML 설정을 다루며, Repository query keywords는 일반적으로 레파지토리추상화에 의해 지원되는 쿼리 메소드 키워드들을 다룹니다. 당신 모듈의 특별한 특징에 관한 정보를 찾는다면, 이 문서에서 그 모듈에 대한 챕터를 살펴보세요.

 

3.1. 핵심 개념

스프링 데이터 리파지토리 추상화에서 중심적인 인터페이스는 Repository(아마 놀라시않으실테지만)입니다.이것은 도메인클래스와 도메인의 id 타입을 타입아규먼트로 받습니다. 이 인터페이스는 주로 마커 인터페이스로 동작하며, 작업할 타입을 가지고 있으면서, 당신이 이것을 확장할 인터페이스를 발견하게 해줍니다. CrudRepository 는 관리되는 엔티티클래스에서 복잡한 CRUD기능을 제공해줍니다.

예제 3. CrudRepository interface
public interface CrudRepository<T, ID extends Serializable>
    extends Repository<T, ID> {

    <S extends T> S save(S entity); 

    T findOne(ID primaryKey);       

    Iterable<T> findAll();          

    Long count();                   

    void delete(T entity);          

    boolean exists(ID primaryKey);  

    // … more functionality omitted.
}
  주어진 엔티티를 저장합니다.
  주어진 아이디로 식별된 엔티티를 반환합니다.
  모든 엔티티를 반환합니다.
  엔티티의 숫자를 반환합니다.
  주어진 엔티티를 삭제합니다.
  주어진 아이디로 엔티티가 존재하는지를 반환합니다.
 
우리는 또한 JpaRepository 나 MongoRepository같은 기술특징적인 추상화를 제공합니다. 이러한 인터페이스는 CrudRepository 를 확장하여 일반적인 기능에 좀 더 해당기술에 해당하는 기술을 사용할 수 있게 해줍니다(의역. 중요한 부분이 아니니 시간소비없이..=3=3)

CrudRepository를 확장한 것 중에 PagingAndSortingRepository 는 쉽게 엔티티들에 대해 페이징을 할수있는 메소드를 제공합니다.

예제 4. PagingAndSortingRepository
public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

User의 페이지사이즈를 20으로 잡고 두번째 페이지에 접근하는 것은간단하게 이렇게 해볼수 있습니다.:

PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));

쿼리메소드들을 더 말해보자면, 쿼리 카운트나 삭제 만들기가 가능합니다.

예제 5. 카운트 쿼리
public interface UserRepository extends CrudRepository<User, Long> {

  Long countByLastname(String lastname);
}
예제 6. 삭제 쿼리
public interface UserRepository extends CrudRepository<User, Long> {

  Long deleteByLastname(String lastname);

  List<User> removeByLastname(String lastname);

}

 

3.2. Query 메소드들

표준 CRUD기능들을 가진 리파지토들은 데이터소스에 대한 쿼리들을 가지고 있습니다. 스프링데이터와 함께 이러한 쿼리들을 4단계로 만들어봅시다.

  1. Repository나 그 하위 인터페이스를 상속하는 인터페이스를 상속하여, 도메인클래스와 ID 타입을 적어줍시다.

    interface PersonRepository extends Repository<User, Long> {  }
  2. 그 인터페이스에 대해서 쿼리메소드를 선언합니다.
    interface PersonRepository extends Repository<User, Long> {
      List<Person> findByLastname(String lastname);
    }
  3. 스프링을 설정하여 이러한 인터페이스들을 위한 프록시 인스턴스를 생성하게 해줍니다. Set up Spring to create proxy instances for those interfaces. Either via 자바 설정 :

    이나, XML을 통해 가능합니다.
    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    
    @EnableJpaRepositories
    class Config {}
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/data/jpa
         http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    
       <jpa:repositories base-package="com.acme.repositories"/>
    
    </beans>

    JPA 네임스페이스는 이 예제에서 사용되었습니다. 만약 당신이 다른 데이터 저장소를 위한 추상화 리파지토리를 사용한다면, 이것을 위해 적적한 네임스페이스 선언으로 바꿔줘야할 것입니다. 예를 들자면 몽고디비나 JPA같이 말이죠 (생략의역)

또한, 자바설정 방법은 기본적으로 사용되는 어노테이션된 클래스를 깔끔하게 설정을 해주지않으므로 패키지를 스캔하기 위하여 다음과 같은 설정을 적어주게 됩니다. basePackage…@Enable…-어노테이션.

  1. 리파지토리 인스턴스를 주입시키고 사용하세요

    public class SomeClient {
    
      @Autowired
      private PersonRepository repository;
    
      public void doSomething() {
        List<Person> persons = repository.findByLastname("Matthews");
      }
    }
다음의 섹션이 각각의 단계를 세부적으로 설명할 것입니다.

 

3.3. 리파지토리 인터페이스 정의하기

첫번째 단계로, 당신은 도메인특정 리파지토리 인터페이스를 정의해야 합니다. 이 인터페이스는 반드시 Repository 를 상속해야 하며, 도메인 클래스와 아이디 타입을 적어줘야 합니다. 만약 당신이 CRUD메소드를 사용하기 원한다면, Repository대신에 CrudRepository 를 사용해줍니다.

 

3.3.1. 좀 더 세밀한 리파지토리 정의 조정

일반적으로, 당신의 리파지토리 인터페이스는 RepositoryCrudRepository or PagingAndSortingRepository같은 것을 상속할 것입니다. 또 다르게는, 만약 당신이 스프링 데이터 인터페이스를 상속하기를 원하지 않는다면 당신은 당신의 리파지토리에 @RepositoryDefinition를 붙일 수 있습니다(역주: 예제에서의 @NoRepositoryBean를 말하는 것인듯하다.). CrudRepository를 상속하는 것은 당신의 엔티티를 다루는 메소드들의 집합을 노출시킵니다. 만약 당신이 노출되는 메소드들에 관해서 선택적으로 하고 싶다면, 단순히 CrudRepository에서 당신이 노출시키는 부분을 복사하여 당신의 도메인 리파지토리에 붙여보셔요(^^)

 
이것은 제공된 스프링데이터 리파지토리에서, 당신 자신의 추상화를 정의할 수 있게 합니다.
Example 7. 선택적으로 CRUD 메소드 노출시키기
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

  T findOne(ID id);

  T save(T entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

이 첫번째 단계에서 당신은 모든 도메인 리파지토리들을 위한 기본 작업 인터페이스를 정의하였고, findOne(…)과 save(…)를 노출시켰습니다. 이러한 메소드들은 리파지토리 구현 구현체에 위치하게 될 것입니다. 당신이 스프링데이터에서 선택한 저장소. 예를 들자면 JPA이면 SimpleJpaRepository같은 것에 위치할 것입니다. 왜냐하면 이것들(역주:구현체?)은 CrudRepository의 메소드 시그니처를 매칭시키기 때문입니다. 그러므로 UserRepository는 사용자들을 저장하고 id로 단일유저를 찾을 수 있고, 뿐만 아니라 그들의 이메일 주소로 Users들을 찾는 쿼리를 동작시킬 것입니다.

 
@NoRepositoryBean 중간 레파지토리 인터페이스를 한번 보세요. 당신이 저 어노테이션을 붙인 모든 리파지토리는 런타임에서 인스턴스를 생성하지 않습니다.

3.4. 쿼리 메소드 정의하기

리파지토리 프록시는 메소드 이름으로 저장소에 맞는 쿼리를 만들어내는 두가지 방법이 있습니다. 하나는 메소드이름으로 직접적으로 쿼리를 만들어내는 것이고, 아니면 수동적으로 정의된 쿼리를 사용하는 것입니다. 저장소에 따라서 옵션이 더 있을 수도 있습니다. 실제 쿼리를 생성하는 것을 결정할 전략이 있을 수 있습니다. 사용가능한 옵션을 살펴볼까요?

3.4.1. 쿼리 탐색(lookup) 전략

다음 전략에서 리파지토리 인프라스트럭쳐가 쿼리를 해석하는 것이 가능합니다. XML 설정의 네임스페이스에서 query-lookup-strategy속성을 통하거나, 자바설정에서 Enable${store}Repositories어노테이션이 있는 곳에서 queryLookupStrategy속성을 통해 전략을 설정할 수 있습니다. 몇몇 전략은 특정 데이터베이스에서 지원되지 않습니다 .

  •  

    CREATE 는 저장소에 맞는 쿼리를 쿼리메소드이름으로 부터 만들어내는 것을 시도합니다. 일반적인 접근은 메소드이름으로부터 잘 알려진 접두어를 제거하고 나머지 부분을 파싱하는 것입니다. 쿼리생성에서 이 부분에서 좀 더 읽어보세요.Query creation

     

  • USE_DECLARED_QUERY 는 선언된 쿼리를 찾는 것을 시도하고, 찾을 수 없을 때는 예외를 날립니다. 쿼리는 어노테이션이나 다른 방법으로 선언된 방식에 의해 정의될 수 있습니다. 특정 데이터 저장소의 문서를 참고하셔서 그 저장소에서 사용가능한 옵션들을 찾아보세요. 만약 리파지토리 인프라스트럭처가 부트스트랩 시간에 그 메소드를 위해 정의된 쿼리를 찾을 수 없으면 이것은 실패합니다.

  • CREATE_IF_NOT_FOUND (default) CREATE 와 USE_DECLARED_QUERY를 조합합니다. 이것은 처음에 정의된 쿼리를 찾아보고 발견되지 않으면, 커스텀 메소드네임 기반의 쿼리를 생성합니다. 이것은 기본 탐색전략이고, 당신이 아무것도 명시적으로 설정하지 않으면 기본 설정이 될 것입니다. 이것은 메소드이름으로 빠른 쿼리 정의를 가능케 하며, 필요에 따라 선언된 쿼리를 소개하면서 이러한 쿼리들의 커스텀 튜닝또한 가능하게 합니다.

 

3.4.2. 쿼리 생성

쿼리 빌더 메커니즘은 스프링 데이터 리파지토리 인프라스트럭쳐로 짜여져, 리파지토리의 엔티티들에 맞는 쿼리들을 만들어내는 데 유용합니다. 이 메커니즘은 find…Byread…Byquery…Bycount…By, 와 get…By같은 접두어들을 메소드에서 떼어내고 나머지부분을 파싱하기 시작합니다. 다음 소개하는 절은 Distinct(쿼리가 생성될지 결정하는 flag를 설정)같은 더 깊은 표현을 포함하고 있습니다. 그러나 처음의 By는 실제 크리테리아의 시작을 가리키는 구별자로 동작합니다. 기본레벨에서 당신은 엔티티의 속성을 결정할 조건을 정의할 수 있으며, 그것들을 And 와 Or로 연결할 수 있습니다.

Example 8.메소드네임으로 쿼리 생성하기
public interface PersonRepository extends Repository<User, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

메소드에 파싱된 실제 결과는 쿼리를 생성하는 데이터베이스에 따라 다르지만, 생각해볼만한 일반적인 것들이 있습니다.

  • 이 표현은 보통 속성을 순회하며(영어 오타인듯?) 연결될 수 있는 연산자와 함께 조합됩니다. AND 나 OR같은 표현과 함께 속성을 조합할 수 있습니다. 당신은 또한 BetweenLessThanGreaterThanLike같은 연산자의 지원을 받을 수 있습니다. 이러한 지원되는 연산자는 데이터스토어에 따라 다를 수 있으니, 해당 레퍼런스 문서에 적절한 부분을 참고해보세요.
  • 메소드파서는 code>IgnoreCase에 대한 독립적인 flag 속성설정을 지원하며 예를 들자면 findByLastnameIgnoreCase(…)같은 것이 되거나 findByLastnameAndFirstnameAllIgnoreCase(…)같이 모든 String인스턴스에서도 지원을 할 수 있습니다. 이 부분도 저장소에 따라 다를 수 있으니 해당 저장소 레퍼런스 문서를 참조하세요

  • 당신은 정적 순서를 OrderBy절을 추가하여 쿼리메소드의 정렬 방향을 정할 수 있습니다. Asc 나 Desc 로 말이죠.. 동적 정렬을 지원하는 쿼리를 만들기 위해 특별 파라미터 핸들링를 보시기 바랍니다.

3.4.3. 속성 표현

속성표현은 이전 예제에서 나타난대로, 관리되는 엔티티의 직접적인 속성을 참조할 수 있습니다. 쿼리 생성 시간에 당신은 이미 파싱된 속성이 관리되는 도메인 클래스의 일부라는 것을 확실하게 하고 가야 합니다. 그러나 당신은 중첩속성에 대한 제약을 정의할 수 있습니다. Person이 ZipCode와 함께 있는 Address를 가지고 있다고 생각해봅시다. 이러한 경우 메소드 네임은

List<Person> findByAddressZipCode(ZipCode zipCode);

이렇게 되며, x.address.zipCode 이라는 속성에 접근하게 됩니다. 이렇게 해석하는 알고리즘은 전체 부분(AddressZipCode)을 속성으로 인터프리팅하면서 시작되어 속성에 맞는 도메인 클래스를 검사합니다. 만약 알고리즘이 성공하면, 이것은 저 속성을 사용할 것이고. 그렇지 않다면 알고리즘은 저 부분을 Camel case 부분의 오른쪽부터 왼쪽으로 그리고 꼬리부분으로 나눠보며, 맞는 속성을 찾으려 할 것입니다. 우리의 예제에서는 AddressZip 와 Code일 것입니다. 만약 알고리즘이 앞에서 맞는 속성을 찾아내면, 거기서부터 뒷부분을 가지고서 트리다운을 만들기 시작합니다. (역주: 그 뒷부부분으로 변수를 만든다는 뜻인듯^^;) 이전에 설명한 방법대로 뒷부분을 잘라보면서 말입니다. 만약 처음 나누기가 매칭이 되지 않으면 알고리즘은 나누는 지점을 왼쪽으로 이동해보게 됩니다. (AddressZipCode) 그리고 다시 시작합니다.

비록 이러한 작업이 대부분의 경우 잘 동작하지만, 잘못 동작할 경우가 있습니다. Person 가 addressZip이라는 속성도 가지고 있다고 생각해봅시다. 이러한 알고리즘은 처음 나누면서 본질적으로 잘못된 뒷부분을 addressZip가지게 되어 아무런 속성도 아닌(code)것을 고르게 됩니다.

이러한 모호한 점을 해결하기 위해 당신은 _ 를 당신의 메소드 이름 내에 사용하여 수동적으로 횡단지점(tracersal이라고 나와있는데 나누는 지점이라 해야 맞을듯)을 정의할 수 있습니다. 그래서 메소드 이름은 다음과 같이 끝나게 됩니다.

List<Person> findByAddress_ZipCode(ZipCode zipCode);

우리가 _를 예약된 문자어로 썼기 때문에, 우리는 강력하게 표준 자바 작명 관습을 따르기를 권합니다 . (i.e. 카멜케이스CamelCase 쓰세요^^)

 

3.4.4. 특별 파라미터 핸들링

당신의 쿼리에서 파라미터를 다루기 위해, 당신은 간단히 메소드파라미터를 예전에 봤던 예제에서 처럼 정의할 수 있습니다. 게다가 인프라스트럭쳐가 PageableSort같은 특정 타입을 인식하여 당신의 쿼리에서 페이징과 정렬을 동적으로 하게 해줄 것입니다.

Example 9. 쿼리메소드에서 Pageable, Slice와 Sort를 사용하기
Page<User> findByLastname(String lastname, Pageable pageable);

Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Pageable pageable);

첫번째 메소드는 org.springframework.data.domain.Pageable인스턴스를 쿼리메소드에 전달하여 정적으로 정의된 쿼리에 동적으로 페이징을 추가하게 해줍니다. Page는 전체적인 요소의 숫자와 가능한 페이지의 개수를 알 수 있습니다. 하부구조에서 숫자조회 쿼리를 발동하여 전체적인 숫자를 계산함으로써 그것이 가능해집니다. 이것은 사용하는 저장소에 따라서 비싼 작업이 될 수도 있습니다. Slice가 반환형으로 대신 리턴될 수도 있습니다. Slice는 오직 다음 Slice 가 가능한지만을 알고 있으며 많은 결과 셋을 가지고 작업할 때 쓰기 충분할 것입니다.

정렬 옵션은 Pageable 인스턴스를 통해서도 다뤄지기도 합니다. 오직 정렬만을 필요로 한다면 org.springframework.data.domain.Sort 를 당신의 메소드에 파라미터로 전달해보세요. 당신이 보듯이, 단순히 List를 리턴하는 것이 가능할 것입니다. 이러한 경우 실제 Page인스턴스를 만들어내는 데 필요한 추가적인 메타데이터 생성되지 않을 것입니다 ( 여기서는 필요할지도 모르는 추가적인 카운트 쿼리가 발생되지 않는다는 것을 의미합니다 ) 대신에 단순히 쿼리를 오직 주어진 범위의 엔티티에서만으로 제한하는 것입니다.

 
당신이 쿼리에서 얼마나 많은 페이지를 얻을 수 있는지 알아보기 위해, 당신은 추가적인 count 쿼리를 동작시켜야 합니다. 기본적으로 이 쿼리는 당신이 실제적으로 동작하는 쿼리로부터 만들어집니다. (역주 : 말이 이상한 것같은데;; 그냥 페이징 하면서 카운트 쿼리가 같이 날아간다 그런 말인 듯하다.)

 

3.4.5. 쿼리 결과 Limit 하기

쿼리메소드의 결과는 first나 top를 통해서 제한될 수 있으며 바꿔 쓸수 있습니다. 선택적인 숫자값이 top/first 로 추가되어 반환될 최대 결과 크기를 명시할 수 있습니다. 만약 숫자가 무시하면 1로 가정합니다.

예제 10. Top 과 First로 결과 크기를 한계짓기 :
User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

List<User> findFirst10ByLastname(String lastname, Sort sort);

List<User> findTop10ByLastname(String lastname, Pageable pageable);

Limiting 표현은 또한 Distinct키워드를 지원합니다. 또한 결과 셋이 하나의 인스턴스로 제한된 쿼리들을 위해 결과를 Optional로 포장(wrapping)하는 것도 지원됩니다 .

만약 페이지네이션이나 슬라이싱이 제한된 쿼리 페이지네이션에 적용이 되면, (사용가능한 페이지숫자들에 적용이 되면) 이것은 제한된 결과내에서 적용됩니다.

 
Sort파라미터를 통한 동적 정렬과 결과값을 limiting 하는 것의 조합은 쿼리메소드를 나타내게 하는 것을 허용하게 해줍니다. ( 뒷부분에 나온 for문인데 잘 이해가 안된다. for the 'K' smallest as well as for the 'K' biggest elements.)

 

3.4.6. 쿼리 결과 Streaming 하기

쿼리 메소드의 결과값은 Java8의 Stream<T>를 이용하여 점차적으로 처리될 수 있습니다. 단순히 쿼리의 결과를 Stream 으로 포장( wrapping )하여, 데이터 스토어 특정 메소드들은 스트리밍을 하는데 사용됩니다.

Example 11. 자바8 Stream<T>로 쿼리 결과 Stream하기 :
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
 
Stream은 잠재적으로 데이터스토어특정자원을 포장(wrap)하고 사용뒤에 반드시 닫혀져야 합니다. 당신은 직접 Stream 를 close()를 사용하여 닫아주거나 Java7의 try-with-resources블록을 사용할 수도 있습니다 .
예제 12. Stream<T> 와 try-with-resources 블록으로 작업해보기
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}
 
모든 스프링 데이터 모듈이 현재 Stream<T>를 반환타입으로 제공하는 것은 아닙니다 .

 

3.5. 리파지토리 인스턴스 생성하기

이 섹션에서는 당신은 리파지토리 인터페이스 정의를 위한 인스턴스들을 생성해보고 빈도 정의해봅니다.. 이것을 하는 한가지 방법은 스프링데이터모듈에 있는 Spring 네임 스페이스를 사용하는 것입니다. 비록 우리가 일반적으로 자바설정 스타일의 설정을 추천하지만, 스프링 데이터 모듈은 리파지토리 메커니즘을 지원합니다.

3.5.1. XML configuration

각각의 스프링 데이터 모듈은 repository 요소들을 포함하며 이 요소들은 단순하게 스프링이 스캔하는 base Package를 정의하게 해줍니다.

예제 13. XML을 통한 SPring data Repository 활성화
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.springframework.org/schema/data/jpa"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

  <repositories base-package="com.acme.repositories" />

</beans:beans>

예전의 예제에서, 스프링은 Repository나 그 하위 인터페이스를 상속한 인터페이스들을 위해서 com.acme.repositories를 스캔을 하기로 되어있으며, 여기서의 모든 하위패키지들들을 스캔하기로 되어있습니다. 각각의 인터페이스가 발견되면 인프라스트럭쳐는 특정기술-영속 FactoryBean에 등록을 하여 적절한 프록시를 만들어 쿼리 메소드의 실행을 다루게 됩니다. 각각의 빈은 인터페이스로 추론된 빈 네임 아래 등록됩니다. 그래서 UserRepository 의 인터페이스는 userRepository로 등록이 될 것입니다. base-package속성은 와일드카드를 허용하여 당신은 스캔될 패키지로 패턴을 사용할 수도 있습니다 .

Using filters

기본적으로 인프라스트럭쳐는 설정된 base package 밑의 각각의 특정영속기술 Repository하위 인터페이스를 골라서, 그것을 위한 빈 인스턴스를 생성합니다. 그러나 당신은 좀더 상세한 설정을 원할 수도 있습니다. 이를 위해서 당신은 <include-filter /> 나 <exclude-filter />를 요소를 <repositories /> 내부에 사용할 수도 있습니다. 이러한 문법은 스프링의 콘텍스트 네임스페이스와 정확히 동일합니다. 세부적인 사항은 스프링 레퍼런스 문서를 보시길 바랍니다. (역주: 번역문서라 스프링 공홈 변수 설정이 안됨ㅠ)

예를 들자면, 리파지토리에서 인스턴스화되는 특정 인터페이스를 제외하기 위해서 당신은 다음의 설정을 할 수도 있습니다 :

예제 14. exclude-filter 요소 사용해보기
<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>

이 예제는 SomeRepository 로 끝나는 모든 요소들이 인스턴스화되지 않게 합니다.

3.5.2. JavaConfig

리파지토리 인프라스트럭쳐는 자바설정에서 특정 저장소 @Enable${store}Repositories 어노테이션을 사용하여 동작합니다. 스프링콘테이너에서 자바 기반의 설정에 대한 소개를 보기 위해 , 레퍼런스 문서를 보세요.[1]

스프링 데이터 리파지토리를 활성화시키는 간단한 설정입니다.

예제 15. 간단한 어노테이션 기반의 리파지토리 설정
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

  @Bean
  public EntityManagerFactory entityManagerFactory() {
    // …
  }
}
 
예제에서는 JPA기반의 어노테이션을 사용하였지만 당신이 실제로 사용하는 저장소모듈에 따라 바꿔줄 수 있습니다. EntityManagerFactory빈의 정의에 똑같이 적용이 됩니다. 특정저장소 설정에 대해 다루는 섹션을 참조하세요

3.5.3. Standalone usage

당신은 또한 스프링 컨테이너의 바깥에서 repository infrastructure를 사용할 수 있습니다. 예를 들자면 CDI환경도 있습니다. 당신은 여전히 클래스 패스에 스프링 라이브러리가 필요할 것이지만, 일반적으로 계획에 따라서 리파지토리들을 설정할 수 있습니다. 리파지토리 지원을 제공하는 스프링 데이터 모듈은 당신이 사용할 수 있는 특정기술영속저장소 RepositoryFactory를 가지고 있을 수 있습니다 .

예제 16. 리파지토리 팩토리의 독립적인 사용. Standalone usage of repository factory
RepositoryFactorySupport factory =  // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

 

3.6. 스프링 데이터 리파지토리의 커스텀 구현

종종 적은 리파지토리 메소드를 위해 커스텀 구현을 제공해야할 필요가 있습니다. 스프링 데이터 리파지토리는 쉽게 커스텀 리파지토리 코드를 제공하고 , 이것을 일반적인 CRUD추상화와 쿼리 메소드 기능에 통합시키게 해줍니다.

 

3.6.1. 단일 리파지토리에 커스텀 행동 추가해보기

리파지토리에 커스텀 기능을 좀 더 넣기 위해, 당신은 먼저 인터페이스를 정의하고, 그 커스텀 기능을 위한 구현체를 정의해야 합니다. repository 인터페이스를 사용하여 커스텀 인터페이스를 확장하세요.

예제 17. 커스텀 리파지토리 기능을 위한 인터페이스
interface UserRepositoryCustom {
  public void someCustomMethod(User user);
}
예제 18. 커스텀 리파지토리 기능 구현
class UserRepositoryImpl implements UserRepositoryCustom {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}
 
다른 핵심리파지토리 인터페이스와 비교해봤을 때, 클래스에서 가장 중요하게 발견되는 부분은 이름의 Impl 접미어입니다. (하단 참조 )

그 스스로의 구현은 스프링데이터에 의존하지 않으며, 정식 스프링빈이 될 수 있습니다. 그러므로 당신은 JDBCTemplate 같은 다른 빈처럼, 표준 의존성주입행동을 주입레퍼런스로써 사용할 수 있습니다. aspects에 참여시키는 것같은 일을 할 수가 있습니다.

예제 19. 당신의 기본적인 리파지토리 인터페이스 변경 Changes to the your basic repository interface
interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {

  // Declare query methods here
}

이제 당신의 표준 리파지토리 인터페이스를 커스텀버젼으로 확장해봅시다. CRUD와 커스텀 기능을 조합하여 클라이언트에게 사용가능하게 해봅시다.

설정

만약 당신이 네임스페이스 설정을 사용한다면 리파지토리 인프라스트럭쳐는 자동으로 리파지토리를 발견한 곳에 있는 패키지에서 클래스를 스캔하여 커스텀 구현체를 찾을 것입니다. 이러한 클래스들은 네임스페이스 요소 속성 repository-impl-postfix을 따를 필요가 있습니다. 이러한 접미어는 기본적으로 Impl입니다.

설정 20. 설정 예제Configuration example
<repositories base-package="com.acme.repository" />

<repositories base-package="com.acme.repository" repository-impl-postfix="FooBar" />

첫번째 설정 예제에서는 com.acme.repository.UserRepositoryImpl 라는 클래스를 찾아 커스텀 리파지토리 구현체로 작동하게 할 것입니다. 반면에 두번째 예제 줄에서는 com.acme.repository.UserRepositoryFooBar를 찾을 것입니다.

수동 와이어링 Manual wiring

만약 당신의 커스텀 구현체가 어노테이션 기반의 설정을 사용한다면 이러한 접근은 잘 동작할 것이며, 커스텀 구현체는 다른 스프링 빈으로 잘 다뤄질 것입니다. 만약 커스텀 구현체가 특별한 와이어링을 원한다면 당신은 단순히 빈을 선언하고 앞서 선언한 컨벤션에 따라 이름지으면 됩니다. 인프라스트럭쳐는 수동으로, 새로 빈을 만드는 대신에 이름으로 정의된 빈정의를 참조할 것입니다.

예제 21. 커스텀 구현체의 수동 와이어링 Manual wiring of custom implementations
<repositories base-package="com.acme.repository" />

<beans:bean id="userRepositoryImpl" class="…">
  <!-- further configuration -->
</beans:bean>

 

3.6.2. 모든 리파지토리들에 커스텀 행동 추가하기

만약 당신이 모든 리파지토리 인터페이스에 하나의 메소드를 추가하고 싶을 때 이전의 접근들은 실현가능하지가 않았습니다.

  1. 모든 리파지토리에서 커스텀 행동을 추가하기 위해, 먼저 공유행동으로 선언할 중계인터페이스를 추가합니다.
    예제 22. 커스텀 공유 행동 선언하는 인터페이스
    @NoRepositoryBean
    public interface MyRepository<T, ID extends Serializable>
      extends PagingAndSortingRepository<T, ID> {
    
      void sharedCustomMethod(ID id);
    }
  2. 이제 당신의 독립적인 리파지토리 인터페이스는 Repository대신에 이 중계인터페이스를 상속하고, 선언된 기능을 포함할 것입니다.
  3. 다음으로, 영속기술특징 리파지토리 빈 클래스를 상속하는 중계인터페이스의 구현체를 생성합니다. 이 클래스는 리파지토리 프록시를 위한 커스텀 base 클래스로 동작할 것입니다 .
    예제 23. 커스텀 리파지토리 베이스 클래스 Custom repository base class
    public class MyRepositoryImpl<T, ID extends Serializable>
      extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
    
      private final EntityManager entityManager;
    
      public MyRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
    
        // Keep the EntityManager around to used from the newly introduced methods.
        this.entityManager = entityManager;
      }
    
      public void sharedCustomMethod(ID id) {
        // implementation goes here
      }
    }
    spring의 <repositories /> 네임스페이스의 기본행동은 base-package아래에 있는 모든 인터페이스들에 대한 구현체를 제공하는 것입니다. 이것은 현재 상태로 남아있다면 구현체는 MyRepository의 인스턴스가 스프링에 의해 생성된다는 것을 의미합니다. 물론 이것은 Repository와 실제 엔티티를 위해 정의한 리파지토리 인터페이스간에서 중계자로 행동하기를 바라고 있는 와중에 의도한 것이 아닙니다. Repository를 상속하는 인터페이스를 제외시키기 위해 당신은 그것을 @NoRepositoryBean로 어노테이션하거나, base-package바깥으로 제외시켜줘야할 것입니다.
  4. 그러고 난 후에 커스텀 리파지토리 팩토리를 만들어 기본 RepositoryFactoryBean를 대체하여 커스텀 RepositoryFactory을 만들어봅시다. 그러면 새로운 리파지토리 팩토리는 당신에게 Repository인터페이스를 상속하는 어떤 인터페이스의 구현체로써 MyRepositoryImpl를 제공할 것이고, 이것은 SimpleJpaRepository 구현체를 대체할 것입니다.

    예제 24. 커스텀 리파지토리 팩토리 빈 Custom repository factory bean
    public class MyRepositoryFactoryBean<R extends JpaRepository<T, I>, T,
      I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
    
      protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
        return new MyRepositoryFactory(em);
      }
    
      private static class MyRepositoryFactory<T, I extends Serializable>
        extends JpaRepositoryFactory {
    
        private final EntityManager em;
    
        public MyRepositoryFactory(EntityManager em) {
    
          super(em);
          this.em = em;
        }
    
        protected Object getTargetRepository(RepositoryMetadata metadata) {
          return new MyRepositoryImpl<T, I>((Class<T>) metadata.getDomainClass(), em);
        }
    
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
          return MyRepositoryImpl.class;
        }
      }
    }
  5. 마침내, 커스텀 팩토리를 직접적으로 선언을 하거나, 스프링 네임스페이스의 factory-class 속성을 사용하거나 @Enable…어노테이션으로 repository 인프라스트럭쳐를 설정하여서, (마침내) 당신의 커스텀 팩토리 구현체를 사용하게 됩니다.
    예제 25. 커스텀 팩토리와 네임스페이스 사용하기 Using the custom factory with the namespace
    <repositories base-package="com.acme.repository"
      factory-class="com.acme.MyRepositoryFactoryBean" />
    예제 26. @Enable… 어노테이션과 함께 커스텀 팩토리 사용하기
    @EnableJpaRepositories(factoryClass = "com.acme.MyRepositoryFactoryBean")
    class Config {}

 

3.7. 스프링 데이터 익스텐션

 

이 섹션문서는 스프링 데이터 익스텐션(확장)문서의 모음으로 스프링 데이터 익스텐션은 스프링 데이터의 사용을 다양한 context로써, 가능케 해줍니다. 현재는 대부분의 통합이 SpringMVC를 대상으로합니다.

 

3.7.1. Web support

 
이 섹션은 스프링 데이터 웹서포트를 위한 문서를 포함하며 스프링 데이터 웹서포트는 1.6 범위에서 스프링데이터 Commons로 구현되었습니다. 새롭게 소개된 지원이 꽤 많은 것들을 변화시켜서, 이전의 행동에 관한 문서를 우리는 레거시 웹 지원에 가지고 있습니다.

스프링 데이터 모듈은, 리파지토리 프로그래밍 모델을 지원하는 모듈이라면 다양한 방식의 웹지원을 가지고 있습니다. 웹관련 작업은 classpath의 SpringMVC JARs를 필요로 하며, 그들의 일부는 심지어 Spring Hateoas[2]를 제공하기도 합니다. 일반적으로 통합 지원은 자바설정클래스에서 @EnableSpringDataWebSupport 어노테이션을 사용함으로써 활성화됩니다.

예제 27. 스프링 데이터 웹지원 활성화 Enabling Spring Data web support
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration { }

@EnableSpringDataWebSupport 어노테이션은 우리가 뒤에 나올 새로운 컴포넌트를 등록시킵니다. 이것은 클래스패스의 Spring HATEOAS를 감지하여서, hateoas가 있다면 통합 컴포넌트로써 등록을 시킵니다.

 

아니면 만약 당신이 XML설정을 쓴다면, SpringDataWebSupport나 HateoasAwareSpringDataWebSupport를 스프링 빈으로 등록합니다.

 

예제 28. XML에서의 스프링 데이터 웹서포트 활성화 Enabling Spring Data web support in XML
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />

<!-- If you're using Spring HATEOAS as well register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
기본 웹 지원
위에 보이는 설정 셋업은 몇가지 기초 컴포넌트들을 등록할 것입니다
  •  

    DomainClassConverter 는 SpringMVC 를 활성화시켜서 요청파라미터나 경로변수로부터 리파지토리 managed도메인클래스를 resolve합니다.

     

  •  

    HandlerMethodArgumentResolver 는 스프링 MVC로 하여금 요청파라미터로부터 Pageable 과 Sort 인스턴스를 resolve하게 해줍니다.

     

도메인 클래스 콘버터 : DomainClassConverter
DomainClassConverter는 SpringMVC 컨트롤러 시그니처에서 직접적으로 도메인 타입을 사용하게 해줍니다. 그래서 당신은 수동적으로 리파지토리를 통해 인스턴스를 찾을 필요가 없습니다.
Example 29. 메소드 시그니처에서 도메인 타입을 사용하는 스프링 MVC 컨트롤러
@Controller
@RequestMapping("/users")
public class UserController {

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {

    model.addAttribute("user", user);
    return "userForm";
  }
}
당신이 볼 수 있듯, 메소드가 유저 인스턴스를 직접적으로 받고 더 이상의 검색은 필요하지가 않습니다. 저 인스턴스는 SpringMVC가 경로변수를 도메인 클래스의 id타입으로 전환하게 하여, 결국 도메인 타입에 등록된 리파지토리에서 findOne(…)을 호출하게 합니다.
 
현재 저 리파지토리는 CrudRepository 를 구현을 하여서 전환을 위한 자격을 얻어야 합니다 .
 
페이지화와 정렬을 위한 HandlerMethodArgumentResolvers

위의 소스설정 일부는 또한 PageableHandlerMethodArgumentResolver를 등록하고 SortHandlerMethodArgumentResolver의 인스턴스를 등록합니다. 이 등록은 Pageable 과 Sort가 유효한 컨틀롤러 메소드 아규먼트가 되게 해줍니다 .

예제 30. Pageable을 컨트롤러 메소드 아규먼트로 사용하기. Using Pageable as controller method argument
@Controller
@RequestMapping("/users")
public class UserController {

  @Autowired UserRepository repository;

  @RequestMapping
  public String showUsers(Model model, Pageable pageable) {

    model.addAttribute("users", repository.findAll(pageable));
    return "users";
  }
}
이 메소드 시그니처는 다음의 설정을 사용하여 스프링 MVC가 요청 파라미터로부터 Pageable 인스턴스를 만들어내게 시도합니다.
표 1. Pageable 인스턴스를 위해 평가된 요청파라미터 Request parameters evaluated for Pageable instances

page

얻기 원하는 페이지, 0 indexed and 기본은 0.

size

얻기 원하는 페이지 크기, 기본 20.

sort

다음의 형식으로 정렬될 형식 property,property(,ASC|DESC). 기본 정렬 방향은 오름차순(asc). 만약 방향을 바꾸고 싶은 여러개의sort 파라미터가 있다면 다음과 같이.., e.g. ?sort=firstname&sort=lastname,asc.

이러한 행동을 커스터마이징 하고 싶다면 @Enable-어노테이션을 사용하는 대신에 SpringDataWebConfiguration를 상속하거나 HATEOAS-활성화 같은 것을 하거나, pageableResolver()sortResolver()메소드를 오버라이드하고 당신의 커스터마이징된 설정파일을 임포트하세요.

이러한 경우 당신은 여러개의 테이블을 위해서, 요청으로부터 여러개의 Pageable 나 Sort 인스턴스가 resolved되기를 필요로 할지도 모릅니다. 예를 들자면 당신은 스프링의 @Qualifier어노테이션을 사용하여 다른 것들끼리 구별을 할 수도 있습니다. 요청파라미터는 그러면 ${qualifier}_로 prefixed됩니다. 그래서 메소드 시그니처가 다음과 같이 됩니다 :

public String showUsers(Model model,
      @Qualifier("foo") Pageable first,
      @Qualifier("bar") Pageable second) {  }
당신은 foo_page 나 bar_page같은 것을 만들어낼 것입니다 .
메소드에 전해진 기본 Pageable 는 new PageRequest(0, 20)와 같습니다만, Pageable 파라미터에서 @PageableDefaults 어노테이션을 통해서 커스터마이징 될 수 있습니다.
Hypermedia support for Pageables
Spring HATEOAS는 표현 모델 클래스 PagedResources를 가지고 있어서 Page인스턴스를, 필요한 Page메타데이터로 풍부하게 해줄 수 있을 뿐만 아니라, 링크가 클라이언트가 쉽게 페이지를 네비게이트하게 해줍니다. Page에서 PagedResources으로의 전환은 Spring HATEOAS ResourceAssembler 인터페이스, PagedResourcesAssembler를 구현함으로써 이뤄집니다. .
예제 31.PagedResourcesAssembler를 컨트롤러 메소드 아규먼트로 사용하기. Using a PagedResourcesAssembler as controller method argument
@Controller
class PersonController {

  @Autowired PersonRepository repository;

  @RequestMapping(value = "/persons", method = RequestMethod.GET)
  HttpEntity<PagedResources<Person>> persons(Pageable pageable,
    PagedResourcesAssembler assembler) {

    Page<Person> persons = repository.findAll(pageable);
    return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
  }
}

위 처럼 설정을 활성화하는 것은 PagedResourcesAssembler가 컨트롤러 메소드 아규먼트로 사용되게 해줍니다. 여기서 toResources(…) 를 호출하는 것은 다음과 같은 것을 발생하게 해줍니다 :

  • PagedResources는, 동봉되어 채워진 정보와 함께 PageMetadata 인스턴스를 얻어서 Page와 기본 PageRequest를 형성합니다.

  • PagedResources는 같이 있는 페이지의 상태에 따라 prev와 next 연결을 얻습니다.. 이 링크는 메소드실행이 연결된 URI를 가리키며, 그 메소드에 추가된 페이지네이션 파라미터는 PageableHandlerMethodArgumentResolver의 설정과 매칭이 되어 링크가 나중에 resolved되게 해줍니다.
  • Page의 내용이 PagedResources인스턴스의 내용(content)이 되게 해줍니다

우리가 30명의 인원 인스턴스를 데이터베이스에 가지고 있다고 해봅시다. 당신은 이제 GET http://localhost:8080/persons요청을 해볼 것이고, 이러한 결과를 보게 될 것입니다

{ "links" : [ { "rel" : "next",
                "href" : "http://localhost:8080/persons?page=1&size=20 }
  ],
  "content" : [
      // 20 Person instances rendered here
  ],
  "pageMetadata" : {
    "size" : 20,
    "totalElements" : 30,
    "totalPages" : 2,
    "number" : 0
  }
}

당신은 정확한 URI를 만들어내고서, 기본 설정이 앞으로 들어오는 요청에 대해 파라미터를 Pageable로 resolve하는 것을 볼 수가 있습니다. 이것이 의미하는 바는 만약 당신이 저 설정을 바꾼다면 이 연결은 자동적으로 바뀔 것입니다. (역주 : 파라미터값이 바뀌면 page metadata도 바뀐다 이말인듯?) 기본적으로 assembler는 실행될 컨트롤러 메소드를 가리킵니다. 하지만 커스텀 Link를 제시하여 커스터마이징할 수 있고, PagedResourcesAssembler.toResource(…)의 메소드를 오버라이딩하여 페이지네이션 링크를 만드는 데 기반이 될 수 있습니다 .

 

3.7.2. 레파지토리 생성자(popluators)

만약 당신이 Spring JDBC 모듈과 함께 작업한다면 당신은 아마 SQL 스크립트를 이용하는 DataSource를 생성하는데 익숙할 것입니다. 비슷한 추상화가 리파지토리 레벨에서 가능한데요. 비록 DDL로써 sql을 사용하지는 않습니다. 왜냐하면 sql은 저장소 의존적이기 때문이죠 . 그렇기 때문에 populator는 XML(스프링의 OXM추상화를 통해)과 JSON(Jackson을 통해)으로 repository에 생성할 데이터를 정의합니다.

당신이 data.json 파일을 다음과 같이 가지고 있다고 가정해봅시다 :

예제 32. JSON으로 정의된 데이터
[ { "_class" : "com.acme.Person",
 "firstname" : "Dave",
  "lastname" : "Matthews" },
  { "_class" : "com.acme.Person",
 "firstname" : "Carter",
  "lastname" : "Beauford" } ]

당신은 Spring Data Commons 에서 제공하는 리파지토리 네임스페이스의 populator 요소를 사용하여서 당신의 리파지토리를 쉽게 populate할 수 있습니다. 이전의 data를 당신의 리파지토리에 populate하기 위해 다음과 같이 해보세요 :

Example 33. Jackson Repository populator선언하기 Declaring a Jackson repository populator
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:repository="http://www.springframework.org/schema/data/repository"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    http://www.springframework.org/schema/data/repository/spring-repository.xsd">

  <repository:jackson-populator locations="classpath:data.json" />

</beans>
이 선언은 data.json이 읽혀져서 Jackson ObjectMapper를 통해 역직렬화 되게 해줍니다 .

JSON객체가 언마샬될 타입은 JSON문서의 _class 속성을 검사하면서 결정됩니다. 인프라스트럭쳐가 결국 적절한 리파지토리를 선택해서 막 역직렬화된 객체를 다룰 것입니다.

XML을 사용하여 리파지토리들을 채울 데이터를 정의하고 싶다면, 당신은 unmarshaller-populator요소를 사용할 수 있습니다. 당신은 SPRING OXM 공급자가 제공하는 XML marshaller 옵션 중의 하나를 사용하여서 설정을 할 수가 있습니다. 스프링 레퍼런스 가이드 문서를 살펴보세요 (역주 : 번역문서라 링크가 깨짐.. { }뭐 이런 변수가 되있다보니;; ㅎㅎ )

Example 34. 언마샬링 repository populator(JAXB를 사용한)을 선언하기
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:repository="http://www.springframework.org/schema/data/repository"
  xmlns:oxm="http://www.springframework.org/schema/oxm"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    http://www.springframework.org/schema/data/repository/spring-repository.xsd
    http://www.springframework.org/schema/oxm
    http://www.springframework.org/schema/oxm/spring-oxm.xsd">

  <repository:unmarshaller-populator locations="classpath:data.json"
    unmarshaller-ref="unmarshaller" />

  <oxm:jaxb2-marshaller contextPath="com.acme" />

</beans>

 

3.7.3. 레거시 웹 지원
 
스프링 MVC를 위한 도메인 클래스 웹 바인딩

 

당신이 SPRING MVC웹 어플리케이션으로 개발중이고 일반적으로 URL에서 도메인 클래스 id를 URL에서 resolve해야할 것입니다. 기본적으로 할일은 그 리퀘스트 파라미터나 URL의 부분을 도메인 클래스로 전달하여 하위 레이어로 전달하고 엔티티들에 대한 직접적인 비즈니스 로직을 수행해야 할 것입니다. 이것은 다음과 같이 보일 것입니다 :

 

@Controller
@RequestMapping("/users")
public class UserController {

  private final UserRepository userRepository;

  @Autowired
  public UserController(UserRepository userRepository) {
    Assert.notNull(repository, "Repository must not be null!");
    this.userRepository = userRepository;
  }

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") Long id, Model model) {

    // Do null check for id
    User user = userRepository.findOne(id);
    // Do null check for user

    model.addAttribute("user", user);
    return "user";
  }
}

먼저 당신은 각각의 컨트롤러에 대한 각각의 의존선을 선언하여 컨트롤러나 repository에서 각자 엔티티들을 찾아야 합니다. 엔티티를 찾는 것은 또한 보일러플레이트한 작업이 될 수가 있으면 이것은 언제나 findOne(…) 을 호출합니다. 다행스럽게도 스프링은 String값을 무작위의 값으로 전환시킬수 있는 커스텀 컴포넌트를 등록하게 해주는 방법을 제공합니다.

PropertyEditors

스프링 버젼 3.0 이전의 간단한 자바 PropertyEditors 가 사용되어졌었습니다. 이것과 통합하기 위해, 스프링 데이터는 DomainClassPropertyEditorRegistrar를 제공하며 이것은 ApplicationContext 에 등록된 모든 스프링 데이터 리파지토리들을 찾고, 관리되는 도메인 클래스들을 위해서 커스텀 PropertyEditor를 등록시킵니다.

<bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="webBindingInitializer">
    <bean class="….web.bind.support.ConfigurableWebBindingInitializer">
      <property name="propertyEditorRegistrars">
        <bean class="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" />
      </property>
    </bean>
  </property>
</bean>

만약 당신이 이전의 예제에서 SPRING MVC를 설정했다면, 당신은 당신의 컨트롤러를 다음과 같이 설정할 수 있으며, 많은 어수선한 부분과 보일러플레이트한 부분을 줄일 수가 있을 것입니다.

@Controller
@RequestMapping("/users")
public class UserController {

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {

    model.addAttribute("user", user);
    return "userForm";
  }
}

 

레퍼런스 문서

 

4. JPA 리파지토리들

이 챕터는 JPA 리파지토리지원에 대한 특별한 점들을 얘기해볼까합니다. 이것은 Working with Spring Data Repositories에서 설명했던 핵심 리파지토리 지원에서 시작합니다(빌드를 의역). 그러니, 저기서 설명한 기본 개념을 충분히 이해하시고 시작하겠습니다.

 

4.1. 소개

 

4.1.1. 스프링 네임스페이스

스프링 데이터의 JPA모듈은 커스텀 네임스페이스를 가지고 있어서 리파지토리 빈을 정의하는 것을 허용합니다.이것은 또한 JPA에 좀 더 특별화되어있는 어떤 특징과, 요소 속성들을 포함하고 있습니다. 일반적으로 JPA리파지토리는 repositories 요소를 사용하여 설정가능합니다 :

Example 35. 네임스페이스를 사용하여 JPA 리파지토리들 설정하기
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:jpa="http://www.springframework.org/schema/data/jpa"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

  <jpa:repositories base-package="com.acme.repositories" />

</beans>

이 요소를 사용하여서, 리파지토리 인스턴스 생성하기에서 설명된 스프링 데이터 리파지토리들을 찾습니다. 그 뒤에서는(Beyond That) @Repository가 붙은 모든 빈에서는 영속 예외 전환이 활성화되어서 JPA 영속화 provider에 의해 던져지는 예외들은 스프링의 DataAccessException hierarchy로 전환되게 됩니다.

 
커스텀 네임스페이스 속성

repositories 요소 다음에 , JPA 네임스페이스는 repositories의 준비를 하며 세부적인 control 을 얻는 추가적인 속성을 제공합니다. :

표2. 리파지토리 요소의 커스텀 JPA-특정 속성

entity-manager-factory-ref

명시적으로 EntityManagerFactory를 wire하면서, repositories요소에 의해 감지되는 리포지들과 함께 사용될 수 있습니다. 만약 여러개의 EntityManagerFactory빈이 어플리케이션이 사용될 때 보통 사용됩니다. 이것이 설정되어있지 않다면, 우리는 자동적으로 EntityManagerFactory 를 ApplicationContext에서 entityManagerFactory의 이름으로 찾습니다.

 

transaction-manager-ref

repositories요소에 의해 감지되는 리파지도리들과 함께 사용되는 PlatformTransactionManager를 wire 합니다. 보통 여러개의 트랜잭션 매니저 와/아니면(and/or) EntityManagerFactoryq빈이 설정되었을 때 필요로 합니다. 기본값은 현재 ApplicationContext 안에 정의된 단일 PlatformTransactionManager입니다.

만약 어떠한 명시적 transaction-manager-ref가 정의되지 않았다면, transactionManager 로 이름지어진 PlatformTransactionManager빈이 있어야한다는 것을 알아둡시다.

 

4.1.2. 어노테이션 기반 설정

스프링 데이터 리파지토리는 XML네임스페이스뿐만이 아니라 어노테이션 기반의 자바설정을 통해서도 설정을 지원합니다.

Example 36. 자바설정을 이용한 스프링 데이터 JPA리자피토리 설정
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {

  @Bean
  public DataSource dataSource() {

    EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
    return builder.setType(EmbeddedDatabaseType.HSQL).build();
  }

  @Bean
  public EntityManagerFactory entityManagerFactory() {

    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    vendorAdapter.setGenerateDdl(true);

    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    factory.setJpaVendorAdapter(vendorAdapter);
    factory.setPackagesToScan("com.acme.domain");
    factory.setDataSource(dataSource());
    factory.afterPropertiesSet();

    return factory.getObject();
  }

  @Bean
  public PlatformTransactionManager transactionManager() {

    JpaTransactionManager txManager = new JpaTransactionManager();
    txManager.setEntityManagerFactory(entityManagerFactory());
    return txManager;
  }
}

방금 보여드린 설정 클래스는 spring-jdbc 의 EmbeddedDatabaseBuilder API 를 사용하여 내장 HSQL 데이터베이스를 설정합니다. 우리는 그 후에 EntityManagerFactory를 설정하고 Hinbernate 를 샘플 영속 provider로 사용합니다. 마지막 인프라스트럭쳐 컴포넌트는 여기서 JpaTransactionManager로 선언되었습니다. 우리는 마침내 Spring data jpa 리파지토리들을, XML 네임스페이스가 필수적으로 가지고 있는 속성과 같은 @EnableJpaRepositories 어노테이션을 사용하여 활성화시켰습니다.

 

4.2. 엔티티들 영속화하기(Persisting)

 

4.2.1. 엔티티들 저장하기(Saving)

엔티티를 저장하는 것은 CrudRepository.save(…)-메소드를 통해서 이뤄질 수 있습니다. 이것은 JPA EntityManager를 사용하여 주어진 엔티티를 persist하거나 merge할 것입니다. 만약에 엔티티가 아직 persisted되지 않았다면, 스프링 데이터 JPA는 entityManager.persist(…)메소드에 대한 호출을 통해, 엔티티를 저장할 것입니다. 그렇지 않으면 entityManager.merge(…)메소드가 호출될 것입니다.

 
엔티티 상태 감지 전략

스프링 데이터 JPA는 다음 전략을 사용하여서 엔티티가 새로운 것인지 아닌지 감지합니다 :

표3. 스프링 데이터 JPA 에서 엔티티가 새로운 것인지 아닌지 감지하는 것을 위한 옵션들

Id-Property inspection (default)

기본적으로 스프링 데이터 JPA에서는 주어진 엔티티의 식별자 속성을 검사를 합니다.(역주 : ID겠지..?) 만약 식별자 속성이 null이라면, 엔티티는 new 라고 가정될 것이고 그렇지 않으면 new가 아닙니다 .

Implementing Persistable

만약 엔티티가 Persistable을 구현한다면, Spring Data JPA 는 엔티티의 isNew(…)메소드에게 새로운 감지를 위임할 것입니다.세부사항은 JavaDoc을 보시길 바랍니다..

Implementing EntityInformation

당신은 JpaRepositoryFactory의 하위클래스를 생성하고 getEntityInformation(…)메소드를 오버라이딩함으로써, SimpleJpaRepository구현체에서 쓰이는 EntityInformation 추상화를 커스터마이징 할 수가 있습니다. 그리고 당신은 JpaRepositoryFactory의 커스텀 구현체를 스프링 빈으로 등록해야 합니다. 이것은 거의 필요로 하지 않는 다는 것을 알아둡시다. 더 많은 정보가 필요로 하면 JavaDoc을 봅시다.

 

4.3. 쿼리 메소드들

 

4.3.1. 쿼리 탐색 전략

JPA 모듈은 쿼리를 수동으로 문자열이나, 메소드네임에서 유추하는 방식으로 정의하는 것을 지원합니다.

 
쿼리들 선언

비록 메소드 네임으로 쿼리를 얻는 것이 편리하지만, 어떤 경우에는 메소드네임 파서가 사용하고자 하는 키워드를 지원하지 않거나, 메소드 네임이 불필요하게 보기 불편해질 때가 있을 것입니다. 그럴때 당신은 JPA named qeury 를 사용하거나(더 많은 정보는 Using JPA NamedQueries를 참조 ) 당신의 쿼리 메소드에 @Query어노테이션을 붙일 수도 있을 것입니다.(더 많은 정보 : Using @Query 참조)

 

4.3.2. 쿼리 생성

일반적으로 JPA 에서 동작하는 쿼리 생성 메커니즘은 쿼리메소드 - 여기에 설명되어있습니다. 여기 JPA 쿼리 메소드 변환이 이뤄지는 짧은 예제가 있습니다. :

예제 37. 메소드 이름으로 쿼리 생성
public interface UserRepository extends Repository<User, Long> {

  List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}

우리는 여기서 JPA 크리테리아 API를 이용하여서 쿼리를 생성할 것입니다. 하지만 기본적으로 이 전환은 다음 쿼리로 될 것입니다. select u from User u where u.emailAddress = ?1 and u.lastname = ?2. 스프링 데이터 JPA는 프로퍼티 검사를 할 것이며 Property expressions에 설명된 중첩 프로퍼티들을 횡단(역주: 횡단 검사한다. 필드를 짜맞춰본다 이런뜻인듯하다. )해볼 것입니다. 여기에 JPA를 위해 지원되는 키워드들에 대한 오버뷰와 기본적으로 전환되는 키워드들을 포함하고 있는 메소드에 대한 오버뷰가 나옵니다.

표 4. 메소드 이름 안에서 지원되는 키워드들
Keyword Sample JPQL snippet

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstname,findByFirstnameIs,findByFirstnameEquals

… where x.firstname = 1?

Between

findByStartDateBetween

… where x.startDate between 1? and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age ⇐ ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… 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 not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1(parameter bound with appended %)

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1(parameter bound with prepended %)

Containing

findByFirstnameContaining

… where x.firstname like ?1(parameter bound wrapped in %)

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> age)

… where x.age not in ?1

True

findByActiveTrue()

… where x.active = true

False

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

… where UPPER(x.firstame) = UPPER(?1)

 

In 이나 NotIn은 또한 Collection의 하위클래스를 받거나 배열, 가변인자들을 받을 수 있습니다. 같은 논리적 연산 검사에 대한 다른 문법적인 내용들은 Repository query keywords를 보시길 바랍니다..

 

4.3.3. JPA NamedQuery들 사용하기
 

이 예제는 간단하게 <named-query />요소와 @NamedQuery어노테이션을 사용합니다. 이 설정 요소들을 위한 쿼리는 JPA 쿼리 언어로 정의되어야 합니다. 물론 당신은 <named-native-query />나 @NamedNativeQuery또한 사용할 수 있습니다. 이러한 요소들은 데이터베이스 플랫폼 독립성을 잃으면서 , native SQL 문법을 사용하게 합니다.

 
XML named query 정의

XML 설정을 단순히 사용하기 위해서 필요한 <named-query /> 요소를 클래스패스의 META-INF내에 위치한 orm.xml JPA 설정 파일에 넣어주세요. named query들의 자동적인 실행이 이름 컨벤션에 의해서 활성화됩니다. 더 많은 정보는 다음과 같습니다.

Example 38. XML 네임드 쿼리 설정
<named-query name="User.findByLastname">
  <query>select u from User u where u.lastname = ?1</query>
</named-query>

당신이 보듯이 query 는 런타임에서 resolve 되는데 사용되는 특수한 이름을 가지고 있어야 합니다.

 
어노테이션 설정

어노테이션 설정은 (아마 유지보수 비용이 줄어들 것같은) 다른 설정 파일이 필요하지 않다는 장점이 있습니다. 당신은 그러한 장점을 위해서, 각각의 새로운 쿼리 선언을 할 때마다 당신의 도메인 클래스를 재컴파일 해줄 필요가 있습니다.

예제 39. 어노테이션 기반의 named query 설정
@Entity
@NamedQuery(name = "User.findByEmailAddress",
  query = "select u from User u where u.emailAddress = ?1")
public class User {

}
 
인터페이스들 선언하기

이러한 named query 를 실행하기 위해서는 당신이 해야할 것은 UserRepository에 다음과 같이 명시해주는 것입니다. :

Example 40. UserRepository 에 선언된 쿼리 메소드
public interface UserRepository extends JpaRepository<User, Long> {

  List<User> findByLastname(String lastname);

  User findByEmailAddress(String emailAddress);
}

스프링 데이터는 이러한 namedquery에 대한 메소드들의 호출을 해석할 것입니다. 설정된 도메인 클래스의 간단한 이름과 함께 시작하기 위해, 다음의 메소드 이름은 dot(.)으로 분리됩니다. 그래서 여기의 예제에서는 메소드 이름으로 쿼리를 만드는 대신에 위의 정의된 named query를 사용하였습니다.

 

4.3.4. @Query 사용하기

엔티티들을 위해서 쿼리들을 선언하는 named 쿼리를 사용하는 것은 적은 수의 쿼리들에 대해 잘 동작하는 유효한 접근입니다. 쿼리 그들 스스로가 그들을 실행하는 자바메소드로 묶이는데, 당신은 실제적으로 Spring data jpa의 @Query메소드들 통해서 그들을 도메인 클래스에 어노테이션 하지 않고 직접적으로 바인딩할수가 있습니다. 이것은 도메인 클래스에서 영속 특징을 가진 정보를 없애게 해주며, 쿼리를 리파지토리 인터페이스근처로 위치하게 해줍니다.

query method에 어노테이션된 쿼리들은 @NamedQuery나 orm.xml에 선언된 namedquery 에 비해서 우선시(precendence)됩니다.

Example 41. @Query를 사용하여서 쿼리메소드에 쿼리 선언하기
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.emailAddress = ?1")
  User findByEmailAddress(String emailAddress);
}

LIKE같이 advanced한 표현을 사용해볼까요. 수동적으로 @Query를 사용해 정의된 쿼리를 위한, 쿼리 실행 메커니즘은 쿼리 정의 내에서 LIKE 표현의 정의를 허용합니다. (역주 : 영어표현에 군살이 많은데 그걸 그대로 옮겨적으면서 하다보니 보일러 플레이트한^^; 표현이 많은 것같기도 한 느낌이 듭니다. 가끔 주어없는 문장도 나오고, 오타도 있고 뭐..번역하기 애매한.. 그런것도 많아요.. 그냥 번역하다가 궁시렁 궁시렁 해봅니다.)

Example 42. @Query 내에서 like같은 고급 표현
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.firstname like %?1")
  List<User> findByFirstnameEndsWith(String firstname);
}

방금전에 본 LIKE 구별자 문자 %가 인식이 되면서, 유효한 JPQL쿼리(%를 삭제하면서)로 변형되는 쿼리가 되게 됩니다. 쿼리 실행동안 메소드 호출쪽에 전해지는, 파라미터가 전에 인식된 LIKE패턴에 아규먼트화됩니다. (역주 : 파라미터가 like쪽에 간다. 이말인듯 ;; )

네이티브 쿼리들. @Query 어노테이션은 nativeQuery를 true로 설정해서 네이티브 쿼리의 실행을 허용합니다. 우리는 현재 네이티브 쿼리를 위한 페이지네이션과 동적 정렬을 지원하지 않는다는 것을 알아둡시다. (우리가 실제 선언된 쿼리를 만들어야하고, 우리는 Native SQL에 확실하게 이것-동적정렬, 페이징을 할수 없기 때문에 )

Example 43. @Query를 사용하여 쿼리 메소드에서 네이티브 쿼리 선언하기
public interface UserRepository extends JpaRepository<User, Long> {

  @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?0", nativeQuery = true)
  User findByEmailAddress(String emailAddress);
}

4.3.5. Using named parameters

기본적으로 Spring Data JPA 는 예전의 샘플에서도 설명했듯이, 파라미터 바인딩에 기반한 포지션을 사용합니다. 이것은 파라미터 포지션에 대해서 쿼리 메소드가 리팩토링해야할 작은 에러들을 만들기 쉽게 합니다. 이것을 해결하기 위해서 당신은 @Param 어노테이션을 이용하여서, 메소드 파라미터에 구체적인 이름을 주고 쿼리에 그 이름으로 바인딩시킬 수가 있습니다.

Example 44. named parameter 이용하기
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
  User findByLastnameOrFirstname(@Param("lastname") String lastname,
                                 @Param("firstname") String firstname);
}

정의된 쿼리의 발생에 따라 메소드 파라미터가 교환됩니다.

 

4.3.6. SpEL 표현 사용하기

Spring data 1.4 버젼부터 우리는 제한된 SpEL템플릿 표현을 @Query를 통해서 수동으로 정해진 쿼리에, 사용할 수 있게 하였습니다. 쿼리가 실행되면서 이러한 표현들은 미리 정의된 변수들의 집합에 대하여 평가됩니다. 우리는 수동쿼리에서 사용될 다음 변수들의 리스트를 지원합니다.

Table 5. 쿼리 템플릿 기반에서 SpEL 안의 지원되는 변수들
변수 사용 설명

entityName

select x from #{#entityName} x

주어진 repository에 관련된 도메인 타입의 entityName 를 삽입하세요. entityName는 다음과 같이 해석됩니다 : 만약 @Entity어노테이션에서 도메인 타입 이름을 정한다면 , 그것은 사용될 것입니다. 그렇지 않으면 도메인 타입의 간단한 class-name이 사용될 것입니다. (역주: 그냥 뒤의 예제들을 조금 살펴보자^^; )

다음 예제는 쿼리 문자열에서 #{#entityName} 표현의 사용 예제를 하나 보여줍니다. (일부러 짧은 표현 쓰다.) @Query어노테이션의 쿼리 문자열에서 실제적인 엔티티 이름을 기술하지 않기 위해서 #{#entityName}변수를 쓴 것입니다.

 

entityName은 @Entity어노테이션을 통해 커스터마이징 됩니다. orm.xml를 통한 커스텀화는 SpEL표현에서 지원되지 않습니다.

Example 45. entityName - 리파지토리 쿼리 메소드에서 SpEl표현을 사용하기
@Entity
public class User {

  @Id
  @GeneratedValue
  Long id;

  String lastname;
}

public interface UserRepository extends JpaRepository<User,Long> {

  @Query("select u from #{#entityName} u where u.lastname = ?1")
  List<User> findByLastname(String lastname);
}

물론 당신은 쿼리 선언에서 직접적으로 User 를 직접적으로 사용했을 것입니다만 이것은 쿼리를 변경할 것을 요구합니다.(역주: 나중에 변경한다는 얘기인듯?).. #entityName는 미래에 잠재적으로, 다른 엔티티 이름을 가지는(예를 들자면 @Entity(name = "MyUser"를 사용하는) User클래스의 재매핑을 선택합니다.

쿼리 문자열에서 다른 #{#entityName} 표현의 유스케이스는, 만약 당신이 제너릭 리파지토리 인터페이스를 구체적인 도메인 타입을 가진 특별화된 리파지토리 인터페이스와 같이 사용하고 싶을 때입니다. 구체적인 인터페이스에서 커스텀 쿼리 메소드들의 반복적인 정의를 피하기 위해, 당신은 제너릭 리파지토리 인터페이스의 @Query어노테이션의, 쿼리문자열에서- 엔티티네임 표현을 사용할 수가 있습니다.

Example 46. SpEL 표현을 리파지토리 쿼리 메소드에서 사용하기 - entityName with inheritance
@MappedSuperclass
public abstract class AbstractMappedType {
  
  String attribute
}

@Entity
public class ConcreteType extends AbstractMappedType {  }

@NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
  extends Repository<T, Long> {

  @Query("select t from #{#entityName} t where t.attribute = ?1")
  List<T> findAllByAttribute(String attribute);
}

public interface ConcreteRepository
  extends MappedTypeRepository<ConcreteType> {  }

이 예제에서 MappedTypeRepository 인터페이스는, AbstractMappedType를 상속하는 적은 도메인들을 위한 평범한 부모 인터페이스입니다. 이것은 또한 제너릭 메소드findAllByAttribute(…)를 정의하여 이것은 구체화된 리파지토리 인터페이스의 인스턴스에서 사용될 수 있습니다. 만약 당신이 ConcreteRepository에서의 findByAllAttribute(…)를 실행시킨다면, 실행되는 쿼리는 select t from ConcreteType t where t.attribute = ?1가 될 것입니다.

 

4.3.7. 쿼리 수정하기 - Modifying queries

위의 모든 섹션들은 어떻게 주어진 엔티티나 엔티티들의 콜렉션에 접근하는 쿼리들을 선언하는 지를 설명합니다. 물론 당신은 Custom implementations for Spring Data repositories에 설명한 기능들을 이용하여서 커스텀 수정 행동을 추가할 수도 있습니다. 이러한 접근은 포괄적인 커스텀 기능에서 실현가능하지만, 당신은 실제로, 쿼리 메소드에 @Modifying로 어노테이션 하면서, 오직 파라미터 바인딩만을 필요로 하는 수정쿼리의 실행을 할 수 있습니다.

Example 47. manipuling 쿼리들의 정의
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);

이것은 하나를 선택하는 대신에 updating하는 쿼리를 동작시킵니다. EntityManager가 아마 수정쿼리의 실행한 이후에 오래된 엔티리를 가지고 있는 까닭에, 우리는 자동적으로 clear it 할 필요가 없습니다. (세부적인 사항을 보려면 자바Doc의 EntityManager.clear() 를 보세요). 이것은 EntityManager안에서 여전히 대기중인 모든 flush되지 않은 변화들을 효과적으로 drop할 것이기 때문입니다. 만약 당신이 EntityManager가 자동적으로 cleared되기를 원한다면, 당신은 @Modifying어노테이션의 clearAutomatically속성을 true로 하면 됩니다. (역주: 조회수 같은 것 올릴 때 이 속성을 걸고 할 때가 있었다. 테스트하면서 이상하게 안 걸리는 경우가 있었는데 명시적으로 이 옵션을 true까지 다 주니까 되었던 기억이 남;; )

 

4.3.8. 쿼리 힌트 적용

당신의 리파지토리 인터페이스에 선언된 쿼리에 쿼리 힌트를 적용하기 위해, @QueryHints어노테이션을 사용할 수가 있습니다. 이것은 JPA @QueryHint 의 배열을 사용하고, 잠재적으로 힌트를 사용하지 않도록 하는 boolean flag가 있습니다. 페이지네이션을 적용할 때 발동되는 카운트 쿼리에 이 flag는 적용이 됩니다. (역주 : 맞는 번역인지 하는 고민)

Example 48. repository 메소드와 함께 QueryHints 사용하기
public interface UserRepository extends Repository<User, Long> {

  @QueryHints(value = { @QueryHint(name = "name", value = "value")},
              forCounting = false)
  Page<User> findByLastname(String lastname, Pageable pageable);
}

방금 보여진 선언은 실제 쿼리를 위하여, 설정된 @QueryHint에 적용될 수 있습니다. 하지만 전체 페이지 숫자를 계산하기 위해 발동되는 카운트쿼리는 생략될 수 있습니다. (역주: 확인필요)

 

4.3.9. Fetch- and LoadGraphs 설정

JPA 2.1 스펙은 Fetch- 와 LoadGraphs에 대한 지원을 소개합니다. 우리는 @NamedEntityGraph 정의에 대한 참조를 허용하는 @EntityGraph어노테이션을 통해서 LoadGraph를 지원합니다. @NamedEntityGraph는 엔티에 어노테이션되어서 resulting쿼리에 대한 fetch plan을 설정하는데 사용됩니다. fetching의 타입 (Fetch/Load) 는 @EntityGraph어노테이션의 type 속성을 통해서 설정될 수 있습니다. 더 나은 레퍼런스를 위해서 JPA 2.1 Spec 3.7.4를 살펴보시길 바랍니다.

Example 49. 엔티티에서 엔티티 그래프의 이름정의하기
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
  attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {

  // default fetch mode is lazy.
  @ManyToMany
  List<GroupMember> members = new ArrayList<GroupMember>();

  
}
Example 50. repository 쿼리 메소드에서 이름지어진 엔티티 그래프 정의 참조하기
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

  @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
  GroupInfo getByGroupName(String name);

}

 

4.4. 스토어 프로시져 - Stored procedures

JPA 2.1 스펙은 JPA 크리테리아 API를 통한 스토어 프로시져 호출을 지원을 소개합니다. 우리는 리파지토리 메소드에서 저장된 프로시져 메타데이터를 선언하는 @Procedure어노테이션을 소개했었습니다.

Example 51. HSQL DB 에서 pus1inout procedure 정의 하기
/;
DROP procedure IF EXISTS plus1inout
/;
CREATE procedure plus1inout (IN arg int, OUT res int)
BEGIN ATOMIC
 set res = arg ` 1;
END
/;

스토어 프로시져를 위한 메타데이터는 엔티티 타입에 NamedStoredProcedureQuery어노테이션을 통해서 설정 될 수 있습니다.

Example 52. 엔티티에서의 StoredProcedure 메타데이터 정의
@Entity
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = {
  @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
  @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
public class User {}

스토어 프로시져는 다양한 방법으로 리파지토리 메소드에서 참조될 수 있습니다. 호출되는 stored procedure 는 value@Procedure의 procedureName속성을 통해 직접적으로 정의될 수 있기도하고, 아니면 name 속성을 통해 간접적으로 정의되기도 합니다. 어떠한 이름도 설정되지 않으면 리파지토리 메소드 이름이 fallback으로 사용됩니다.

Example 53. 데이터베이스에 명시적으로 매핑된 프로시져 "plus1inout" 참조
@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);
Example 54. 데이터베이스에 암시적으로 매핑된 프로시져 "plus1inout" 을 통해 procedureName alias.으로 참조
@Procedure(procedureName = "plus1inout")
Integer plus1inout(Integer arg);
Example 55. 엔티티 매니저에서 명시적으로 매핑된 "User.plus1IO" stored procedure를 참조
@Procedure(name = "User.plus1IO")
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
Example 56. 암시적으로 엔티티 매니저에 매핑된 stored procedure "User.plus1" 를 메소드 네임을 통해 참조
@Procedure
Integer plus1(@Param("arg") Integer arg);

 

4.5. 스페시피케이션 - Specifications(원문 그대로 가자)

JPA 2 는 프로그램적으로 쿼리를 만들어내는 크리테리아 API를 소개합니다. criteria를 쓰는 것은 당신이 실제로 도메인 클래스를 위해 쿼리의 where-구문을 정의한다는 것입니다. 이러한 크리테리아는 (JPA 크리테리아 API 제약에 의해 설명된)엔티티에 대해 predicate(서술어)로 여겨집니다.

스프링 데이터 JPA는 에릭 에반스의 책"Domain Driven Design"에서 specification의 개념을 따와서, 같은 문법을 따라, 크리테리아 API 를 사용하는 그러한 specification을 정의하는 API를 제공하였습니다. specifications를 지원하기 위해, 당신의 리파지토리 인터페이스를 JpaSpecificationExecutor인터페이스로 인터페이스와 함께 상속할 수 있습니다.

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
 
}

추가적인 인터페이스는 당신이 specification 를 다양한 방법으로 하게 해주는 메소드를 가지고 있습니다. 예를 들면 findAll 메소드는 speficiation에 맞는 모든 엔티티를 반환하여 줍니다.

List<T> findAll(Specification<T> spec);

Specification인터페이스는 다음과 같이 정의합니다. :

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder);
}

자, 그럼 뭐가 일반적인 유스케이스 일까요? Specification는 엔티티위에서 predicates들의 모음을 확장하게끔 하는 데 쉽게 사용될수 있어서, 매번 필요한 조합을 위한 쿼리를 선언할 필요없이 JpaRepository와 같이 사용되고 조합될 수 있습니다. 여기에 그 예제가 있습니다.

Example 57. Customer를 위한 Spcifications
public class CustomerSpecs {

  public static Specification<Customer> isLongTermCustomer() {
    return new Specification<Customer>() {
      public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query,
            CriteriaBuilder builder) {

         LocalDate date = new LocalDate().minusYears(2);
         return builder.lessThan(root.get(_Customer.createdAt), date);
      }
    };
  }

  public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
    return new Specification<Customer>() {
      public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder) {

         // build query here
      }
    };
  }
}

인정하건대, 상당한 양의 보일러플레이트가 더 나은 향상을 위해 남아있지만(자바8 클로저로 좀더 줄어들기를 바랍니다), 클라이언트사이드는 보시는 바와 같이 좀 더 나아졌습니다. _Customer타입은 JPA 메타모델 generator를 이용하여 생성된메타모델 타입입니다.(see the Hibernate implementation’s documentation for example을 보세요 ) 그래서 이 표현-_Customer.createdAt은 DatecreatedAt속성을 가지는 Customer를 가정합니다. 그 옆에는 우리는 비즈니스 요구사항 추상화 레벨에서 몇가지 크리테리아와 생성된 실행가능한 Specifications를 표현했습니다. 그럼 클라이언트는 다음과 같이 Specification를 사용할 것입니다. :

Example 58.단순한 specification 사용하기
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());

자, 왜 단순하게 이러한 종류의 데이터 접근을 위해 쿼리를 생성하지 않을까요? 당신이 맞습니다. 단순한 Specification을 사용하는 것은 단순한 평문 쿼리 선언에서는 많은 장점을 얻지 못합니다. specifications의 장점은 그들을 조합해서 새로운 Specification객체를 만들때 나타납니다. 당신은 Specificationshelper 클래스(우리가 다음과 같이 표현을 만들기 위해 제공하는)를 통해서 이러한 점을 달성할 수 있습니다. :

Example 59. 조합된 Specifications
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));

당신이 보듯이 Specifications는 Specification인스턴스를 체인하고 조합하기 위해 몇가지 glue-code 메소드를 제공합니다. 그러므로 당신의 데이터 접근 레이어를 확장하는 것은 단순히 새로운 Specification 구현체들을 만들어, 그들을 이미 존재하는 하나로 조합하는 일이 될 것입니다.

4.6. Transactionality

리파지토리 인스턴스의 CRUD 메소드들은 기본적으로 transactional합니다. 읽기 작업을 위해서 트랜잭션 설정 readOnly flag가 true로 설정되고, 모든 다른 것들은 평문@Transactional로 설정되어 기본 트랜잭션이 적용되게 됩니다. 세부사항은 CrudRepository의 JavaDoc을 보시길 바랍니다. 만약 당신이 조금 다르게 리파지토리에 선언된 메소드들 중 하나의 트랜잭션 설정을 하고 싶다면, 단순히 다음과 같이 재선언해보세요 :

Example 60. CRUD 설정을 위한 커스텀 트랜잭션
public interface UserRepository extends CrudRepository<User, Long> {

  @Override
  @Transactional(timeout = 10)
  public List<User> findAll();

  // Further query method declarations
}

이것은 findAll()메소드가 readOnlyflag없이 10초간의 시간제한안에 실행되게 할 것입니다.

트랜잭션 행동을 변경하는 다른 가능성은 facade나 일반적으로 하나이상의 리파지토리를 다루는 서비스구현체를 사용하는 것입니다. 그것의 목적은 non-CRUD 작업에 대해 트랜잭션 경계선을 정의하는 데 있습니다.

Example 61. 여러개의 리파지토리 호출을 위해 트랜잭션을 호출하는 facade 사용하기
@Service
class UserManagementImpl implements UserManagement {

  private final UserRepository userRepository;
  private final RoleRepository roleRepository;

  @Autowired
  public UserManagementImpl(UserRepository userRepository,
    RoleRepository roleRepository) {
    this.userRepository = userRepository;
    this.roleRepository = roleRepository;
  }

  @Transactional
  public void addRoleToAllUsers(String roleName) {

    Role role = roleRepository.findByName(roleName);

    for (User user : userRepository.findAll()) {
      user.addRole(role);
      userRepository.save(user);
    }
}

이것은 addRoleToAllUsers(…)에 대한 호출을 일으키고, 트랜잭션 내부에서 동작하게 할 것입니다. (특별히 존재하는 트랜잭션에 참여시키거나, 아무것도 실행되지 않는다면 새로운 것을 만들것입니다). 이 리파지토리의 트랜잭션 설정은 실제 사용되기로 결정한 다른 바깥 쪽의 트랜잭션 설정때문에 무시(neglected)될 것입니다. 당신은 <tx:annotation-driven />을 활성화시키거나@EnableTransactionManagement 를 명시적으로 사용하여서 facades가 동작하는 어노테이션 기반의 설정 을 얻어야 합니다. 위의 예제는 당신이 컴포넌트 스캐닝을 한다고 가정합니다.

4.6.1. Transactional query methods

당신의 쿼리 메소드가 트랜잭션하기 위해 단순히 @Transactional을 레파지토리 인터페이스에 사용하세요.

Example 62. @Transactional 을 쿼리 메소드에 사용하기
@Transactional(readOnly = true)
public interface UserRepository extends JpaRepository<User, Long> {

  List<User> findByLastname(String lastname);

  @Modifying
  @Transactional
  @Query("delete from User u where u.active = false")
  void deleteInactiveUsers();
}

일반적으로 대부분의 쿼리가 읽기 전용이기 때문에, 당신은 readOnly flag를 true로 설정하기를 원할 겁니다..이와는 대조적으로 deleteInactiveUsers()는 @Modifying어노테이션을 사용하여 트랜잭션 설정을 오버라이딩합니다. 그러므로 저 메소드는 readOnly가 false인 상태로 설정되어서 실행됩니다

 

읽기전용 쿼리를 위해 트랜잭션을 사용하는 것은 절대적으로 합리적입니다. 우리는 readOnlyflag 같은 것을 설정함으로써 그것들(읽기전용쿼리들)을 mark하는 것이 가능합니다. 하지만 당신이 manipulating하는 쿼리를 동작시키지 않았는지 체크하는 것은 되지가 않습니다. (비록 몇몇 데이터베이스가 읽기전용 트랜잭션 내의 INSERT 와 UPDATE 문을 거절하지만) readOnly flag는 대신에 성능 최적화를 위해 JDBC 드라이버에게 hint를 전파시킵니다(propagated). 더군다나 스프링은 몇가지 JPA provider에 대핸 최적화를 제공합니다. 예를 들자면 하이버네이트를 사용할 때, flush 모드가 NEVER로 된 경우 당신은 트랜잭션을 readOnly로 설정하여 하이너네이트의 변경감지를 skip합니다.(large 객체 trees에서는 주목할만한 향상이 있습니다.)

4.7. Locking

사용할 lock 모드를 명시하기 위해 @Lock어노테이션이 쿼리메소드에서 사용될 수 있습니다.

Example 63. 쿼리메소드에 락 메타데이터 정의하기
interface UserRepository extends Repository<User, Long> {

  // Plain query method
  @Lock(LockModeType.READ)
  List<User> findByLastname(String lastname);
}

이 메소드 선언은 동작될 쿼리가 LockModeType READ를 갖추게 할 것입니다. 당신은 또한 CRUD 메소드를 위한 locking을 리파지토리 인터페이스에서 재선언하고 @Lock어노테이션을 추가함으로써 정의할 수 있습니다.

Example 64. CRUD메소드들에 대해 락 메타데이터를 정의하기
interface UserRepository extends Repository<User, Long> {

  // Redeclaration of a CRUD method
  @Lock(LockModeType.READ);
  List<User> findAll();
}

4.8. 감사 - Auditing

4.8.1. 기초 - Basics

스프링 데이터는 엔티티를 누가 만들었고 변경했는지, 발생한 시간 지점에 관한 것들을 투명하게 추적하는 세련된 지원을 제공합니다. 이러한 기능의 장점을 누리기 위해 당신은 엔티티 클래스에 어노테이션을 사용하거나 인터페이스를 구현하여서 정의되는 auditing 메타데이터를 넣을 수 있습니다.

 
어노테이션 기반 auditing 메타데이터

우리는@CreatedBy@LastModifiedBy 를 제공하여 누가 엔티티를 생성하였고 수정하였는지 캡쳐할 뿐만 아니라 @CreatedDateand @LastModifiedDate를 사용하여 어떠한 시점에 발생하였는지 캡쳐합니다.

Example 65. 감사된 엔티티 - An audited entity
class Customer {

  @CreatedBy
  private User user;

  @CreatedDate
  private DateTime createdDate;

  // … further properties omitted
}

당신이 보듯이, 당신이 캡쳐하고 싶은 정보에 따라 선택적으로 어노테이션이 적용 가능합니다. 제 시간 지점을 캡쳐하는 어노테이션을 위해 다음의 타입들이 사용가능합니다 => JodaTimes DateTime, legacy Java Date and Calendar, JDK8 date/time types as well as long/Long

 
인터페이스 기반 감사 메타데이터

당신이 auditing메타데이터를 정의하지 않는 경우에, 당신은 도메인 클래스에 Auditable인터페이스를 구현할 수가 있습니다. 이것은 모든 auditing 속성을 위한 setter메소드를 노출시킵니다.

또한 AbstractAuditable 기반의 편리한 클래스가 있는데, 이것으로 상속을 할 수가 있어서, 수동적으로 인터페이스 메소드를 구현할 필요로를 피하게 합니다. 이것은 스프링 데이터에 대한 도메인클래스들에, 피하고 싶어할 지도 모르는 커플링을 증가시킨다는 것을 알아둡시다. 보통 auditing 메타데이터를 정의하는 어노테이션 기반ㅇ 방법은 좀 더 유연하고 덜 침투적이기 때문에 선호됩니다.

AuditorAware

당신이 @CreatedBy 나 @LastModifiedBy같은 것을 사용하고 싶은 경우에, auditing인프라스트럭처는 어떻게든 현재 printcipal 에 대해 알게될 필요가 있습니다. 그렇게 하기 위해 우리는 AuditorAware<T> SPI interface를 제공을 하는데, 이것은 누가 어플리케이션과 시스템 상호작용을 하는 지, 누가 현재 유저인지 인프라스트럭쳐에게 알려주기 위해 구현을 합니다. 제너릭 타입 T 는 @CreatedBy나 @LastModifiedBy가 어노테이션되는 속성이 어떤 속성인지 정의합니다.

여기에 Spring 시큐리티 Authentication객체를 사용하는 인터페이스 구현에 대한 예제가 있습니다

Example 66. 스프링 시큐리티 기반의 AuditorAware구현
class SpringSecurityAuditorAware implements AuditorAware<User> {

  public User getCurrentAuditor() {

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication == null || !authentication.isAuthenticated()) {
      return null;
    }

    return ((MyUserDetails) authentication.getPrincipal()).getUser();
  }
}

저 구현은 스프링시큐리티에 의해 제공된 Authentication객체에 접근하고, 당신이 생성한 UserDetailsService구현에서 커스텀 UserDetails을 탐색합니다. 우리는 여기서 당신이 도메인user를 UserDetails구현을 통해 노출했다고 가정하지만, 당신은 또한Authentication가 발견될 수 있는 어디에서건 검색을 할 수가 있습니다.

4.9. JPA Auditing

 

4.9.1. 일반적인 auditing 설정

스프링 데이터 JPA는 엔티티리스너를 가지고 있습니다. 엔티티리스너는 auditing 정보를 캡쳐하는 데 사용될 수가 있습니다. 먼저 당신은 orm.xml 내에 AuditingEntityListener를 등록을 해야 합니다. 당신의 영속성 컨텍스트에서 모든 엔티티가 사용할 수 있게 말이죠 .

auditing 기능은 클래스패스에spring-aspects.jar 를 필요로 합니다.

Example 67. Auditing 설정 orm.xml
<persistence-unit-metadata>
  <persistence-unit-defaults>
    <entity-listeners>
      <entity-listener class="….data.jpa.domain.support.AuditingEntityListener" />
    </entity-listeners>
  </persistence-unit-defaults>
</persistence-unit-metadata>

이제 auditing기능을 활성화시키는 것은 스프링 데이터 JPA의 auditing 네임스페이스 요소를 당신의 설정에 추가하면됩니다. :

Example 68. XML 설정을 이용한 auditing 활성화
<jpa:auditing auditor-aware-ref="yourAuditorAwareBean" />

Spring data jpa 1.5 에서는 auditing 은 @EnableJpaAuditing설정이 클래스에 의해서도 활성화 될 수가 있습니다

Example 69. 자바 설정을 통해 auditing 활성화 시키기
@Configuration
@EnableJpaAuditing
class Config {

  @Bean
  public AuditorAware<AuditableUser> auditorProvider() {
    return new AuditorAwareImpl();
  }
}

만약 당신이 어플리케이션 콘텍스트에 AuditorAware의 빈타입을 노출시킨다면 auditing인프라스트럭쳐는 자동적으로 그것을 pick up해서 현재 user를 도메인 타입을 결정하는 데 그것을 사용할 것입니다. 만약 당신이 어플리케이션 내에 여러개의 구현체를 등록시켰다면, 당신은 명시적으로 @EnableJpaAuditing의 auditorAwareRef속성을 설정하여 그 중에 하나를 고를 수가 있습니다.

 

5. 오해

 

5.1. persistence 유닛을 합병(merging)하기

스프링은 여러개의 영속 유닛을 가지는 것을 지원합니다. 하지만 때때로 당신은 어플리케이션을 모듈화하면서도, 여전히 이런 모듈들이 런타임 시에 단일 영속 유닛 내부에서 실행되기를 확인하고 싶을 것입니다. 이렇게 하기 위해 스프링데이터JPA는 PersistenceUnitManager구현체를 제공하여, 그들의 이름에 기반하여 영속 유닛들을 자동적으로 merge하게 해줍니다.

Example 70. MergingPersistenceUnitmanager 사용
<bean class="….LocalContainerEntityManagerFactoryBean">
  <property name="persistenceUnitManager">
    <bean class="….MergingPersistenceUnitManager" />
  </property>
</bean>

 

5.1.1. JPA매핑파일과 @Entity 클래스들을 위한 클래스 패스 스캐닝

평범한 JPA설정은 orm.xml에 리스트된 모든 어노테이션 매핑된 엔티티 클래스들을 필요로 합니다. 스프링데이터 JPA는 ClasspathScanningPersistenceUnitPostProcessor를 제공하는데, 이것은 base package 설정을 얻고, 선택적인 매핑 파일이름 패턴을 가집니다. 이것은 그후, 주어진 패키지에서 @Entity 나 @MappedSuperclass 로 어노테이션된 클래스들을 스캔하고, 파일이름패턴과 매칭되는 설정파일을 불러와 JPA설정에 그것들을 전달합니다. PostProcessor가 다음과 같이 설정되어야 합니다. :

Example 71. ClasspathScanningPersistenceUnitPostProcessor사용
<bean class="….LocalContainerEntityManagerFactoryBean">
  <property name="persistenceUnitPostProcessors">
    <list>
      <bean class="org.springframework.data.jpa.support.ClasspathScanningPersistenceUnitPostProcessor">
        <constructor-arg value="com.acme.domain" />
        <property name="mappingFileNamePattern" value="**/*Mapping.xml" />
      </bean>
    </list>
  </property>
</bean>
 

스프링 3.1 이후로, 스캔할 패키지는 LocalContainerEntityManagerFactoryBean에 직접적으로 설정되어 엔티티클래스르들을 위한 클래스패스 스캐닝을 활성화시킬수가 있습니다. 더 자세한 사항은 JavaDoc을 참고하세요

 

5.2. CDI 통합

repository 인터페이스의 인스턴스는 보통 컨테이너에 의해 생성됩니다. 이는 스프링데이터와 작업할 때 가장 자연스런 선택입니다. 리파지토리 인스턴스에 나왔던, 빈 인스턴스를 생성하는 방법을 스프링에서 쉽게 설정하는 세련된 방법이 있습니다. 스프링데이터 버젼 1.1.0 부터 CDI환경의 리파지토리 추상화를 사용하는 커스텀 CDI 확장(extension)을 가질 수가 있습니다. 이 extension은 Jar의 일부이며, 당신이 해야할 모든 것은 스프링 데이터 JPA jar 를 클래스패스에 두는 것입니다.

Y당신은 그러면 EntityManagerFactory 와 EntityManager를 위한 CDI Producer를 구현함으로써 인프라스트럭쳐를 설정할 수가 있게 됩니다. :

class EntityManagerFactoryProducer {

  @Produces
  @ApplicationScoped
  public EntityManagerFactory createEntityManagerFactory() {
    return Persistence.createEntityManagerFactory("my-presistence-unit");
  }

  public void close(@Disposes EntityManagerFactory entityManagerFactory) {
    entityManagerFactory.close();
  }

  @Produces
  @RequestScoped
  public EntityManager createEntityManager(EntityManagerFactory entityManagerFactory) {
    return entityManagerFactory.createEntityManager();
  }

  public void close(@Disposes EntityManager entityManager) {
    entityManager.close();
  }
}

필요한 설치는 당신이 운영하는 JAVA EE 환경에 따라 매우 다양해질 수가 있지만 또한 다음과 같이 CDI빈으로서 EntityManager를 재선언하는 것으로 충분할 수도 있습니다.

class CdiConfig {

  @Produces
  @RequestScoped
  @PersistenceContext
  public EntityManager entityManager;
}

이 예제에서 컨테이너는 EntityManagers를 생성 할 수 있습니다. 여기서 설정이 하는 것은 CDI빈으로써 JPAEntityManager을 다시 export시키는 것입니다.

스프링 데이터 JPA CDI 확장은 모든 EntityManagers 를 CDI빈으로 고를수 있게(pick up) 해주며, 리파지토리 타입의 빈이 컨테이너에 의해 요청될때마다 스프링 데이터 리파지토리를 위한 프록시를 생성합니다. 그러므로 스프링데이터 리파지토리의 인스턴스를 얻는 것은 @Injected속성을 선언하기만 하면 됩니다

class RepositoryClient {

  @Inject
  PersonRepository repository;

  public void businessMethod() {
    List<Person> people = repository.findAll();
  }
}

부록

 

부록 A: 네임스페이스 레퍼런스

The <repositories /> element

<repositories /> 요소는 스프링 데이터 리파지토리 인프라스트럭쳐의 설정을 동작시킵니다. 가장 중요한 속성은 base-package로 이것은 스프링 데이터 리파지토리 인터페이스[3]를 찾을 패키지를 스캔하게 해줍니다.

Table 6. 속성들
Name Description

base-package

*Repository를 상속하는 리파지토리 인터페이스를 자동 감지 모드 아래 스캔하기 위해 사용됨. (실제 인터페이스는 스프링 데이터 모듈에 따라 결정된다.) 설정된 패키지의 아래 있는 모든 패키지는 스캔되며 와일드 카드 또한 가능하다.

 

repository-impl-postfix

커스텀 리파지토리 구현체를 자동감지하기 위한 접미사를 설정한다. 설정된 접미사를 가진 클래스들은 그 후보로 고려되며 기본값은 Impl이다.

 

query-lookup-strategy

쿼리 검색자를 만드는데 사용되는 전략을 결정하기 위함. 쿼리 탐색 전략을 보시고 기본값은 create-if-not-found이다.

 

named-queries-location

외부적으로 정의된 쿼리들을 포함하는 설정파일을 찾기 위한 위치를 정의함

 

consider-nested-repositories

중첩(nested) 리파지토리 인터페이스 정의를 조정할지 고려함. 기본값은 false.

 

 

부록 B : Populators 네임스페이스 레퍼런스

The <populator /> element

 

<populator /> 요소는 저장소를 Spring data 리파지토리 인프라스트럭쳐[4]를 통해 채울 있게 해준다.

 

Table 7. Attributes
Name Description

locations

어디서 저장소를 채울 객체를 읽어들일 파일들을 발견할지 정함.

 

 

부록 C : 리파지토리 쿼리 키워드들

 

지원되는 쿼리 키워드들

다음의 테이블은 일반적으로 스프링데이터리파지토리 쿼리 추론 메커니즘에서 지원되는 키워드들을 나타낸다 그러나 특정 디비에서 정확히 지원되는 키워드를 위해 저장소 문서를 참조하기를 바란다. 왜냐하면 몇몇 리스트는 특정 데이터 저장소에서 지원이 되지 않기 때문이다.

 

Table 8. Query keywords
Logical keyword Keyword expressions

AND

And

OR

Or

AFTER

AfterIsAfter

BEFORE

BeforeIsBefore

CONTAINING

ContainingIsContainingContains

BETWEEN

BetweenIsBetween

ENDING_WITH

EndingWithIsEndingWithEndsWith

EXISTS

Exists

FALSE

FalseIsFalse

GREATER_THAN

GreaterThanIsGreaterThan

GREATER_THAN_EQUALS

GreaterThanEqualIsGreaterThanEqual

IN

InIsIn

IS

IsEquals, (or no keyword)

IS_NOT_NULL

NotNullIsNotNull

IS_NULL

NullIsNull

LESS_THAN

LessThanIsLessThan

LESS_THAN_EQUAL

LessThanEqualIsLessThanEqual

LIKE

LikeIsLike

NEAR

NearIsNear

NOT

NotIsNot

NOT_IN

NotInIsNotIn

NOT_LIKE

NotLikeIsNotLike

REGEX

RegexMatchesRegexMatches

STARTING_WITH

StartingWithIsStartingWithStartsWith

TRUE

TrueIsTrue

WITHIN

WithinIsWithin

 

부록 D : 리파지토리 쿼리 리턴 타입

 

지원되는 쿼리 리턴 타입

 

다음의 테이블은 일반적으로 스프링 데이터 리파지토리에서 지원되는 리턴 타입들을 나타냅니다. 그러나 앞서 말씀드린 대로 데이터 저장소 문서를 참조하세요.

 

 
GeoResultGeoResultsGeoPage와 같은 지역기반 타입 은 오직 지역기반 쿼리를 지원하는 데이터저장소에서만 사용가능합니다.
표 6. 쿼리 리턴 타입
Return type Description

void

 

아무런 리턴 타입 없다.

 

Primitives

 

자바 주요요소

 

Wrapper types

 

자바 래퍼 타입

 

T

 

유니크 엔티티. 대부분 쿼리 메소드가 하나의 결과를 돌려주기를 예상합니다. 어떠한 결과도 발견되지 않은 경우 null이 반환될 것입니다. 하나 이상의 결과가 나오게 되면IncorrectResultSizeDataAccessException가 나올 것입니다.

 

Iterator<T>

 

반복자.

 

Collection<T>

Collection.

List<T>

List.

Optional<T>

 

자바8 이나 Guava Optional입니다. 쿼리메소드가 대부분 하나의 결과값을 리턴하기를 기대하며 어떠한 결과도 없으면 Optional.empty()/Optional.absent()가 반환됩니다. 하나 이상의 결과에는 IncorrectResultSizeDataAccessException가 동작할 것입니다.

 

Stream<T>

A Java 8 Stream.

Slice

 

더 이상의 데이터가 가능한지에 대한 정보가 같이 있는 데이터의 덩어리입니다. Pageable를 메소드 파라미터로 필요로 합니다 .

 

Page<T>

 

Slice 와 추가적인 정보가 있는 (예를 들자면 결과의 총 개수) 타입입니다. Pageable 를 메소드 파라미터로 필요로 합니다.

 

GeoResult<T>

 

추가적인 정보(참조 위치에 대한 거리)와 함께 있는 결과 엔트리

 

GeoResults<T>

 

추가적인 정보(참조 위치에 대한 평균 거리 )와 함께 있는 GeoResult<T>의 리스트

 

GeoPage<T>

 

Page 와 GeoResult<T>,예를 들면 참조 위치와 평균 거리

 

 

부록 E: 자주묻는 질문들

 

흔한 질문
  1. 저는 JpaRepository내에서 호출되는 메소드들에 대해 좀더 상세한 로깅을 얻고 싶은데 어떻게 할까요?

    당신으 스프링에서 제공되는 CustomizableTraceInterceptor 를 이용할 수가 있습니다 :

    <bean id="customizableTraceInterceptor" class="
      org.springframework.aop.interceptor.CustomizableTraceInterceptor">
      <property name="enterMessage" value="Entering $[methodName]($[arguments])"/>
      <property name="exitMessage" value="Leaving $[methodName](): $[returnValue]"/>
    </bean>
    
    <aop:config>
      <aop:advisor advice-ref="customizableTraceInterceptor"
        pointcut="execution(public * org.springframework.data.jpa.repository.JpaRepository+.*(..))"/>
    </aop:config>

 

인프라스트럭쳐
  1. 현재 저는 HibernateDaoSupport에 기반하는 리파지토리 레이어를 구현했습니다. 저는 스프링의 AnnotationSessionFactoryBean를 이용하여서 SessionFactory를 만들었습니다. 어떻게 이런 환경에서 스프링 데이터 리파지토리들을 얻을 수 있을까요?

    당신은 다음과 같이 AnnotationSessionFactoryBean 을 HibernateJpaSessionFactoryBean로 바꾸시면 됩니다. :

    Example 72. SessionFactoryHibernateEntityManagerFactory로부터 SessionFactory탐색
    <bean id="sessionFactory" class="org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean">
      <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

Auditing

  1. 저는 스프링 데이터 auditing 기능들을 사용하고 싶지만 제 데이터베이스는 이미 엔티티들에 수정,생성날짜를 설정해두었습니다. 어떻게 스프링데이터가 date를 programmatically하게 설정하는 것을 막아둘까요?

    네임스페이스 요소 auditing의 set-dates 속성을 false로 하세요

 

부록 F: 용어집
AOP

관점 지향 프로그래밍

Commons DBCP

Commons DataBase Connection Pools - 데이터 소스 인터페이스를 구현하는 pooling 구현체. 아파치 단체의 라이브러리

CRUD

만들고, 읽고, 수정하고, 삭제하는 기본적인 작업 Create, Read, Update, Delete - Basic persistence operations

DAO

데이터 접근 객체 - 영속화되는 객체로부터 분리된, 영속하는 logic

Dependency Injection

컴포넌트의 의존성을 바깥에 있는 컴포넌트로 전달하는 패턴. 자신이 스스로 의존성을 탐색해야되는 것에서 자유롭게 합니다. 더 나은 정보는 http://en.wikipedia.org/wiki/Dependency_Injection

EclipseLink

JPA를 구현하는 객체관계매퍼 - http://www.eclipselink.org

Hibernate

JPA를 구현하는 객체관계매퍼 - http://www.hibernate.org

JPA

자바 영속 API

Spring

자바 어플리케이션 프레임 워크 - http://projects.spring.io/spring-framework

[출처] http://arahansa.github.io/docs_spring/jpa.html

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED