[번역] spring-data-elasticsearch

rin·2020년 7월 19일
12

Document 번역

목록 보기
15/22
post-thumbnail

Spring-data-elasticsearch를 번역합니다.

Elasticsearch Clients

https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#core.extensions

Spring Data Elasticsearch는 단일 Elasticsearch 노드 또는 클러스터에 연결된 Elasticsearch 클라이언트에서 작동한다. Elasticsearch Client를 사용하여 클러스터 작업을 수행 할 수 있지만 Spring Data Elasticsearch를 사용하는 애플리케이션은 일반적으로 상위 레벨의 Elasticsearch Operations 및 Elasticsearch Repositories를 사용한다.

Transport Client

❗️
잘 알려진 TransportClient는 Elasticsearch 7부터 더 이상 사용되지 않으며 Elasticsearch 8에서 제거된다 (Elasticsearch 설명서 참조). Spring Data Elasticsearch는 사용되고 있는 Elasticsearch 버전에서 사용 가능한 TransportClien를 지원하지만, 버전 4.0부터 이 클래스를 더 이상 지원하지 않는다.

TransportClient 대신 High Level REST Client를 사용할 것을 권장한다.

@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {

    @Bean
    public Client elasticsearchClient() throws UnknownHostException {
        // TransportClient를 구성하는 데에는 클러스터 이름이 반드시 필요하다.
        Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();        
        TransportClient client = new PreBuiltTransportClient(settings);
        // 클라이언트로 연결하기 위한 host와 port
        client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); 
        return client;
    }

    @Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
    public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
        return new ElasticsearchTemplate(elasticsearchClient());
    }
}

// ...

IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID())
 .source(someObject)
 .setRefreshPolicy(IMMEDIATE);

IndexResponse response = client.index(request);

High Level REST Client

Java High Level REST Client는 Elasticsearch의 기본 클라이언트이며 TransportClient와 동일한 요청을 수락하고 응답 객체를 반환하므로 (Elasticsearch 핵심 프로젝트에 따라) TransportClient를 대체 할 수 있다. 비동기 호출은 스레드 풀에서 관리되는 클라이언트에서 작동하며 요청이 완료되면 콜백으로 알림을 보내야한다.

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {

    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        // 클러스터 주소를 제공하기 위해 builder를 사용한다. 디폴트 HttpHeaders나 사용가능한 SSL로 셋한다.
        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()  
            .connectedTo("localhost:9200")
            .build();
        // RestHighLevelClient를 만든다.
        return RestClients.create(clientConfiguration).rest();                         
    }
}

// ...

  @Autowired
  RestHighLevelClient highLevelClient;

  // LowLevelRest() 메소드로 RestClient를 얻는 것도 가능하다.
  RestClient lowLevelClient = highLevelClient.lowLevelClient();                        

// ...

IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID())
  .source(singletonMap("feature", "high-level-rest-client"))
  .setRefreshPolicy(IMMEDIATE);

IndexResponse response = highLevelClient.index(request);

Elasticsearch Object Mapping

Spring Data Elasticsearch Object Mapping은 Java 오브젝트(도메인 엔티티)를 ES와 뒷단에 저장된 JSON 형식에 매핑하는 과정이다.

이전 버전의 Spring Data Elasticsearch는 Jackson 기반의 변환을 사용했으며, Spring Data Elasticsearch 3.2.x는 Meta Model Object Mapping을 도입했다. 버전 4.0에서는 Meta Object Mapping만 사용되며, Jackson 기반 매퍼는 더이상 사용할 수 없으며 MappingElasticsearchConverter가 사용된다.

Jackson기반 매퍼를 제거하는 주요 이유는 다음과 같다.

  • 필드에 대한 커스텀 매핑은 @JsonFormat이나 @JsonInclude와 같은 어노테이션의 사용이 요구된다. 이는 동일한 오브젝트가 JSON 기반의 데이터 저장소에서 사용되거나 JSON 기반 API를 통해 전송될 때 종종 문제를 일으켰다.
  • 커스텀 필드 타입과 포맷은 Elasticsearch 인덱스 매칭에 저장되어야 한다. Jackson 기반의 어노테이션은 Elasticsearch의 유형을 나타내기 위한 필요한 모든 정보를 충분히 제공하지 못했다.
  • 필드는 엔티티로 변환될 때 뿐만 아니라 쿼리의 argument, 반환 데이터 등 다른 곳에서도 매핑되어야한다.

MappingElasticsearchConverter를 사용해 이 모든 사례를 처리할 수 있다.

Meta Model Object Mapping

Meta Model 기반의 접근방식은 ES에서 읽기/쓰기를 위해 도메인의 타입 정보를 사용한다. 이를 통해 특정 도메인 타입 매칭을 위한 converter 인스턴스를 등록할 수 있다.

Mapping Annotation Overview

MappingElasticsearchConverter는 메타데이터를 사용하여 도큐먼트로의 오브젝트 매핑을 수행한다. 메타데이터는 어노테이션이 달린 엔터티의 property에서 가져온다.

다음과 같은 어노테이션을 사용할 수 있다.

  • @Document : 데이터베이스에 매핑할 클래스임을 나타낸다. 클래스 레벨에 적용. 가장 중요한 속성은 다음과 같다.
    • indexName : 이 엔티티를 저장할 인덱스 이름
    • type : 매핑 유형. 설정되지 않은 경우 클래스의 소문자 이름이 사용된다. (버전 4.0 이후 사용 안 함)
    • shards : 인덱스에 대한 샤드 개수
    • replicas : 인덱스에 대한 복제본 개수
    • refreshIntervall : 인덱스의 refresh 실행 간격. 인덱스 생성에 사용됨. default= "1s"
    • indexStoreType : 인덱스의 인덱스 저장소 유형. 인덱스 생성에 사용됨. default= "fs"
    • createIndex : repository 부트스트래핑에 인덱스를 생성할지 여부 configuration. defalut= true
    • versionType : 버전 관리 configuration. default= EXTERNAL
  • @Id : ID 목적으로 사용되는 필드를 표시한다.
  • @Transient : 기본적으로 모든 필드는 저장 또는 검색 시 도큐먼트에 매핑되는데 이 주석이 포함된 필드는 제외된다.
  • @PersistenceConstructor : 데이터베이스에서 개체를 인스턴스화할 때 사용할 생성자(보호된 패키지도 포함). 생성자 argument는 검색된 문서의 키 값에 이름에 의해 매핑된다.
  • @Field : 필드의 속성을 정의. 대부분의 속성이 각 Elasticsearch Mapping 정의에 매핑된다. (전체 참조는 어노테이션 Javadoc을 확인하라)
    • name : Elasticsearch 문서에 나타낼 필드 이름. 설정되지 않은 경우 Java 필드 이름이 사용된다.
    • type : 필드 유형, Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type 중 하나일 수 있다. Elasticsearch 매핑 유형을 참조하라.
    • Date 유형의 formatpattern 정의. Date 유형에 대해 format은 반드시 정의해야 한다.
    • store : 원래 필드 값이 Elasticsearch에 저장되야 하는지 판단하는 플래그, default= false.
    • 커스텀 analyzer와 normalizer를 지정하여 커스텀마이징하는 analyzer, searchAnalyzer, Normalizer.
  • @GeoPoint: 필드가 geo_point 데이터 형식임을 표시한다. 만약 필드가 GeoPoint 클래스의 인스턴스인 경우 생략할 수 있다.

❗️ NOTE
TemporalAccessor에서 파생되는 속성에는 FieldType.Date 타입은 @Field 어노테이션이 있어야하며 이 타입에는 커스텀 converter를 등록해야 한다.
Elasticsearch 7의 변동으로 인해 사용자 정의 날짜 형식을 사용하는 경우, 연도에 yyyy 대신 uuuu를 사용해야한다.

매핑 메타데이터 인프라는 기술에 구애받지 않는 별도의 spring-data-commons 프로젝트에서 정의된다.

Mapping Rules

✔️ Type Hints
매핑은 서버로 전송 된 도큐먼트에 포함 된 type hints를 사용하여 일반적인 타입의 매핑을 수행한다. 이러한 type hints는 도큐먼트에서 _class 속성으로 표시되며 각 aggreate root에 대해 작성된다.

public class Person {              

  @Id String id;
  String firstname;
  String lastname;
}

{
  "_class" : "com.example.Person", //기본적으로 도메인 타입의 클래스 이름이 type hint로 사용된다.
  "id" : "cb7bef",
  "firstname" : "Sarah",
  "lastname" : "Connor"
}

사용자 지정 정보로 type hints를 구성하고 싶으면 @TypeAlias를 사용하면 된다.

❗️
저장 공간에서 데이터를 처음 읽을 때 엔티티 정보를 미리 사용할 수 있도록 초기화된 엔티티 셋(AbstractElasticSearchConfiguration#getInitialEntitySet)에 @TypeAlias를 사용한 타입을 추가하라.

@TypeAlias("human")                
public class Person {

  @Id String id;
  // ...
}

{
  "_class" : "human",              
  "id" : ...
}

❗️
property 타입이 Object, interface, 실제 값의 타입이 property의 선언값과 일치하지 않는 중첩 오브젝트에 대해 type hint는 기록되지 않는다.

🔎 Geospatial Types
Point, GeoPoint 같은 Geospatial Type은 lat/lon 타입으로 변환된다.

public class Address {

  String city, street;
  Point location;
}

{
  "city" : "Los Angeles",
  "street" : "2800 East Observatory Road",
  "location" : { "lat" : 34.118347, "lon" : -118.3026284 }
}

🔎 Maps
Map 내의 값에 대해서는 type hint와 커스텀 변환에 있어서 aggregate root와 동일한 매핑 규칙이 적용된다. 그러나 Map key는 ES에 의해 처리되기 위해선 String이여야한다.

public class Person {

  // ...

  Map<String, Address> knownLocations;

}

{
  // ...

  "knownLocations" : {
    "arrivedAt" : {
       "city" : "Los Angeles",
       "street" : "2800 East Observatory Road",
       "location" : { "lat" : 34.118347, "lon" : -118.3026284 }
     }
  }
}

Elasticsearch Operations

Spring Data Elasticsearch는 Elasticsearch 색인에 대해 호출 가능한 연산을 정의하기 위해 여러 인터페이스를 사용한다 (반응형 인터페이스에 대한 설명은 Reactive Elasticsearch Operations 참조).

  • IndexOperations는 인덱스 생성 또는 삭제와 같은 인덱스 수준에서 작업을 정의한다.
  • DocumentOperations는 ID에 따라 엔티티를 저장, 업데이트 및 검색하는 작업을 정의한다.
  • SearchOperations는 쿼리를 사용하여 여러 엔티티를 검색하는 작업을 정의한다.
  • ElasticSearchOperationsDocumentOperationsSearchOperations 인터페이스를 결합한다.

이러한 인터페이스는 Elasticsearch API의 구조에 해당한다.

인터페이스의 기본 구현체는 다음을 제공한다.

  • 인덱스 관리 기능.
  • 도메인 타입에 대한 읽기/쓰기 매핑 지원.
  • 풍부한 쿼리와 criteria(기준) api.
  • 리소스 관리와 예외 변환

ElasticsearchTemplate

❗️
ElasticsearchTemplate은 사용은 버전 4.0에서 더 이상 사용되지 않는다. 대신 ElasticsearchRestTemplate를 사용한다.

ElasticsearchTemplate는 Transport Client를 사용해 ElasticsearchOperations 인터페이스를 구현한 것이다.

@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {

  @Bean
  public Client elasticsearchClient() throws UnknownHostException {    
    // Transport client 셋팅
    Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
    TransportClient client = new PreBuiltTransportClient(settings);
    client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
    return client;
  }

  // ElasticsearchTemplate 빈을 만들고, 두 개의 이름을 제공한다.
  @Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
  public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { 
  	return new ElasticsearchTemplate(elasticsearchClient());
  }
}

ElasticsearchRestTemplate

ElasticsearchRestTemplate는 High Level REST Client를 사용해 ElasticsearchOperations 인터페이스를 구현한 것이다.

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
  @Override
  public RestHighLevelClient elasticsearchClient() {       
    // High Level REST Client를 셋팅한다.
    return RestClients.create(ClientConfiguration.localhost()).rest();
  }

  // no special bean creation needed    
  // 베이스 클래스인 AbstractElasticsearchConfiguration는 이미 elasticsearchTemplate 빈을 제공한다.
}

Usage examples

ElasticsearchTemplateElasticsearchRestTemplate 모두 ElasticsearchOperations 인터페이스를 구현하므로 이를 사용할 코드가 다르지 않다. 이 예는 Spring REST 컨트롤러에서 주입된 ElasticsearchOperations 인스턴스를 사용하는 방법을 보여준다. TransportClient 또는 RestClient를 사용하는 경우 위의 구성 중 하나를 사용하여 해당 Bean을 결정한다.

@RequestMapping("/")
public class TestController {

  private  ElasticsearchOperations elasticsearchOperations;

  // 생성자를 이용하여 ElasticsearchOperations 빈을 자동 주입한다.
  public TestController(ElasticsearchOperations elasticsearchOperations) { 
    this.elasticsearchOperations = elasticsearchOperations;
  }

  // ES 클러스터에 엔티티를 저장한다.
  @PostMapping("/person")
  public String save(@RequestBody Person person) {                         

    IndexQuery indexQuery = new IndexQueryBuilder()
      .withId(person.getId().toString())
      .withObject(person)
      .build();
    String documentId = elasticsearchOperations.index(indexQuery);
    return documentId;
  }

  // id를 사용하는 쿼리로 엔티티를 검색한다.
  @GetMapping("/person/{id}")
  public Person findById(@PathVariable("id")  Long id) {                   
    Person person = elasticsearchOperations
      .queryForObject(GetQuery.getById(id.toString()), Person.class);
    return person;
  }
}

Reactive Elasticsearch Operations

ReactiveElasticSearchOperationsReactiveElasticSearchClient를 사용하여 Elasticsearch 클러스터에 대해 상위 수준의 명령을 실행할 수 있는 게이트웨이이다.

ReactiveElasticSearchTemplate는 ReactiveElasticSearchOperations의 기본 구현이다.

Reactive Elasticsearch Template

ReactiveElasticSearchTemplate를 시작하려면 함께 작동하는 실제 클라이언트에 대해 알아야 한다. 클라이언트에 대한 자세한 내용은 Reactive Client를 참조하라.

~생략~
ref. https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.reactive.operations

Search Result Types

DocumentOperations 인터페이스의 메소드를 이용해 도큐먼트를 검색하면 찾아진 엔티티만을 반환한다. SearchOperations 인터페이스의 메소드로 검색한 경우엔 각 엔티티에 대한 추가적인 정보가 더해진다. 이를테면 찾은 엔티티의 scoresortValues 같은 것들이다.

이런 정보를 반환하기 위해서 각 엔티티는 추가 정보를 포함하는 SearchHit 오브젝트로 래핑된다. 이러한 SearchHit 오브젝트는 maxScore나 요청된 집계와 같은 전체 검색에 대한 정보를 포함하고 있는 SearchHits 오브젝트에서 반환된다. 다음과 같은 클래스와 인터페이스를 사용할 수 있다.

SearchHit<T>는 다음의 정보를 포함한다.

  • Id
  • Score
  • Sort Values
  • Highlight fields
  • The retrieved entity of type <T>

SearchHits<T>는 다음의 정보를 포함한다.

  • Number of total hits
  • Total hits relation
  • Maximum score
  • A list of SearchHit<T> objects
  • Returned aggregations

SearchPage<T>
SearchHits<T> 요소에 포함되며 레파지토리 메소드를 사용할 때 페이징 엑세스에 사용되는 spring data Page를 정의한다.

SearchScrollHits<T>
ElasticsearchRestTemplate의 낮은 수준의 scroll API 함수로 반환되며 Elasticsearch scroll ID로 SearchHits<T>를 풍부하게 한다.

SearchHitsIterator<T>
SearchOperations 인터페이스의 스트리밍 함수에 의해 반환되는 Iterator

Elasticsearch Repositories

Query methods

Query lookup strategies

Elasticsearch 모듈은 문자열 쿼리, 네이티브 검색 쿼리, criteria 기반 쿼리, 메서드 기반 쿼리 작성 기능을 지원한다.

Declared queries
메서드 기반 쿼리가 항상 충분하지 않고, 읽기 힘든 메서드 이름이 만들어 질 수 도 있다. 이 경우 @Query 어노테이션을 사용할 수 있다(@Query annotation의 사용 참조).

Query creation

~생략~

Method return types

레포지토리 메소드는 여러 요소를 반환할 수 있도록 다음과 같은 리턴 타입을 정의할 수 있다.

  • List<T>
  • Stream<T>
  • SearchHits<T>
  • List<SearchHit<T>>
  • Stream<SearchHit<T>>
  • SearchPage<T>

Using @Query Annotation

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
    Page<Book> findByName(String name,Pageable pageable);
}

어노테이션의 인수로 설정된 문자열(위 예제에서 "{\"match\": {\"name\": {\"query\": \"?0\"}}}")은 유효한 Elasticsearch JSON 쿼리여야 한다. 이는 "query" element의 값으로 대치되어 Easticsearch로 전송된다. 예를 들어 함수를 매개 변수 John과 함께 호출할 경우 다음과 같은 쿼리 본문을 생성한다.

{
  "query": {
    "match": {
      "name": {
        "query": "John"
      }
    }
  }
}

Annotation based configuration

Spring Data Elasticsearch 레파지토리 지원은 JavaConfig의 어노테이션을 사용하여 활성화할 수 있다.

@Configuration
@EnableElasticsearchRepositories(                            
  basePackages = "org.springframework.data.elasticsearch.repositories"
  ) 
static class Config {

  @Bean
  public ElasticsearchOperations elasticsearchTemplate() {    
      // ...
  }
}

class ProductService {

  private ProductRepository repository;                       

  public ProductService(ProductRepository repository) {
    this.repository = repository;
  }

  public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
    return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
  }
}

1️⃣ Annotation based configuration 어노테이션은 Repository 지원을 활성화한다. 베이스 패키지가 구성되지 않은 경우, 구성 클래스가 적용되는 것들 중 하나를 사용한다.

2️⃣ Elasticsearch Operations 챕터에서 보여준 구성 중 하나를 사용해 ElasticsearchOperations 타입의 elasticsearchTemplate 라는 이름의 빈을 제공한다.

profile
🌱 😈💻 🌱

1개의 댓글

comment-user-thumbnail
2021년 2월 24일

와 번역 너무 도움이되네요 ㅜㅜ
감사합니다

답글 달기