Elasticsearch를 Spring Boot에서 작업을 하는 간단한 정리를 해보자.


1. Library 추가

Elasticsearch를 사용하기 위해서는 spring-data-elasticsearch 라이브러리가 추가되어야 한다. 

gradle에 추가해보자.

1
2
3
4
5
6
7
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    compileOnly "org.projectlombok:lombok:1.16.16"
}
 
cs


spring-data-elasticsearch 버전별로 호환되는 elasticsearch가 상이하니 참고

spring data elasticsearchelasticsearch
3.2.x6.5.0
3.1.x6.2.2
3.0.x5.5.0
2.1.x2.4.0
2.0.x2.2.0
1.3.x1.5.2


2. Configuration

Elasticsearch에 접속하기 위한 Configuration을 정의해준다.

Elasticsearch  접속을 위해서는 host, port, cluster name이 필요하다. cluster name을 알아야 하는데 docker에 설치 한 경우 여기서 확인하면 된다.

우선 docker exec -it elastic bash로 콘솔에 접속한 후에 elasticsearch.yml에 적혀있는 cluster name을 확인한다.

그리고 application.properties에 설정 내용을 적어준다.

1
2
3
4
elasticsearch.host=127.0.0.1
elasticsearch.port=9300
elasticsearch.cluster_name=docker-cluster
spring.main.allow-bean-definition-overriding=true
cs

그리고 EnableElasticsearchRepositories 애노테이션을 설정한 Configuration 클래스를 만들어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.elasticsearch.study.configuration;
 
import org.springframework.beans.factory.annotation.Value;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
 
import java.net.InetAddress;
 
/**
 * Elasticsearch Configuration
 *
 * @author wedul
 * @since 2019-02-09
 **/
@EnableElasticsearchRepositories
@Configuration
public class ElasticConfiguration {
 
  @Value("${elasticsearch.host}")
  private String host;
 
  @Value("${elasticsearch.port}")
  private int port;
 
  @Value("${elasticsearch.cluster_name")
  private String clusterName;
 
  @Bean
  public Client client() throws Exception {
    Settings settings = Settings.builder().put("cluster.name", clusterName).build();
 
    TransportClient client = new PreBuiltTransportClient(settings);
    client.addTransportAddress(new TransportAddress(InetAddress.getByName(host), port));
    return client;
  }
 
  @Bean
  public ElasticsearchOperations elasticsearchTemplate() throws Exception {
    return new ElasticsearchTemplate(client());
  }
 
}
 
cs


3. DTO 생성

Elasticsearch에서 Document 내용을 담을 DTO를 만들어주고 @Document 애노테이션을 달고 index name과 type을 정의해준다.

@Id 어노테이션이 붙은 필드는 각 Doucument에 붙어있는 _id 값이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.elasticsearch.study.dto;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
 
/**
 * studyFor
 *
 * @author wedul
 * @since 2019-02-09
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Document(indexName = "wedul_play", type = "story")
public class WedulPlay {
 
  @Id
  private String id;
  private String title;
  private String user;
  private long startAt;
  private long endAt;
 
}
 
cs


4. Repository

JPA를 사용하면 익숙할 패턴으로 Elasticsearch에서도 ElasticsearchRepository가 존재한다. 사용방법은 JPA와 동일하게 저장할 때는 save, 조회할 때는 find(), findByUser()등으로 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.elasticsearch.study.repository;
 
import com.elasticsearch.study.dto.WedulPlay;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
 
/**
 * study
 *
 * @author wedul
 * @since 2019-02-09
 **/
@Repository("wedulPlayRepository")
public interface WedulPlayRepository extends ElasticsearchRepository<WedulPlay, String> {
 
  WedulPlay findByUser(String user);
  
}
 
cs


5. Service

지금 테스트 하는 부분에서는 크게 비즈니스 로직에 들어갈 소스가 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.elasticsearch.study.service;
 
import com.elasticsearch.study.dto.WedulPlay;
import com.elasticsearch.study.repository.WedulPlayRepository;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
 
import java.util.List;
 
/**
 * study
 *
 * @author wedul
 * @since 2019-02-09
 **/
@AllArgsConstructor
@NoArgsConstructor
@Service
public class WedulPlayService {
 
  private WedulPlayRepository wedulPlayRepository;
 
  public void save(WedulPlay play) {
    wedulPlayRepository.save(play);
  }
 
  public List<WedulPlay> findAll() {
    return Lists.newArrayList(wedulPlayRepository.findAll());
  }
 
  public WedulPlay findByUser(String user) {
    return wedulPlayRepository.findByUser(user);
  }
 
}
 
cs


6. Test 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.elasticsearch.study.wedulplay;
 
import com.elasticsearch.study.dto.WedulPlay;
import com.elasticsearch.study.repository.WedulPlayRepository;
import com.elasticsearch.study.service.WedulPlayService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
import java.util.List;
 
import org.hamcrest.core.IsNull;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
 
/**
 * wedul play document 조회
 *
 * @author wedul
 * @since 2019-02-09
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
public class WedulPlayTest {
 
  WedulPlayService wedulPlayService;
 
  @Autowired
  @Qualifier("wedulPlayRepository")
  WedulPlayRepository wedulPlayRepository;
 
  @Before
  public void setup() {
    wedulPlayService = new WedulPlayService(wedulPlayRepository);
  }
 
  @Test
  public void whenValidParameter_thenSuccessFind() {
    List<WedulPlay> list = wedulPlayService.findAll();
 
    assertNotNull(list);
  }
 
  @Test
  public void whenValidParameter_thenSuccessSave() {
    Exception ex = null;
 
    try {
      wedulPlayService.save(WedulPlay.builder().title("안녕 이건 테스트야").user("위들").startAt(1242421424).endAt(23214124).build());
    } catch (Exception exception) {
      ex = exception;
    }
 
    assertTrue(null == ex);
  }
 
  @Test
  public void whenValidParameter_thenSuccessFindByUser() {
    Exception ex = null;
 
    try {
      WedulPlay play = wedulPlayService.findByUser("위들");
 
      assertThat(play, is(IsNull.notNullValue()));
    } catch (Exception exception) {
      ex = exception;
    }
 
    assertTrue(null == ex);
  }
 
 
}
 
cs


설정이 간단하다. 

나중에 이용해 먹어야지


자세한 소스코드는 여기 참조

https://github.com/weduls/spring_elastic

결국은 순회하면서 하는 DFS를 했는데 다음번에는 DP 또는 그래프 문제를 좀 많이 풀어 보고 싶다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import java.util.*;
 
public class Main {
 
    public static void main(String[] args) {
 
        // 붕어빵 개수
        Scanner sc = new Scanner(System.in);
        List<LandDto> lands = new ArrayList<>();
 
        while (true) {
            int weight = sc.nextInt();
            int height = sc.nextInt();
 
            if (weight == 0 && height == 0) {
                break;
            }
 
            int[][] dataMatrix = new int[height][weight];
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < weight; x++) {
                    dataMatrix[y][x] = sc.nextInt();
                }
            }
 
            lands.add(new LandDto(weight, height, dataMatrix, new boolean[height][weight]));
        }
 
        lands.forEach(land -> {
            calLandCount(land);
        });
 
    }
 
    private static void calLandCount(LandDto landDto) {
        int count = 0;
        for (int y = 0; y < landDto.getHeight(); y++) {
            for (int x = 0; x < landDto.getWeight(); x++) {
                if (!landDto.getVisited()[y][x]) {
                    if (landDto.getDataMatrix()[y][x] == 1) {
                        count++;
                        findLand(x, y, landDto);
                    } else {
                        landDto.getVisited()[y][x] = true;
                    }
                }
            }
        }
        System.out.println(count);
    }
 
    private static void findLand(int x, int y, LandDto landDto) {
        landDto.getVisited()[y][x] = true;
 
        if (checkIsGo(x + 1, y, landDto)) {
            findLand(x + 1, y, landDto);
        }
 
        if (checkIsGo(x + 1, y - 1, landDto)) {
            findLand(x + 1, y - 1, landDto);
        }
 
        if (checkIsGo(x, y - 1, landDto)) {
            findLand(x, y - 1, landDto);
        }
 
        if (checkIsGo(x - 1, y - 1, landDto)) {
            findLand(x - 1, y - 1, landDto);
        }
 
        if (checkIsGo(x - 1, y, landDto)) {
            findLand(x - 1, y, landDto);
        }
 
        if (checkIsGo(x - 1, y + 1, landDto)) {
            findLand(x - 1, y + 1, landDto);
        }
 
        if (checkIsGo(x, y + 1, landDto)) {
            findLand(x, y + 1, landDto);
        }
 
        if (checkIsGo(x + 1, y + 1, landDto)) {
            findLand(x + 1, y + 1, landDto);
        }
 
    }
 
    private static boolean checkIsGo(int x, int y, LandDto landDto) {
        return x >= 0 && y >= 0 && x < landDto.getWeight() && y < landDto.getHeight() && landDto.getDataMatrix()[y][x] == 1 && !landDto.getVisited()[y][x];
    }
 
    static class LandDto {
        private int weight;
        private int height;
 
        private int[][] dataMatrix;
        private boolean[][] visited;
 
        LandDto(int weight, int height, int[][] dataMatrix, boolean[][] visited) {
            this.weight = weight;
            this.height = height;
            this.dataMatrix = dataMatrix;
            this.visited = visited;
        }
 
        public int getWeight() {
            return weight;
        }
 
        public int getHeight() {
            return height;
        }
 
        public int[][] getDataMatrix() {
            return dataMatrix;
        }
 
        public boolean[][] getVisited() {
            return visited;
        }
 
    }
 
}
 
cs

자세한 소스는 git 참고

https://github.com/weduls/algorithm/tree/master/%EA%B7%B8%EB%9E%98%ED%94%84/%EC%84%AC%EC%9D%98%20%EA%B0%9C%EC%88%98

Assert는 node.js의 단위 테스트를 하기위해서 제공되는 테스트 모듈이다. 해당 모듈의 주요 메소드를 정리해보자.


Assert 메소드 정리

// 모듈 로드
const assertion = require('assert');

assertion.ok(object)
인자값이 true 아니면 에러
assertion.ifError(object)
인자값이 false 아니면 에러
assertion.equal(object, object) 
두 개의 인자를 == 으로 비교 
assertion.notEqual(object, object)
두 개의 인자를 != 으로 비교
assertion.staticEqual(object, object)
두 개의 인자를 === 으로 비교
assertion.notStaticEqual(object, object)
두 개의 인자를 !== 으로 비교
assertion.deepEqual(object, object)
객체를 그냥 equal 하게 되면 객체를 참조하고 있는 주소가 서로 다르기 때문에 무조건 false가 나온다. 그래서 해당 객체의 속성값을 == 으로 비교해주는 메소드
assertion.notDeepEqual(object, object)
deepEqual 메소드를 != 으로 비교하는 메서드
assertion.deepStrictEqual() 
deepEqual 메소드를 === 으로 비교하는 메서드
assertion.notDeepStrictEqual() 
deepEqual 메소드를 !== 으로 비교하는 메서드
assertion.throws(() => new Error(‘에러’), TypeError) 
인자로 넘어온 값이 함수이면서, 반환값이 에러인경우 ( 두 번째 인자는 오류의 유형인데 option)
assertion.doesNotThrow(() => new Error(‘에러’), TypeError)
인자로 넘어온 값이 함수이면서, 반환값이 에러가 아닌 경우 ( 두 번째 인자는 오류의 유형인데 option)
assertion.fail() 
무조건 실패


단일 책임 원칙 (Single Responsibility Principle) SRP
-> 객체는 오직 하나의 책임만을 가져야한다.
-> 객체에서 책임이라고 한다면 객체의 역할을 의미한다.

좋은 설계란
=> 기본적으로 시스템에 새로운 요구사항이나 변경이 있을 때 가능한 한 영향 받는 부분을 줄여야 한다.

프로그램의 요구사항은 계속해서 변경되기 마련이다.
이를 위해서 항상 변경이 가능하도록 조치가 되어있어야 하며, 이런 시스템이 변화에 잘 적용되도록 설계되어 있는지 확인 하는 것을 회귀 테스트(regression)라고 한다.

개방폐쇄원칙 (Open-Closed principle) OCP 
- 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다.


리스코프 치한원칙 
- 자식클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야한다.
- 자식 클래스와 부모클래스 사이의 행위가 일관성이 있어야 한다.
- is a kind of 관계 사이를 유지하라.

의존 역전 원칙 Dependency Inversion Priciple 
- 의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 거, 거의 변화가 없는 것에 의존하라.
- 아이가 장난감을 가지고 노는 행위자체는 변화하기 어렵지만, 가지고 노는 장난감은 변화가 쉽다.
- 행위를 적는 인터페이스는 변화가 적고, 실채가 있는 구체 클래스는 변하기 쉽다.

인터페이스 분리 원칙 (Interface Segregation Principle)
- 복합기라는 하나의 커다란 인터페이스를 구현한 팩스 클래스가 갑작스러운 복합기 기능의 변경으로 인해 인터페이스가 변경되면 영향을 받을 수 있다. 그러한 이유로 자신이외에 다른 곳에서 크게 사용하지 않는 특징일 경우 별도의 인터페이스로 관리하는 것이 좋다.
- 진짜 변하지 않는 인터페이스를 상속 받아 재 구현한 인터페이스를 이용하여 클래스를 구현하는 방식을 이용하면 된다.
- SRP (단일 책임 원칙)을 생각하면서 인터페이스도 분리를 해야한다.

결론.
하나의 객체의 하나의 역할을. 관련이 없는 것 끼리는 서로 독립시킬 것.
섣부른 상속은 큰 문제를 야기할 수 있다.



'IT 지식 > 소프트웨어 공학' 카테고리의 다른 글

프로그래밍 코드 작성 시 주요원칙  (0) 2018.05.30
피터 코드의 상속 규칙  (0) 2018.05.30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
for(int i = 0; i<money.length ;i++){
 
    min = i;
 
    for(int j=i;j<money.length;j++){
 
     if(money[j]<money[min])
 
      min=j;
 
    }
 
    temp=money[i];
 
    money[i] = money[min];
 
    money[min]=temp;
 
   }
 
 
 
 
 
 
 
여기서 money.length는 정렬하고자하는 배열의 키기임
 
money는 정렬하고자하는 배열의 이름을 칭함
cs


'JAVA > 알고리즘' 카테고리의 다른 글

정렬알고리즘 - 버블정렬  (0) 2018.05.28
정렬알고리즘 - 삽입정렬  (0) 2018.05.28
정렬알고리즘 - 선택정렬  (0) 2018.05.28
10진수 2진수 변환  (0) 2018.05.28
더블링크드 리스트 구현하기  (0) 2018.05.28
백준 1924 - 요일 맞추기  (0) 2018.05.28

+ Recent posts