Skip to content

Latest commit

 

History

History
579 lines (494 loc) · 14 KB

File metadata and controls

579 lines (494 loc) · 14 KB

elasticsearch 설치

# elasticsearch 컨테이너 실행
$ docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -it -d --name es01 docker.elastic.co/elasticsearch/elasticsearch:8.7.0

# elasticsearch 설정파일을 컨테이너에서 로컬환경으로 copy
$ docker cp es01:/usr/share/elasticsearch/config <로컬 경로>

# elasticsearch 설정파일 vim으로 열기
$ vim elasticsearch.yml
cluster.name: "docker-cluster"
network.host: 0.0.0.0

#----------------------- BEGIN SECURITY AUTO CONFIGURATION -----------------------
#
# The following settings, TLS certificates, and keys have been automatically
# generated to configure Elasticsearch security features on 01-08-2024 04:23:52
#
# --------------------------------------------------------------------------------

# Enable security features
xpack.security.enabled: false

xpack.security.enrollment.enabled: false

# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
xpack.security.http.ssl:
  enabled: false
  keystore.path: certs/http.p12

# Enable encryption and mutual authentication between cluster nodes
xpack.security.transport.ssl:
  enabled: false
  verification_mode: certificate
  keystore.path: certs/transport.p12
  truststore.path: certs/transport.p12
#----------------------- END SECURITY AUTO CONFIGURATION -------------------------
# 로컬에서 수정한 elasticsearch.yml을 컨테이너로 복사
$ docker cp <로컬 elasticsearch.yml 경로> es01:/usr/share/elasticsearch/config/elasticsearch.yml

# 컨테이너 재실행
$ docker restart es01

# 컨테이너 정상수행 확인
$ curl -XGET "localhost:9200"

kibana 설치

# kibana 컨테이너 실행
$ docker run -it -d --link es01 -p 5601:5601 --name kibana [docker.elastic.co/kibana/kibana:8](http://docker.elastic.co/kibana/kibana:8).14.3

$ docker cp kibana:/usr/share/kibana/config <로컬 경로>

$ vim kibana.yml
#
# ** THIS IS AN AUTO-GENERATED FILE **
#

# Default Kibana configuration for docker target
server.host: "0.0.0.0"
server.shutdownTimeout: "5s"
elasticsearch.hosts: [ "http://es01:9200" ]
monitoring.ui.container.elasticsearch.enabled: true
# 컨테이너 재실행
$ docker restart kibana

# localhost:5601 접속
$ wget http://media.sundog-soft.com/es/ml-latest-small.zip
$ unzip ml-latest-small.zip
# movies 인덱스 맵핑정보 추가
PUT /movies
{
  "mappings": {
    "properties": {
      "year":{
        "type": "date"
      }
    }
  }
}

# movies 인덱스의 맵핑정보 조회
GET /movies/_mapping

# movies 인덱스에 document 추가
PUT /movies/_doc/109487
{
  "genre" : ["IMAX","Sci-Fi"],
  "title" : "Interstellar",
  "year" : 2014
}

# movies 인덱스에 존재하는 모든 documents 조회
GET /movies/_search

# bulk로 다양한 연산을 한 번에 가능
# http://media.sundog-soft.com/es8/movies.json 에서 다운로드 가능
PUT /_bulk
{ "create" : { "_index" : "movies", "_id" : "135569" } }
{ "id": "135569", "title" : "Star Trek Beyond", "year":2016 , "genre":["Action", "Adventure", "Sci-Fi"] }
{ "create" : { "_index" : "movies", "_id" : "122886" } }
{ "id": "122886", "title" : "Star Wars: Episode VII - The Force Awakens", "year":2015 , "genre":["Action", "Adventure", "Fantasy", "Sci-Fi", "IMAX"] }
{ "create" : { "_index" : "movies", "_id" : "109487" } }
{ "id": "109487", "title" : "Interstellar", "year":2014 , "genre":["Sci-Fi", "IMAX"] }
{ "create" : { "_index" : "movies", "_id" : "58559" } }
{ "id": "58559", "title" : "Dark Knight, The", "year":2008 , "genre":["Action", "Crime", "Drama", "IMAX"] }
{ "create" : { "_index" : "movies", "_id" : "1924" } }
{ "id": "1924", "title" : "Plan 9 from Outer Space", "year":1959 , "genre":["Horror", "Sci-Fi"] }

# PUT으로 동일한 id에 덮어쓰기 가능, 이 때 매번 version이 증가
PUT /movies/_doc/109487
{
  "genre" : ["IMAX","Sci-Fi"],
  "title" : "Interstellar foo",
  "year" : 2014
}

# 특정 필드만 변경할 때는 POST로 _update수행하면됨. 이 때는 변경사항 있을 때만 version 증가
POST /movies/_update/109487
{
  "doc":{
    "title": "Interstellar poo"
  }
}

# document 삭제
DELETE /movies/_doc/109487

# title이 star를 포함하는 document를 반환한다.
# URL에 검색할 정보를 담으면 보안에 취약하고 인코딩하는 cost가 발생한다.
GET /movies/_search?q=title:star

# query DSL을 이용하여 title에 star가 포함되는 document를 반환한다.
# match는 해당 text를 포함하는 document를 반환한다.
GET /movies/_search
{
  "query": {
    "match": {
      "title" : "star"
    }
  }
}

# term은 정확하게 일치하는 document를 반환한다.
GET /movies/_search
{
  "query": {
    "match": {
      "term" : "star"
    }
  }
}

# analyzer는 0개이상의 캐릭터필터, 1개의 토크나이저, 0개 이상의 토큰 필터로 구성된다.
# 캐릭터 필터는 텍스트를 캐릭터의 스트림으로 받아서 특정한 문자를 추가, 변경, 삭제한다.

# 문자열을 standard analyzer로 분석
POST _analyze
{
  "analyzer": "standard",
  "text": "Hello, HELLO, WORLD!"
}

# 문자열을 html_stript 캐릭터필터로 필터링
POST _analyze
{
  "char_filter": ["html_strip"],
  "text": "<p>HEllo hello</p>!"
}

# 문자열을 keyword 토크나이저로 토큰화
POST _analyze
{
  "tokenizer": "keyword",
  "text": "Hello, HELLO, World!"
}

# 문자열을 ngram 토크나이저로 토큰화
POST _analyze
{
  "tokenizer": {
    "type": "ngram",
    "min_gram": 3,
    "max_gram": 4
    },
  "text": "Hello, HELLO, World!"
}

# 문자열을 ngram으로 토큰화할 때 토큰에 담을 내용을 지정
# Letter, Digit, whitespace, punctuation, symbol, custom
POST _analyze
{
  "tokenizer": {
    "type": "ngram",
    "min_gram": 3,
    "max_gram": 4,
    "token_chars": ["letter"]
    },
  "text": "Hello, HELLO, World!"
}

# edge_ngram은 token_chars에 지정되지 않은 문자를 기준으로 삼아 단어 단위로 쪼갠 후 ngram한다.
POST _analyze
{
  "tokenizer": {
    "type": "edge_ngram",
    "min_gram": 3,
    "max_gram": 4,
    "token_chars": ["letter"]
    },
  "text": "Hello, HELLO, World!"
}

# 토큰 필터는 토큰 스트림을 받아서 토큰을 추가, 변경, 삭제한다.
# lowercase/uppercase, stop, synonym, patter_replace, stemmer, trim, truncate
POST _analyze
{
  "filter": ["lowercase"],
  "text": "Hello, World!"
}

# 내장 analyzer에서는 standard, simple, whitespace, stop, keyword, pattern, language, fingerprint가 있다.

# custom tokenizer와 token filter를 만들고 custom analyzer를 생성할 수 있다.
# 인덱스 초기 맵핑시 analzer를 필드별로 지정해주어야한다. 수정시 인덱스를 다시 생성해야한다.
PUT /movies
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "my_tokenizer": {
          "type": "standard"
        }
      },
      "filter": {
        "my_filter": {
          "type": "lowercase"
        }
      },
      "analyzer": {
        "my_custom_analyzer": {
          "type": "custom",
          "tokenizer": "my_tokenizer",
          "filter": ["my_filter"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "my_custom_analyzer",
        "search_analyzer": "standard_analyzer"
      },
      "id": {
        "type": "keyword"
      },
      "year": {
        "type": "integer"
      },
      "genre": {
        "type": "keyword"
      }
    }
  }
}

# 쿼리 검색
GET /movies/_search
{
  "query": {
    "match": {
      "title" : "Star"
    }
  }
}

DELETE /movies

# ngram으로 생성하자.
PUT /movies
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "ngram_tokenizer": {
          "type": "ngram",
          "min_gram": 3,
          "max_gram": 4
        }
      },
      "filter": {
        "my_filter": {
          "type": "lowercase"
        }
      },
      "analyzer": {
        "my_custom_analyzer": {
          "type": "custom",
          "tokenizer": "ngram_tokenizer",
          "filter": ["my_filter"]
        },
        "standard_analyzer": {ㅁㅁ 
          "type": "standard"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "my_custom_analyzer",
        "search_analyzer": "standard_analyzer"
      },
      "id": {
        "type": "keyword"
      },
      "year": {
        "type": "integer"
      },
      "genre": {
        "type": "keyword"
      }
    }
  }
}

# ngram으로 검색가능하다.
GET /movies/_search
{
  "query": {
    "match": {
      "title" : "Sta"
    }
  }
}
# mysql 도커 컨테이너 실행
$ docker run -it -d --name mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 mysql:latest

# mysql 컨테이너 내부 접속
$ docker exec -it mysql /bin/bash

# mysql 클라이언트 접속
$ mysql -u root -p

> CREATE DATABASE movies;
> USE movies;
> GRANT ALL PRIVILEGES ON *.* to root@'%';
> FLUSH PRIVILEGES;

>CREATE TABLE movies (
    id INT PRIMARY KEY,
    title VARCHAR(255),
    genres VARCHAR(255)
);

$ pip3 install pandas mysql-connector-python

$ vim insert_movie.py

import pandas as pd
import mysql.connector
from mysql.connector import Error
# CSV 파일 경로
csv_file_path = 'path/to/movies.csv'

# MySQL 데이터베이스 연결 설정
db_config = {
    'user': 'root',
    'password': 'your_password',
    'host': 'localhost',
    'database': 'movies_db'
}

def load_csv_to_mysql(csv_file_path, db_config):
    try:
        # MySQL 데이터베이스에 연결
        connection = mysql.connector.connect(**db_config)
        if connection.is_connected():
            cursor = connection.cursor()

            # CSV 파일 읽기
            df = pd.read_csv(csv_file_path)

            # 데이터베이스에 삽입
            for _, row in df.iterrows():
                sql = "INSERT INTO movies (id, title, genres) VALUES (%s, %s, %s)"
                cursor.execute(sql, tuple(row))
                connection.commit()

            print("CSV 파일의 데이터가 MySQL 데이터베이스에 성공적으로 삽입되었습니다.")

    except Error as e:
        print(f"Error: {e}")
    finally:
        if connection.is_connected():
            cursor.close()
            connection.close()
            print("MySQL 연결이 닫혔습니다.")

# 스크립트 실행
load_csv_to_mysql(csv_file_path, db_config)

$ python3 ./insert_movie.py
import mysql.connector
from mysql.connector import Error
from elasticsearch import Elasticsearch, helpers
# MySQL 데이터베이스 연결 설정
db_config = {
    'user': 'root',
    'password': 'root',
    'host': 'localhost',
    'database': 'movies'
}

# Elasticsearch 연결 설정
es = Elasticsearch([{'host': 'localhost', 'port': 9200, 'scheme': 'http'}])

def fetch_movies_from_db():
    try:
        connection = mysql.connector.connect(**db_config)
        if connection.is_connected():
            cursor = connection.cursor(dictionary=True)
            cursor.execute("SELECT * FROM movies")
            result = cursor.fetchall()
            return result
    except Error as e:
        print(f"Error: {e}")
    finally:
        if connection.is_connected():
            cursor.close()
            connection.close()

def index_movies_to_es(movies):
    actions = [
        {
            "_index": "movies",
            "_id": movie["id"],
            "_source": {
                "title": movie["title"],
                "genres": movie["genres"]
            }
        }
        for movie in movies
    ]
    helpers.bulk(es, actions)

def main():
    movies = fetch_movies_from_db()
    if movies:
        index_movies_to_es(movies)
        print("Movies have been indexed successfully.")
    else:
        print("No movies found in the database.")

if __name__ == "__main__":
    main()
@Data
@Document(indexName = "movies")
public class Movie {
    private int id;
    private String title;
    private String genres;
}
spring.application.name=ass1
spring.datasource.url=jdbc:mysql://localhost:3306/movies
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update

q

    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    runtimeOnly 'com.mysql:mysql-connector-j'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
public interface MovieRepository {
    public List<Movie> getByTitle(String title);
}
@RequiredArgsConstructor
@Repository
public class MovieRDBRepository implements MovieRepository{

   private final JdbcTemplate jdbcTemplate;

    @Override
    public List<Movie> getByTitle(String title) {
        String sql = "SELECT * FROM movies WHERE title LIKE ?";
        String searchTitle = "%" + title + "%";
        return jdbcTemplate.query(sql, new Object[]{searchTitle}, new MovieRowMapper());
    }
}

class MovieRowMapper implements RowMapper<Movie> {
    @Override
    public Movie mapRow(ResultSet rs, int rowNum) throws SQLException {
        Movie movie = new Movie();
        movie.setId(rs.getInt("id"));
        movie.setTitle(rs.getString("title"));
        movie.setGenres(rs.getString("genres"));
        return movie;
    }
}
@Repository
public interface MovieESRepository extends ElasticsearchRepository<Movie, String> {
    List<Movie> findByTitle(String title);
}
    @GetMapping("/movies/rdb/{title}")
    public List<Movie> getMoviesByTitleFromRDB(@PathVariable("title") String title){
        return movieRDBRepository.getByTitle(title);
    }

    @GetMapping("/movies/es/{title}")
    public List<Movie> getMoviesByTitleFromES(@PathVariable("title") String title){
        return movieESRepository.findByTitle(title);
    }