eclipes4j's 개발은 언제나 즐겁다.

Local Docker Elasticsearch 운영 - Spring + High Level Client

Programming!

이전에 설치했던 elasticsearch 가 잘 운영되고 있다면..


http://127.0.0.1:9200/_cat/health

1545724265 07:51:05 docker-cluster green 2 2 10 5 0 0 0 0 - 100.0%



스프링부트로 새로운 프로젝트를 생상한다.

IDEA 도움을 받아 프로젝트 설치 후, build.gradle 파일을 열어보자

buildscript {

ext {

springBootVersion = '2.1.1.RELEASE'

}

repositories {

mavenCentral()

}

dependencies {

classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")

}

}


apply plugin: 'java'

apply plugin: 'eclipse'

apply plugin: 'org.springframework.boot'

apply plugin: 'io.spring.dependency-management'


group = 'com.example'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = 1.8


repositories {

mavenCentral()

}



dependencies {

    //implementation('org.springframework.boot:spring-boot-starter-data-elasticsearch')

    compile('org.elasticsearch.client:elasticsearch-rest-client:6.5.4')

    compile('org.elasticsearch.client:elasticsearch-rest-high-level-client:6.5.4')

    compile('com.google.guava:guava:27.0.1-jre')

    

    

    compile('org.springframework.boot:spring-boot-starter-web')

    compile('org.springframework.boot:spring-boot-configuration-processor')

    compile('org.projectlombok:lombok')

    testImplementation('org.springframework.boot:spring-boot-starter-test')

}


기본적으로 elasticsearch를 지정하면 spring-data 버전이 따라오는데, elasticsearch를 저장소 개념으로 사용한다면 data를 사용해도 되지만 단순히 검색을 사용하는 것이라 개인적인 선호도를 반영해 high level client를 사용하게 되었다.


elasticsearch hlc를 사용하기 위한 설정 코드를 작성한다.

@Configuration

public class ElasticSearchConfiguration {

private static final String HTTP_SCHEME = "http";


@Value("#{'${elasticsearch.hosts}'.split(',')}")

private List<String> esHosts;

@Value("${elasticsearch.port:9200}")

private int port;


@Bean(name = "esObjectMapper")

public ObjectMapper esObjectMapper() {

return new ObjectMapper();

}


@Bean(name = "esRestClientBuilder")

public RestClientBuilder esRestClientBuilder() {

HttpHost[] hosts = esHosts.stream().map(this::makeHttpHost).filter(Objects::nonNull).toArray(HttpHost[]::new);

return RestClient.builder(hosts);

}


@Bean(name = "esHighLevelClient", destroyMethod = "close")

public RestHighLevelClient highLevelClient(@Autowired @Qualifier("esRestClientBuilder") RestClientBuilder esRestClientBuilder) {

esRestClientBuilder.setMaxRetryTimeoutMillis(3000);

return new RestHighLevelClient(esRestClientBuilder);

}


private HttpHost makeHttpHost(String ip) {

return new HttpHost(ip, port, HTTP_SCHEME);

}

}


작성중...

















Local Docker Elasticsearch 설치

Programming!

제공괴는 Image에 추가로 몇몇 플러그인등을 적용해야하니 별도의 Dockerfile을 작성한다.

$> vi Dockerfile


# Elasticsearch 6.5.4


FROM docker.elastic.co/elasticsearch/elasticsearch:6.5.4

WORKDIR /usr/share/elasticsearch

RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch discovery-ec2

RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch analysis-nori

$> docker build -t elasticsearch-my6_5_4 .


EC2 용 플러그인과 한글 형태소 Nori를 추가했다. build 실행 후,image목록을 보면 아래와 같이 추가되어 있는 image를 볼 수 있다.

$> docker images

REPOSITORY                                      TAG                 IMAGE ID            CREATED              SIZE

elasticsearch-my_6_5_4                  latest              d21d0f522868        About a minute ago   788MB

docker.elastic.co/elasticsearch/elasticsearch   6.5.4               93109ce1d590        7 days ago           774MB




이제 docker-compose.yml를 만든다.


version: '2.2'

services:

  elasticsearch:

    image: elasticsearch-my_6_5_4 

    container_name: elasticsearch

    environment:

      - node.name=es01

      - cluster.name=docker-cluster

      - bootstrap.memory_lock=true

      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"

    ulimits:

      memlock:

        soft: -1

        hard: -1

    volumes:

      - esdata1:/usr/share/elasticsearch/data

    ports:

      - 9200:9200

      - 9300:9300

    networks:

      - esnet

  elasticsearch2:

    image: elasticsearch-my_6_5_4 

    container_name: elasticsearch2

    environment:

      - node.name=es02

      - cluster.name=docker-cluster

      - bootstrap.memory_lock=true

      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"

      - discovery.type=zen

      - "discovery.zen.ping.unicast.hosts=elasticsearch"

    ulimits:

      memlock:

        soft: -1

        hard: -1

    volumes:

      - esdata2:/usr/share/elasticsearch/data

    networks:

      - esnet

volumes:

  esdata1:

    driver: local

  esdata2:

    driver: local


networks:

  esnet:


실행

$> docker-compose up -d


실행확인 

$> docker ps -a

CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS                  PORTS                                            NAMES

b9ce987bdc8e        elasticsearch-my_6_5_4   "/usr/local/bin/dock…"   36 seconds ago      Up 34 seconds           0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   elasticsearch

92f8e1d8853e        elasticsearch-my_6_5_4   "/usr/local/bin/dock…"   36 seconds ago      Up 34 seconds           9200/tcp, 9300/tcp                               elasticsearch2



http://127.0.0.1:9200/_cat/health



설치는 여기까지. 이제 Spring boot 를 이용하여 Document의 입력,수정,삭제,검색을 진행해 보자.



QueryDSL 이용시 datetime column type에 대한 group by

Programming!

아래와 같이 세개의 컬럼이 존재한다. 



상품번호 

 판매수량

 일자 

 100003

 2

 2018-12-19 12:23:11

 100003

 1

 2018-12-18 11:13:09

 100003

 1

 2018-12-18 09:13:09


요구사항은 일자별 판매수량 sum이라고 치자.


즉, 아래와 같이 시간은 제거되고 일자 만으로 groupby와 판매수량에 대한 sum 처리를 해야 한다.


100003 | 2 | 2018-12-19

100003 | 2 | 2018-12-18


QueryDSL으로 이용하려니 날짜에 대한 시간 제거를 모르겠다..흠.

해서 MySQL의 자체 function을 이용하는 방법으로 우선 해결.


코드는 다음과 같다.

StringTemplate datePathOrderDate = Expressions.stringTemplate(

    "DATE_FORMAT({0},'{1s}')", qOrderItem.orderDate, ConstantImpl.create("%Y-%m-%d")); 



from(qOrderItem).where(...).and(...)

.select(Projections.constructor(LowStockGoodsStatistic.class ...sum()datePathOrderDate.as("salesDay") ))

.groupBy(datePathOrderDate).fetch();


Entity가 없는 일반 통계용 결과이므로 Projections.constructor 를 사용했다.

 

Annotation 정리 문서 - 추가중

Programming!
이렇게 정리해두니 좋네~

https://dzone.com/articles/all-java-built-in-annotation-examples

https://dzone.com/articles/spring-web-mvc-annotation-examples

https://dzone.com/articles/all-hibernate-annotations-mapping-annotations

https://dzone.com/articles/all-jpa-annotations-mapping-annotations


Spring Boot 에서 다중 Datasource 의 사용. #2

Programming!

첫번째.


http://eclipse4j.tistory.com/284



Spring Boot / Data / JPA 에서 위와 같이 다중 Datasource를 기본적으로 사용했다면 문제가 없지만, 

만약 QueryDsl을 사용시에는 QuerydslRepositorySupport 부분에서 어떤 EntityManager를 선택해야 하는지 몰라서(?) 오류를 내뱉는다.


QuerydslRepositorySupport.java 

@Repository

public abstract class QuerydslRepositorySupport {


private final PathBuilder<?> builder;


private @Nullable EntityManager entityManager;

private @Nullable Querydsl querydsl;


/**

* Creates a new {@link QuerydslRepositorySupport} instance for the given domain type.

*

* @param domainClass must not be {@literal null}.

*/

public QuerydslRepositorySupport(Class<?> domainClass) {


Assert.notNull(domainClass, "Domain class must not be null!");

this.builder = new PathBuilderFactory().create(domainClass);

}


/**

* Setter to inject {@link EntityManager}.

*

* @param entityManager must not be {@literal null}.

*/

@Autowired

public void setEntityManager(EntityManager entityManager) {


Assert.notNull(entityManager, "EntityManager must not be null!");

this.querydsl = new Querydsl(entityManager, builder);

this.entityManager = entityManager;

}

...



보면 setEntityManager(EntityManager entityManager) { .. 이부분에 Autowired 가 걸려있는 것을 확인 할 수 있다. 저 EntityManager가 과연 다중 Datasource에서 어떤것인지 알 수 있겠는가?

해서 persistentUnit을 지정해줘야 한다. 하지만 QuerydslRepositorySupport 이놈은 제공 소스아니던가...



단순 클래스니까..  QuerydslRepositorySupport 와 동일한 FirstQuerydslRepositorySupport, SecondQuerydslRepositorySupport 로 두개를 분리해서 사용하도록 하자.

...

    @PersistenceContext(unitName = "first")

    public void setEntityManager(EntityManager entityManager) {

        Assert.notNull(entityManager, "EntityManager must not be null!");

        this.querydsl = new Querydsl(entityManager, builder);

        this.entityManager = entityManager;

    }

...