ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Elasticsearch + Springboot
    🔍 elastic search 2022. 9. 27. 15:34

    Docker-compose로 Elasticsearch 설치


    compose 파일 생성

    vi elasticsearch.yml
    
    version: '3.7'
    services:
      es:
        image: docker.elastic.co/elasticsearch/elasticsearch:7.15.2
        container_name: elasticsearch
        environment:
          - node.name=single-node
          - cluster.name=beomsic
          - discovery.type=single-node
        ports:
          - 9200:9200
          - 9300:9300
        networks:
          - es-bridge
    
      kibana:
        container_name: kibana
        image: docker.elastic.co/kibana/kibana:7.15.2
        environment:
          SERVER_NAME: kibana
          # Elasticsearch 기본 호스트는 <http://elasticsearch:9200>
          # 현재 docker-compose 파일에 Elasticsearch 서비스 명 es로 설정
          ELASTICSEARCH_HOSTS: <http://elasticsearch:9200>
        ports:
          - 5601:5601
        # Elasticsearch Start Dependency
        depends_on:
          - es
        networks:
          - es-bridge
    
    networks:
      es-bridge:
        driver: bridge

    실행 하기

    # 실행, 데몬으로 띄우려면 맨 뒤에 -d를 붙여준다.
    # 기본 실행 도커파일은 docker-compose.yml인데 
    # elasticsearch.yml로 만들었으므로 지정해주기 위해서 -f 옵션을 사용
    docker-compose -f elasticsearch.yml up
    
    # 죽이기
    docker-compose -f elasticsearch.yml down
    

    9200 포트는 HTTP 클라이언트 와 통신에 사용

    9300 포트는 노드들간 통신 에 사용

    이번엔 간단하게 구성하기 위해 volumne 매핑등 많은 부분을 안 함. → 자세한 내용 공식문서 확인

    nori 형태소 분석기 추가하기

    Dockerfile을 따로 만들어주고 elasticsearch.yml에 이미지로 추가

    # dockerfile 생성
    vi Dockerfile
    
    # dockerfile 작성
    ARG ELK_VERSION
    FROM docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION}
    RUN elasticsearch-plugin install analysis-nori
    
    # elasticsearch.yml 파일 수정
    version: '3.7'
    services:
      es:
        build:    
          # 도커파일의 위치 알려주기
          context: .
          # 인자 넣어주기
          args:
            ELK_VERSION: 7.15.2
        container_name: elasticsearch
        environment:
          - node.name=single-node
          - cluster.name=beomsic
          - discovery.type=single-node
        ports:
          - 9200:9200
          - 9300:9300
        networks:
          - es-bridge
    
      kibana:
        container_name: kibana
        image: docker.elastic.co/kibana/kibana:7.15.2
        environment:
          SERVER_NAME: kibana
          ELASTICSEARCH_HOSTS: <http://elasticsearch:9200>
        ports:
          - 5601:5601
        depends_on:
          - es
        networks:
          - es-bridge
    
    networks:
      es-bridge:
        driver: bridge
    

    Spring boot 설정


    build.gradle

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
    }
    

    Elasticsearch를 사용하기 위해 추가

    yml 파일

    elasticsearch:
      host: localhost
      port: 9200
    
    • 로컬에서 테스트 해볼 예정

    코드 작성


    AbstractElasticsearchConfig.java

    public abstract class AbstractElasticsearchConfig extends ElasticsearchConfigurationSupport {
    
      @Bean
      public abstract RestHighLevelClient elasticsearchClient();
    
      @Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
      public ElasticsearchOperations elasticsearchOperations(
          ElasticsearchConverter elasticsearchConverter,
          RestHighLevelClient elasticsearchClient) {
    
        ElasticsearchRestTemplate elasticsearchRestTemplate = new ElasticsearchRestTemplate(
            elasticsearchClient, elasticsearchConverter);
    
        elasticsearchRestTemplate.setRefreshPolicy(refreshPolicy());
    
        return elasticsearchRestTemplate;
      }
    }
    

    Elasticsearch 관련 작업을 할 때, 주로 ElasticsearchOperations 인터페이스의 구현체를 사용.

    • AbstractElasticsearchConfig 클래스에서 ElasticsearchOperations을 Bean 으로 등록

    elasticsearchClient 추상 메서드로 등록되어 있어 상속받아 구현하여 빈으로 등록을 해준다.

    ElasticsearchProperties.java

    @Component
    public class ElasticsearchProperties {
    
      @Value("${elasticsearch.host}")
      private String host;
    
      @Value("${elasticsearch.port}")
      private Integer port;
    
      public HttpHost httpHost() {
        return new HttpHost(host, port, "http");
      }
    
    }
    
    • Elasticsearch 을 연결하기 위한 필요한 데이터들을 설정해주는 파일

    ElasticsearchConfig.java

    @Configuration
    @EnableElasticsearchRepositories
    public class ElasticsearchConfig extends AbstractElasticsearchConfig {
    
      private final ElasticsearchProperties elasticsearchProperties;
    
      public ElasticsearchConfig(ElasticsearchProperties elasticsearchProperties) {
        this.elasticsearchProperties = elasticsearchProperties;
      }
    
      @Override
      public RestHighLevelClient elasticsearchClient() {
        return new RestHighLevelClient(RestClient.builder(elasticsearchProperties.httpHost()));
      }
    }
    

    Client는 기본적으로 High Level REST Client 를 사용한다.

    ElasticsearchProperties에서 가져온 host:port 에 떠있는 Elasticsearch와 연결

    → ElasticsearchClient를 사용할 수 있게 되었다.

    실제로 사용할 때는 ElasticsearchOperations 또는 ElasticsearchRepository를 사용

    User.java / UserDocument.java

    @Entity
    @Getter
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class User extends BaseEntity {
    
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
    
      private String name;
    
      private String nickname;
    
      private int age;
    
      private String description;
    
    }
    
    @Document(indexName = "user")
    @Mapping(mappingPath = "elastic/user-mapping.json")
    @Setting(settingPath = "elastic/user-setting.json")
    @Getter
    @Builder
    public class UserDocument {
    
      @Id
      private Long id;
    
      private String name;
    
      private String nickname;
    
      private int age;
    
      private String description;
    
      @Field(type = FieldType.Date, format = {DateFormat.date_hour_minute_second_millis, DateFormat.epoch_millis})
      private LocalDateTime createdAt;
    
    }
    

    User 엔티티와 ES에 매핑할 UserDocument 클래스를 따로 만들었다.

    • 클래스 하나에 Entity와 ES를 매핑할 수 있지만 JPA repository와 ES repository를 사용할 때 문제가 생긴다.
    해결 방법
    • @EnableJpaRepository@EnableElasticsearchRepositories 의 속성을 사용
    • 어떤 것은 JPA에만 어떤 것은 ES에만 적용되도록 별도의 세팅을 한다.
    • 이런 해결방법은 새로운 Repository를 추가할 때마다 코드가 추가되어 매우 힘들다.

    ES에 데이터 타입을 매핑하는 2가지 방법

    1. @Field 를 사용 - 간단한 경우
    2. @Setting, @Mapping을 사용 - 복잡한 경우

    @Field

      @Field(type = FieldType.Date, format = {DateFormat.date_hour_minute_second_millis, DateFormat.epoch_millis})
      private LocalDateTime createdAt;

    @Setting, @Mapping

    복잡한 경우 resource 부분에 json 파일을 만들어 사용한다.

    // resource/elastic/user-setting.json
    // 노리 분석기 정의
    
    {
      "analysis" : {
        "analyzer" : {
          "korean" : {
            "type" : "nori"
          }
        }
      }
    }
    
    // resource/elastic/user-mapping.json
    // 데이터 타입을 정의
    // nori 분석기를 사용할 곳에는 분석기도 등록
    
    {
      "properties" : {
        "id" : {"type" :  "keyword"},
        "age" : {"type" :  "keyword"},
        "name" : {"type" :  "keyword"},
        "nickname" : {"type" :  "text"},
        "description" : {
          "type" : "text",
          "analyzer" : "korean"
        },
        "createdAt" : {
          "type" : "date",
          "format" : "yyyy-MM-dd'T'HH:mm:ss.SSS||epoch_millis"
        }
      }
    }
    

    UserSearchRepository.java

    public interface UserSearchRepository extends ElasticsearchRepository<UserDocument, Long> {
    
      List<UserDocument> findByAge(int age);
    
      Page<UserDocument> findByNickname(String nickname, Pageable pageable);
    
    }
    

    ElasicsearchRepository 인터페이스를 확장해 정의

    UserSearchQueryRepository.java

    @Repository
    public class UserSearchQueryRepository {
    
      private final ElasticsearchOperations operations;
    
      public UserSearchQueryRepository(
          ElasticsearchOperations operations) {
        this.operations = operations;
      }
    
      public List<UserDocument> findByCondition(SearchCondition searchCondition, Pageable pageable) {
    
        CriteriaQuery query = createConditionCriteriaQuery(searchCondition)
            .setPageable(pageable);
    
        SearchHits<UserDocument> search = operations.search(query, UserDocument.class);
    
        return search.stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
      }
    
      public List<UserDocument> findByStartWithNickname(String nickname, Pageable pageable) {
        Criteria criteria = Criteria.where("nickname").startsWith(nickname);
        Query query = new CriteriaQuery(criteria).setPageable(pageable);
        SearchHits<UserDocument> search = operations.search(query, UserDocument.class);
        return search.stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
      }
    
      public List<UserDocument> findByMatchedDescription(String description, Pageable pageable) {
        Criteria criteria = Criteria.where("description").matches(description);
        Query query = new CriteriaQuery(criteria).setPageable(pageable);
        SearchHits<UserDocument> search = operations.search(query, UserDocument.class);
        return search.stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
      }
      
      public List<UserDocument> findByContainsDescription(String description, Pageable pageable) {
        Criteria criteria = Criteria.where("description").contains(description);
        Query query = new CriteriaQuery(criteria).setPageable(pageable);
        SearchHits<UserDocument> search = operations.search(query, UserDocument.class);
        return search.stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
      }
    
      private CriteriaQuery createConditionCriteriaQuery(SearchCondition searchCondition) {
    
        CriteriaQuery query = new CriteriaQuery(new Criteria());
    
        if (searchCondition == null)
          return query;
    
        if (searchCondition.getId() != null)
          query.addCriteria(Criteria.where("id").is(searchCondition.getId()));
    
        if(searchCondition.getAge() > 0)
          query.addCriteria(Criteria.where("age").is(searchCondition.getAge()));
    
        if(StringUtils.hasText(searchCondition.getName()))
          query.addCriteria(Criteria.where("name").is(searchCondition.getName()));
    
        if(StringUtils.hasText(searchCondition.getNickname()))
          query.addCriteria(Criteria.where("nickname").is(searchCondition.getNickname()));
    
        return query;
      }
    
    }
    

    QueryDSL을 사용할 때 처럼 복잡한 쿼리를 처리해야 하는 경우 리포지토리를 분리했다.

    검색 조건에 따라서 동적 쿼리를 만들어 처리

    UserService.java

    @Service
    @Transactional(readOnly = true)
    public class UserService {
    
      private final UserRepository userRepository;
      private final UserSearchRepository userSearchRepository;
      private final UserSearchQueryRepository userSearchQueryRepository;
    
      public UserService(
          UserRepository userRepository,
          UserSearchRepository userSearchRepository,
          UserSearchQueryRepository userSearchQueryRepository) {
        this.userRepository = userRepository;
        this.userSearchRepository = userSearchRepository;
        this.userSearchQueryRepository = userSearchQueryRepository;
      }
    
      @Transactional
      public void saveAllUser(SaveAllUserRequest userRequest) {
        List<SaveUserRequest> userRequestList = userRequest.getUserRequestList();
        System.out.println(userRequestList.size());
    
        List<User> userList = userRequest.getUserRequestList()
            .stream()
            .map(UserConverter::to)
            .collect(Collectors.toList());
    
        userRepository.saveAll(userList);
        System.out.println("finish");
      }
    
      @Transactional
      public void saveAllUserDocuments() {
        List<UserDocument> userDocumentList = userRepository
            .findAll()
            .stream()
            .map(UserConverter::from)
            .collect(Collectors.toList());
    
        System.out.println("done1");
    
        userSearchRepository.saveAll(userDocumentList);
      }
    
      public Page<UserResponse> findAllByNickname(String nickname, Pageable pageable) {
        return userSearchRepository.findByNickname(nickname, pageable)
            .map(UserConverter::toUserResponse);
      }
    
      public List<UserResponse> findAllByAge(int age) {
        return userSearchRepository.findByAge(age)
            .stream()
            .map(UserConverter::toUserResponse)
            .collect(Collectors.toList());
      }
    
      public List<UserResponse> searchByCondition(SearchCondition searchCondition, Pageable pageable) {
        return userSearchQueryRepository.findByCondition(searchCondition, pageable)
            .stream()
            .map(UserConverter::toUserResponse)
            .collect(Collectors.toList());
      }
    
      public List<UserResponse> findAllByDescription(String description, Pageable pageable) {
        return userSearchQueryRepository.findByMatchedDescription(description, pageable)
            .stream()
            .map(UserConverter::toUserResponse)
            .collect(Collectors.toList());
      }
    
      public List<UserResponse> findAllByContainedDescription(String description, Pageable pageable) {
        return userSearchQueryRepository.findByContainsDescription(description, pageable)
            .stream()
            .map(UserConverter::toUserResponse)
            .collect(Collectors.toList());
      }
    }
    

    saveAllUser 메서드는 DB에 유저 정보를 저장하는 메서드,

    saveAllUserDocuments 메서드는 DB에서 유저 정보를 꺼내 Document로 변환해서 Elasticsearch에 저장하는 메서드이다.

    변환하는 과정은 UserConverter라는 클래스를 따로 만들어 처리했다.

    UserConverter

    public class UserConverter {
    
      public static UserDocument from(User user) {
        return UserDocument.builder()
            .id(user.getId())
            .name(user.getName())
            .nickname(user.getNickname())
            .age(user.getAge())
            .description(user.getDescription())
            .createdAt(user.getCreatedAt())
            .build();
      }
    
      public static User to(SaveUserRequest request) {
        return User.builder()
            .name(request.getName())
            .nickname(request.getNickname())
            .age(request.getAge())
            .description(request.getDescription())
            .build();
      }
    
      public static UserResponse toUserResponse(UserDocument userDocument) {
        return UserResponse.builder()
            .name(userDocument.getName())
            .nickname(userDocument.getNickname())
            .age(userDocument.getAge())
            .description(userDocument.getDescription())
            .build();
      }
    }
    

    UserController.java

    @RestController
    @RequestMapping("/api/v1/users")
    public class UserController {
    
      private final UserService userService;
    
      public UserController(UserService userService) {
        this.userService = userService;
      }
    
      @PostMapping()
      public ResponseEntity<Void> saveAll(@RequestBody SaveAllUserRequest userRequest) {
        userService.saveAllUser(userRequest);
        return ResponseEntity.ok().build();
      }
    
      @PostMapping("/Documents")
      public ResponseEntity<Void> saveAllUserDocuments() {
        userService.saveAllUserDocuments();
        System.out.println("done2");
        return ResponseEntity.ok().build();
      }
    
      @GetMapping()
      public ResponseEntity<List<UserResponse>> searchByName(
          SearchCondition searchCondition,
          @PageableDefault(page = 0, size = 10) Pageable pageable) {
    
        List<UserResponse> userList = userService.searchByCondition(searchCondition, pageable);
        return ResponseEntity.ok(userList);
      }
    
      @GetMapping("/age")
      public ResponseEntity<List<UserResponse>> getByAge(@RequestParam int age) {
    
        List<UserResponse> userList = userService.findAllByAge(age);
        return ResponseEntity.ok(userList);
      }
    
      @GetMapping("/nickname")
      public ResponseEntity<Page<UserResponse>> getByNickname(
          @RequestParam String nickname,
          @PageableDefault(page = 0, size = 10) Pageable pageable) {
    
        Page<UserResponse> userList = userService.findAllByNickname(nickname, pageable);
        return ResponseEntity.ok(userList);
      }
    
      @GetMapping("/matchedDescription")
      public ResponseEntity<List<UserResponse>> getByMatchedDescription(
          @RequestParam String description,
          @PageableDefault(page = 0, size = 10) Pageable pageable) {
    
        List<UserResponse> userList = userService.findAllByDescription(description, pageable);
        return ResponseEntity.ok(userList);
      }
    
      @GetMapping("/containedDescription")
      public ResponseEntity<List<UserResponse>> getByContainedDescription(
          @RequestParam String description,
          @PageableDefault(page = 0, size = 10) Pageable pageable) {
    
        List<UserResponse> userList = userService.findAllByContainedDescription(description, pageable);
        return ResponseEntity.ok(userList);
      }
    }
    

    실행 시키기


    IntelliJ에서 제공하는 .http 를 사용

    미리 저장할 유저 정보들을 JSON 파일로 만들어 사용

    User.json

    {
      "userRequestList": [
        {
          "name": "고범석1",
          "nickname": "beomsic1",
          "age": 26,
          "description": "안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 홍길도입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석1",
          "nickname": "beomsic1",
          "age": 26,
          "description": "안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 홍길도입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석1",
          "nickname": "beomsic1",
          "age": 26,
          "description": "안녕하세요 안녕하세요 안녕하세요 안녕하세요 안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석2",
          "nickname": "beomsic2",
          "age": 26,
          "description": "안녕하세요 안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석2",
          "nickname": "beomsic2",
          "age": 26,
          "description": "안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석3",
          "nickname": "beomsic3",
          "age": 26,
          "description": "안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석4",
          "nickname": "beomsic4",
          "age": 26,
          "description": "안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석4",
          "nickname": "beomsic4",
          "age": 26,
          "description": "안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석4",
          "nickname": "beomsic4",
          "age": 26,
          "description": "안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석4",
          "nickname": "beomsic4",
          "age": 25,
          "description": "안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석4",
          "nickname": "beomsic4",
          "age": 25,
          "description": "안녕하세요 고범석입니다. 만나서 반갑습니다."
        },
        {
          "name": "고범석4",
          "nickname": "beomsic4",
          "age": 26,
          "description": "안녕하세요"
        }
      ]
    }
    

    User.http

    # 엔티티(유저 리스트) 저장
    POST <http://localhost:8080/api/v1/users>
    Content-Type: application/json
    
    < ./User.json
    
    ###
    
    # 엔티티 document로 전환해서 ES에 저장 (user -> userDocument)
    POST <http://localhost:8080/api/v1/users/Documents>
    
    ###
    
    # 닉네임 검색
    GET <http://localhost:8080/api/v1/users/nickname?nickname=beomsic1&size=10>
    
    ###
    
    # 나이 검색
    GET <http://localhost:8080/api/v1/users/age?age=26&size=10>
    
    ###
    
    # 조건 검색
    GET <http://localhost:8080/api/v1/users?id=1&name=고범석1&nickname=beomsic1&age=26&size=10>
    
    ###
    
    # 일부 조건만 넣어 검색
    GET <http://localhost:8080/api/v1/users?nickname=beomsic1&age=26&size=10>
    
    ###
    
    # nickname으로 시작하는 것들 찾기
    GET <http://localhost:8080/api/v1/users/nickname/startwith?nickname=beomsic1&size=10>
    
    ###
    
    # description 매치되는 것 찾기
    GET <http://localhost:8080/api/v1/users/matchedDescription?description=안녕하세요&size=10>
    
    ###
    
    # 포함하는 것 찾기
    # 노리 분석기가 안녕하세요를 [안녕,하,시,어요] 로 토큰화 하기 때문에 안녕하세요는 contain으로 찾을 수 없다.
    GET <http://localhost:8080/api/v1/users/containedDescription?description=안녕&size=10>
    

    ###’ 는 각 요청을 구분해주는 역할

    < 를 통해서 json 파일을 지정해 전송할 수 있다.

    먼저 2개의 POST 요청을 보내 데이터를 추가

    • localhost:5601 로 들어가 키바나에 접속
    • Discover를 클릭하고 create index pattern을 클릭해
    • Name에 user를 입력 → user가 index로 등록되어 있는 것이 보인다.
      • 코드에서 @Document(indexName = “user”)
    • create
    • 다시 discover 클릭시 데이터가 잘 들어가 있는 것을 확인할 수 있다.

    다른 GET 요청을 보내 데이터가 제대로 나오는지 확인

    테스트 코드 짜기


    테스트 코드를 위해 다른 TEST 용 Elasticsearch가 필요하다

    • TestContainer를 사용

    build.gradle

    testImplementation "org.testcontainers:elasticsearch"1.16.3"
    

    ElasticTestContainer.java

    // 기존에 정의한 config를 커스터마이징하고자 할 때 사용
    // 테스트가 실행될 때 정의된 빈을 생성하여 등록
    @TestConfiguration
    // ES 관련 repository 등록
    @EnableElasticsearchRepositories(basePackageClasses = {
        UserSearchRepository.class,
        UserSearchQueryRepository.class
    })
    public class ElasticSearchTestContainer extends AbstractElasticsearchConfig {
    
    	private static final String ELASTICSEARCH_VERSION = "7.15.2";
    	private static final DockerImageName ELASTICSEARCH_IMAGE =
          DockerImageName
              .parse("docker.elastic.co/elasticsearch/elasticsearch")
              .withTag(ELASTICSEARCH_VERSION);
    
    	private static final ElasticsearchContainer container;
    	
    	// test container 띄우기
    	static {
    	   container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE);
    	   container.start();
    	}
    	
    	// ES Client 재정의
    	@Override
    	public RestHighLevelClient elasticsearchClient() {
    	   ClientConfiguration clientConfiguration = ClientConfiguration.builder()
            .connectedTo(container.getHttpHostAddress())
            .build();
    
        return RestClients.create(clientConfiguration).rest();
      }
    }
    

    노리 분석기 사용

    // build.gradle
    
    //    testImplementation "org.testcontainers:elasticsearch:1.16.3"
        testImplementation("org.testcontainers:junit-jupiter:1.16.3"
    

    ES testContainer에 Nori 분석기를 설치해주어야 한다.

    • ElasticsearchContainer를 이용해서는 할 수 없다.
    • 따라서 이를 해결하기 위해 상위 클래스인 GenericContainer를 사용

    ES container 의존성이 필요 없다.

    ElasticSearchTestContainerWithNori.java

    // Nori 분석시 사용하려고 할 시 ES container에 Nori 분석기도 설치
    @TestConfiguration
    @EnableElasticsearchRepositories(basePackageClasses = {
        UserSearchRepository.class,
        UserSearchQueryRepository.class
    })
    public class ElasticSearchTestContainerWithNori extends AbstractElasticsearchConfig {
    
      private static final GenericContainer genericContainer;
    
      static {
        genericContainer = new GenericContainer(
            new ImageFromDockerfile()
                .withDockerfileFromBuilder(dockerfileBuilder -> {
                  dockerfileBuilder
                      .from("docker.elastic.co/elasticsearch/elasticsearch:7.15.2") // es 이미지 가져오기
                      .run("bin/elasticsearch-plugin install analysis-nori") // nori 분석기 설치
                      .build();
                })
        )
            .withExposedPorts(9200, 9300)  // 기본 포트 설정
            .withEnv("discovery.type", "single-node"); // ES 가 싱글노드로 돌아가도록 설정
    
        genericContainer.start();
      }
    
      @Override
      public RestHighLevelClient elasticsearchClient() {
    
        // ES container에서 제공해주던 httpHostAddress를 사용할 수 없어 직접 만들어줘야 한다.
        String hostAddress = new StringBuilder()
            .append(genericContainer.getHost())
            .append(":")
            .append(genericContainer.getMappedPort(9200))
            .toString();
    
        ClientConfiguration clientConfiguration = ClientConfiguration.builder()
            .connectedTo(hostAddress)
            .build();
    
        return RestClients.create(clientConfiguration).rest();
      }
    }
    

    참고

    https://backtony.github.io/spring/elk/2022-03-02-spring-elasticsearch-2/

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

    https://veluxer62.github.io/tutorials/spring-data-elasticsearch-test-with-test-container/

    https://www.testcontainers.org/features/creating_images/

    '🔍 elastic search' 카테고리의 다른 글

    ES - Aggregations  (0) 2022.09.22
    ES - 텍스트 분석  (0) 2022.09.22
    ES - 데이터 검색  (1) 2022.09.21
    ES - 기본 API  (0) 2022.09.21
    Docker로 ES 설치하기  (1) 2022.09.21

    댓글

Designed by Tistory.