반응형

외부에 물리db 를 생성하기 어려울떄가 있다.

나 같은 경우 사이드 프로젝트를 하고 싶은데 물리 db 를 생성하기 부담스러울때 간단히 내부 db 를 사용하고 싶어 인메모리 h2 를 사용한다.

 

h2 는 보통 프로덕션보다는 보통 test 용으로 사용한다.

왜냐면 휘발성이기 때문이다.

 

 

  • 디스크가 아닌 주 메모리에 모든 데이터를 보유하고 있는 데이터베이스입니다. 
  • 디스크 검색보다 자료 접근이 훨씬 빠른 것이 큰 장점입니다. 단점은 매체가 휘발성이기 때문에 DB 서버가 꺼지면 모든 데이터가 유실된다는 단점이 있습니다.
  • 스프링 부트에서 H2, HSQL 같은 인메모리, 디스크 기반 DB를 지원합니다.

 

라이브러리 추가

현재 spring boot 버젼 2.4.5

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

 

 

  • H2 데이터베이스 의존성을 추가하고 난 후, 설정 파일에 아무 설정이 되어 있지 않으면 스프링 부트는 자동적으로 H2 데이터베이스를 기본 데이터베이스로 선택한다
  • spring-boot-starter-jdbc 의존성을 추가하면 DataSource, JdbcTemplate을 별다른 설정없이 @Autowired 같은 빈 주입 어노테이션만 가지고도 쓸 수 있다.
@Component
public class H2Runner implements ApplicationRunner {

    @Autowired
    DataSource dataSource;

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        try(Connection connection = dataSource.getConnection()){
            System.out.println(connection);
            String URL = connection.getMetaData().getURL();
            System.out.println(URL);
            String User = connection.getMetaData().getUserName();
            System.out.println(User);

            Statement statement = connection.createStatement();
            String sql = "CREATE TABLE USER(ID INTEGER NOT NULL, NAME VARCHAR(255), PRIMARY KEY (ID) )";
            statement.executeUpdate(sql);
        }

        jdbcTemplate.execute("INSERT INTO USER VALUES(1, 'testuser')");
    }
}

위의 코드 까지만 작성하면 h2 가 정상적으로 동작하는 지 확인 가능하다.

아래 기본 ddl sql 로드 방법을 사용하면 위의 자바 class 는 필요가 없다.

spring:
  h2:
    console:
      enabled: true
      path: /h2-console

 

다음과 같이 yml 에 설정하면 웹에서 sql 접근 가능

localhost:8080/h2-console   로 접근 가능

별도의 yml 로 디비 접속 설정을 안하면 

connection.getMetaData().getURL()  -> JDBC URL

connection.getMetaData().getUserName() -> User Name, 디폴트값 : sa

로 로그 찍은 값으로 접속 가능하다.

 

디폴트 값으로 하지 않고 yml 에서 설정하려면

datasource:
    url: jdbc:h2:mem:testdb
    username: sa

다음과 같이 작성하면 된다.

 

 

기본 ddl sql script 로드

기본적으로 jpa 와 h2 라이브러리 import 하는 거 만으로 schema.sql 과 data.sql 를 초기에 로딩 할 수 있다.

스키마 파일은 

schema.sql  로 명명 하고

resources > 하위에 schema.sql 을 두면 초기 구동 되면서 sql 문을 실행한다.

CREATE TABLE Employee
(
    id integer NOT NULL,
    firstName varchar(255) not null,
    lastName varchar(255) not null,
    email varchar(255),
    phoneNumber varchar(255),
    hireDate timestamp,
    salary integer,
    commissionPct integer,
    primary key(id)
);

data 삽입도 같이 data.sql 파일안에 insert 문을 넣어주면 된다.

 

별도로 해줘야 할 작업은 application.yml 파일에

spring:
  jpa:
    hibernate:
      ddl-auto: none

같이 ddl-auto 를 none 으로 설정해야 resources 하위의 sql 문을 실행한다.

 

지금 하는 sql 초기 구동시 로딩을 해주면 위에서 작성한 H2Runner.class 를 따로 생성하지 않아도 된다.

 

결과

 

최종 yml 파일

spring:
  h2:
    console:
      enabled: true
      path: /h2-console
  jpa:
    hibernate:
      ddl-auto: none
  datasource:
    url: jdbc:h2:mem:testdb
    username: sa

 

반응형
반응형

@Id는 해당 프로퍼티가 테이블의 주키(primary key) 역할을 한다는 것을 나타낸다.


@GeneratedValue는 주키의 값을 위한 자동 생성 전략을 명시하는데 사용한다.


Primary 키 생성 전략으로 JPA가 지원하는 것은 아래의 네 가지이다.


1. AUTO : (persistence provider가) 특정 DB에 맞게 자동 선택

  데이터베이스 벤더에 의존하지 않고, 데이터베이스는 기본키를 할당하는 벙법
- 데이터베이스에 따라서 IDENTITY, SEQUENCE, TABLE 방법 중 하나를 자동으로 선택해주는 방법이다.
- 예를들어, Oracle일 경우 SEQUENCE를 자동으로 선택해서 사용합니다. 따라서, 데이터베이스를 변경해도 코드를 수정할 필요가 없다.


2. IDENTITY : DB의 identity 컬럼을 이용

  기본 키 생성을 데이터베이스에 위임하는 방법 (데이터베이스에 의존적)
  - 주로 MySQL, PostgresSQL, SQL Server, DB2에서 사용합니다.


3. SEQUENCE : DB의 시퀀스 컬럼을 이용

  데이터베이스 시퀀스를 사용해서 기본 키를 할당하는 방법 (데이터베이스에 의존적)
  - 주로 시퀀스를 지원하는 Oracle, PostgresSQL, DB2, H2에서 사용한다. 
  - @SequenceGenerator를 사용하여 시퀀스 생성기를 등록하고, 실제 데이터베이스의 생성될 시퀀스이름을 지정해줘야 한다.


4. TABLE : 유일성이 보장된 데이터베이스 테이블을 이용

  키 생성 테이블을 사용하는 방법
- 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만드는 방법이다.
- 테이블을 사용하므로, 데이터베이스 벤더에 상관없이 모든 데이터베이스에 적용이 가능하다.

postgres를 이용하여 테스트해보면 AUTO와 SEQUENCE는 실제 INSERT 쿼리가 일어나기 전에 다음 쿼리를 통해서 주키를 가져오는 것을 확인할 수 있다.

select nextval ('hibernate_sequence')


기본키 자동생성 사용방법

  • @Id 어노테이션을 사용한다.
  • @GeneratedValue를 사용한다.
  • @GeneratedValue에 원하는 키 생성 전략을 선택한다. (IDENTITY, SEQUENCE, TABLE, AUTO)

Sample 코드

@Entity
public class Team {
  @Id
  @Column(name = "team_id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
}


@Entity
@SequeceGenerator(name = "TEAM_SEQ_GENERATOR", sequenceName = "TEAM_SEQ", initialValue = 1, allocationSize = 1)
// name=식별자 생성기 이름, sequenceName=DB에 등록될 시퀀스이름, initialValue=최초시작하는 수, allocationSize=증가하는수)
public class Team {

  @Id
  @Column(name = "team_id")
  @GeneratedValue(strategy = GenerationType. SEQUENCE, generator = "TEAM_SEQ_GENERATOR")
  private Long id;

}

@Entity
@TableGenerator(name="TEAM_SEQ_GENERATOR", table="TEAM_SEQUENCES", pkColumnValue="TEAM_SEQ", allocationSize=1)
// name=식별자 생성기 이름, table=키생성 테이블 이름, pkColumnValue=DB에 등록될 시퀀스이름)
public class Team {
  @Id
  @Column(name = "team_id")
  @GeneratedValue(strategy = GenerationType. TABLE, generator = "TEAM_SEQ_GENERATOR")
  private Long id;

}

@Entity
public class Team {
  @Id
  @Column(name = "team_id")
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

}


참고문헌

ithub.tistory.com/24

 

반응형
반응형

jpa 를 사용하려면 @Entity 클래스를 먼저 생성해야 한다.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="coin.market_code")
public class MarketCode {

    @Id
    @Column(name = "market_code")
    private String marketCode;
    @Column(name = "korean_name")
    private String koreanName;
    @Column(name = "english_name")
    private String englishName;

    @Builder
    public MarketCode(String marketCode, String koreanName, String englishName) {
        this.marketCode = marketCode;
        this.koreanName = koreanName;
        this.englishName = englishName;
    }
}

 

먼저 데이터베이스에 저장하기 위해 유저가 정의한 클래스가 필요한데 그런 클래스를

Entity라고 한다

@Id

primary key를 가지는 변수를 선언하는 것을 뜻한다. @GeneratedValue 어노테이션은 해당 Id 값을

어떻게 자동으로 생성할지 전략을 선택할 수 있다. 

보통 일반적으로

@GeneratedValue(strategy = GenerationType.AUTO)

를 사용한다. DB에 맞게 자동으로 생성해주는 역할이다.

참고 -> juntcom.tistory.com/156 - GeneratedValue생성방법

@Table

별도의 이름을 가진 데이터베이스 테이블과 매핑한다. 기본적으로 @Entity로 선언된 클래스의 이름은 실제

데이터베이스의 테이블 명과 일치하는 것을 매핑한다. 따라서 @Entity의 클래스명과 데이터베이스의 테이블명이

다를 경우에 @Table(name=" ")과 같은 형식을 사용해서 매핑이 가능하다.

@Column

@Column 선언이 꼭 필요한 것은 아니다. 하지만 @Column에서 지정한 변수명과 데이터베이스의 컬럼명을

서로 다르게 주고 싶다면 @Column(name=" ") 같은 형식으로 작성하면 된다.

그렇지 않은 경우에는 기본적으로 멤버 변수명과 일치하는 데이터베이스 컬럼을 매핑한다.

컬럼에 언더바가 있을 경우에는 꼭 column 어노테이션을 사용해야 한다.

 

 

JpaRepository

public interface MarketCodeRepository extends JpaRepository<MarketCode, String> {

}

Spring Data JPA에서 제공하는 JpaRepository 인터페이스를 상속하기만 해도 되며,

인터페이스에 따로 @Repository등의 어노테이션을 추가할 필요가 없다



Spring Data JPA에서 제공하는 JpaRepository 인터페이스를 상속하기만 해도 되며,

인터페이스에 따로 @Repository등의 어노테이션을 추가할 필요가 없다

 

 

그렇게 JpaRepository를  단순하게 상속하는 것만으로 위의 인터페이스는 Entity 하나에 대해서

아래와 같은 기능을 제공하게 된다.

 method

 기능

 save()

 레코드 저장 (insert, update)

 findOne()

 primary key로 레코드 한건 찾기
 findAll()

 전체 레코드 불러오기. 정렬(sort), 페이징(pageable) 가능

 count()  레코드 갯수
 delete()  레코드 삭제

 

public interface MarketCodeRepository extends JpaRepository<MarketCode, String> {
	List<MarketCode> findBykoreanName(String koreanName);
}

주의사항 변수명을 korean_name 처럼 언더바로 사용할시에 findBy가 적용이 안된다.

변수명을 카멜케이스로 맞추자.

 

Query 메소드에 포함할 수 있는 키워드는 다음과 같다.

메서드 이름 키워드

 샘플

 설명

 And

 findByEmailAndUserId(String email, String userId)

 여러필드를 and 로 검색

 Or

 findByEmailOrUserId(String email, String userId)

 여러필드를 or 로 검색

 Between

 findByCreatedAtBetween(Date fromDate, Date toDate)

 필드의 두 값 사이에 있는 항목 검색

 LessThan

 findByAgeGraterThanEqual(int age)

 작은 항목 검색

 GreaterThanEqual

 findByAgeGraterThanEqual(int age)

 크거나 같은 항목 검색

 Like

 findByNameLike(String name)

 like 검색

 IsNull

 findByJobIsNull()

 null 인 항목 검색

 In

 findByJob(String … jobs)

 여러 값중에 하나인 항목 검색

 OrderBy

 findByEmailOrderByNameAsc(String email)

 검색 결과를 정렬하여 전달

 


참고문헌

docs.spring.io/spring-data/jpa/docs/1.10.1.RELEASE/reference/html/#jpa.sample-app.finders.strategies  - jpa문서
반응형
반응형

세팅 옵션

- autoCommit:

auto-commit설정 (default: true)

- connectionTimeout:

pool에서 커넥션을 얻어오기전까지 기다리는 최대 시간, 허용가능한 wait time을 초과하면 SQLException을 던짐. 설정가능한 가장 작은 시간은 250ms (default: 30000 (30s))

- idleTimeout:

pool에 일을 안하는 커넥션을 유지하는 시간. 이 옵션은 minimumIdle이 maximumPoolSize보다 작게 설정되어 있을 때만 설정. pool에서 유지하는 최소 커넥션 수는 - - - - minimumIdle (A connection will never be retired as idle before this timeout.). 최솟값은 10000ms (default: 600000 (10minutes))

 

- maxLifetime:

커넥션 풀에서 살아있을 수 있는 커넥션의 최대 수명시간. 사용중인 커넥션은 maxLifetime에 상관없이 제거되지않음. 사용중이지 않을 때만 제거됨. 풀 전체가아닌 커넥션 별로 적용이되는데 그 이유는 풀에서 대량으로 커넥션들이 제거되는 것을 방지하기 위함임. 강력하게 설정해야하는 설정 값으로 데이터베이스나 인프라의 적용된 connection time limit보다 작아야함. 0으로 설정하면 infinite lifetime이 적용됨(idleTimeout설정 값에 따라 적용 idleTimeout값이 설정되어 있을 경우 0으로 설정해도 무한 lifetime 적용 안됨). (default: 1800000 (30minutes))

 

- connectionTestQuery:

JDBC4 드라이버를 지원한다면 이 옵션은 설정하지 않는 것을 추천. 이 옵션은 JDBC4를 지원안하는 드라이버를 위한 옵션임(Connection.isValid() API.) 커넥션 pool에서 커넥션을 획득하기전에 살아있는 커넥션인지 확인하기 위해 valid 쿼리를 던지는데 사용되는 쿼리 (보통 SELECT 1 로 설정) JDBC4드라이버를 지원하지않는 환경에서 이 값을 설정하지 않는다면 error레벨 로그를 뱉어냄.(default: none)

- minimumIdle:

아무런 일을 하지않아도 적어도 이 옵션에 설정 값 size로 커넥션들을 유지해주는 설정. 최적의 성능과 응답성을 요구한다면 이 값은 설정하지 않는게 좋음. default값을 보면 이해할 수있음. (default: same as maximumPoolSize)

- maximumPoolSize:

pool에 유지시킬 수 있는 최대 커넥션 수. pool의 커넥션 수가 옵션 값에 도달하게 되면 idle인 상태는 존재하지 않음.(default: 10)

- poolName:

이 옵션은 사용자가 pool의 이름을 지정함. 로깅이나 JMX management console에 표시되는 이름.(default: auto-generated)

 

- initializationFailTimeout:

이 옵션은 pool에서 커넥션을 초기화할 때 성공적으로 수행할 수 없을 경우 빠르게 실패하도록 해준다. 상세 내용은 한국말보다 원문이 더 직관적이라 생각되어 다음 글을 인용함.

 

- readOnly:

pool에서 커넥션을 획득할 때 read-only 모드로 가져옴. 몇몇의 database는 read-only모드를 지원하지 않음. 커넥션이 read-only로 설정되어있으면 몇몇의 쿼리들이 최적화 됨.(default: false)

 

- driverClassName:

HikariCP는 jdbcUrl을 참조하여 자동으로 driver를 설정하려고 시도함. 하지만 몇몇의 오래된 driver들은 driverClassName을 명시화 해야함. 어떤 에러 메시지가 명백하게 표시 되지않는다면 생략해도됨.

 

- validationTimeout:

valid 쿼리를 통해 커넥션이 유효한지 검사할 때 사용되는 timeout. 250ms가 설정될 수 있는 최솟값(default: 5000ms)

 

- leakDetectionThreshold:

커넥션이 누수 로그메시지가 나오기 전에 커넥션을 검사하여 pool에서 커넥션을 내보낼 수 있는 시간. 0으로 설정하면 leak detection을 이용하지않음. 최솟값 2000ms (default: 0)

 

MySQL Performance Tips

- prepStmtCacheSize:

커넥션당 캐시할 prepared statementsts 숫자. 250 ~ 500 개 추천

 

- prepStmtCacheSqlLimit:

드라이버가 캐시할 sql문의 최대길이. 기본값은 256. Hibernate 와 같은 ORM 프레임워크의 기본값은 낮다. 권장 설정은 2048 이다.

 

- cachePrepStmts:

캐시가 기본적으로 비활성화 되어있는 경우 위의 매개 변수 는 효과가 없어진다. 이 매개변수를 true 로 해야 설정이 된다.

 

- useServerPrepStmts: 최신버젼의 MySql 은 서버 측 명령문을 지원하므로 상당한 성능 향상을 제공할 수 잇다. 이 속성을 true 로 해야 한다.

 

참고문헌

> https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby

반응형
반응형

ElasticsearchRepository search 메소드 내부에 NativeSearchQuery 를 넘겨 사용 가능하다.

또는 ElasticsearchTemplate 및 elasticsearchOperations 을 사용할떄도 NativeSearchQuery 를 사용할 수 있다.

 

쿼리 클래스의 종류로는 

CriteriaQuery, StringQuery, NativeSearchQuery

 

NativeSearchQuery 란 

복잡한 쿼리를 사용하려 할때 Criteria 로는 표현이 불가할때 사용한다.

CriteriaQuery 와 StringQuery 의 단점을 보완한 클래스이다.

 

ElasticsearchRepository 를 상속받아 사용하는 방식

@Repository
public interface PersonRepository extends ElasticsearchRepository<Person, String> {

}

또는 

ElasticsearchOperations elasticsearchOperations 을 사용하여 사용가능하다.

@Resource
ElasticsearchOperations elasticsearchOperations;

@Resource
PersonRepository personRepository;

 

두 클래스 중 하나를 사용하면 된다. elasticsearchOperations 은 elasticsearchTemplate 을 구현한 것이라 같게 봤다.

쿼리메소드 종류

matchQuery

//    GET /person/_search
//    {
//        "query": {
//        	"age": 30
//    	   }
//    }
// age 가 30 인 데이터
PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("age", 30);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();

String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = sampleRepository.search(nativeSearchQuery).getContent();
// 쿼리스트링으로도 가능

termsQuery

//{
//  "query": {
//    "terms": {
//      "firstname": ["vera", "kari"]
//    }
//  }
//}

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("firstname", "vera", "kari", "blake");
//term 
//TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("firstname", "Vera", "Kari", "Blake");
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(termsQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

multiMatchQuery

// id 와 age 가 44 인 것 찾기

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("44", "id", "age");
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(multiMatchQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

BoolQueryBuilder

 PageRequest pageRequest = PageRequest.of(0, 50);
 FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
 .must(QueryBuilders.matchQuery("firstname", "Effie"))
 .mustNot(QueryBuilders.matchQuery("gender", "M"))
 .should(QueryBuilders.matchQuery("balance", "3607"));

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

IdsQueryBuilder

//    GET /person/_search
//    {
//        "query": {
//        "ids": {
//            "values": [
//            "25",
//                    "44",
//                    "126"
//      ]
//        }
//    }
//    }
PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
IdsQueryBuilder idsQueryBuilder = QueryBuilders.idsQuery().addIds("25", "44", "126");
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(idsQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

DisMaxQueryBuilder

//    GET /person/_search
//    {
//        "query": {
//        "dis_max": {
//            "tie_breaker": 0.7,
//                    "boost": 1.2,
//                    "queries": [
//            {
//                "term": {
//                "firstname": {
//                    "value": "Effie"
//                }
//            }
//            },
//            {
//                "match": {
//                "gender": "m"
//            }
//            }
//      ]
//        }
//    }
//    }

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

DisMaxQueryBuilder disMaxQueryBuilder = QueryBuilders.disMaxQuery()
.add(QueryBuilders.termQuery("firstname", "Effie"))
.add(QueryBuilders.matchQuery("gender", "m"))
.boost(1.2f)
.tieBreaker(0.7f);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(disMaxQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();
    

FuzzyQueryBuilder

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("firstname", "effie");

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(fuzzyQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

prefixQuery

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

PrefixQueryBuilder prefixQueryBuilder = QueryBuilders.prefixQuery("firstname", "b");
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(prefixQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

rangeQuery

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age").from(20).to(30).includeLower(true).includeUpper(false);
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(rangeQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();
PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
//address: "398 Dearborn Court",  有3个term  查询court 结束的位置为 3
SpanFirstQueryBuilder spanFirstQueryBuilder = QueryBuilders.spanFirstQuery(QueryBuilders.spanTermQuery("address", "court"), 3);
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(spanFirstQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

boolQuery

PageRequest pageRequest = PageRequest.of(0, 10);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("gender", "f"))
.should(QueryBuilders.termQuery("address", "court"))
.should(QueryBuilders.termQuery("state","md"))
.filter(QueryBuilders.rangeQuery("age").gte(30))
.filter(QueryBuilders.rangeQuery("balance").gte(2726));

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();
/**
     *
     *
    GET /person/_search
    {
        "query": {
            " function_score": {
                "query": {
                    "match": {
                        "gender": "F"
                    }
                },
                "field_value_factor": {
                    "field": "balance",
                            "modifier": "log1p",
                            "factor": 0.5
                }
          , "boost_mode": "sum"
            }
        }
    }*/
    
PageRequest pageRequest = PageRequest.of(0, 10);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

//bool query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("gender", "F"));

//feild_value_factor
FieldValueFactorFunctionBuilder functionBuilder = ScoreFunctionBuilders.fieldValueFactorFunction("balance")
.modifier(FieldValueFactorFunction.Modifier.LOG1P)
.factor(0.5f);

FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, functionBuilder).boostMode(CombineFunction.SUM);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(functionScoreQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
Page<Person> page = personRepository.search(nativeSearchQuery);
logger.info("page: "+page.toString());
List<Person> content = page.getContent();

functionScoreQuery

/**

     GET /person/_search
     {
         "query": {
             "function_score": {
                 "query": {
                     "match": {
                         "gender": "F"
                    }
             },
             "gauss": {
                 "balance": {
                     "origin": "43951",
                     "scale": "100",
                     "offset": "10"
                 }
             },
             "boost_mode": "sum"
             }
         }
     }
     */
PageRequest pageRequest = PageRequest.of(0, 10);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

//bool query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("gender", "F"));
GaussDecayFunctionBuilder gaussDecayFunctionBuilder = ScoreFunctionBuilders.gaussDecayFunction("balance", "43951", "100", "10");

//builder
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, gaussDecayFunctionBuilder).boostMode(CombineFunction.SUM);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(functionScoreQueryBuilder)
.withPageable(pageRequest)
//.withSort(idSortBuilder)
.build();

String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
Page<Person> page = personRepository.search(nativeSearchQuery);
logger.info("page: "+page.toString());
List<Person> content = page.getContent();
/**   function_score weight query
    GET /person/_search
    {
        "query": {
            "function_score": {
                    "query": {
                        "bool": {
                            "must": [
                            {
                                "match": {
                                "gender": "F"
                            }
                            },{
                                "range": {
                                    "age": {
                                        "gte": 25,
                                                "lte": 30
                                    }
                                }
                            }
                  ]
                        }
                    },
                    "functions": [
                    {
                        "weight": 2
                    }
                ]
            }
        }
    }
     **/
PageRequest pageRequest = PageRequest.of(0, 10);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

//bool query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("gender", "F"))
.must(QueryBuilders.rangeQuery("age").gte(25).lte(30));

//weight buil
WeightBuilder weightBuilder = ScoreFunctionBuilders.weightFactorFunction(2.0f);

//builder
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, weightBuilder).boostMode(CombineFunction.SUM);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(functionScoreQueryBuilder)
.withPageable(pageRequest)
//.withSort(idSortBuilder)
.build();

String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
Page<Person> page = personRepository.search(nativeSearchQuery);
logger.info("page: "+page.toString());
List<Person> content = page.getContent();
반응형
반응형

엘라스틱 을 조회할떄 네이밍에 언더바(언더스코어)가 들어간 경우 일반적인 방법으로 ElasticsearchRepository 라이브러리로 조회가 가능하지 않다.

EX) name_field : '데이터'

 

 

이거 오류 해결 방법 좀 알고 싶었으나 지지고 볶아도 오류해결 방법이 없다. 

애초에 네이밍을 카멜로 했거나 그게 안되면 ElasticsearchOperations 을 사용해서 엘라스틱 조회를 해야한다.

 

간편하게 사용하기에는 아무래도 ElasticsearchRepository 가 jpa 랑 같아 사용하기 편한데, 복잡한 쿼리에는 불편한 점이 따르니 어쩔 수 없다.

굳이 언더바를 검색하려면 방법은 있다.

@Repository
public interface SampleRepository extends ElasticsearchRepository<SampleData, String> {

    @Query("{\"bool\": {\"must\": {\"match\": {\"name_field\": \"?0\"}}}}")
    List<SampleData> findByName(String name);
}

이런 식으로 네이티브 쿼리를 보내는 방법 밖에 알 수가 없다.

하지만 이렇게 할거면 굳이 ElasticsearchRepository 를 쓰는 이유가 있을까 싶다. 그냥 ElasticsearchOperations 로만 사용해도 된다.

또는 ElasticsearchRepository 에서 search 메소드에 queryBuilder 를 넣어서 ElasticsearchOperations처럼 사용해도 된다.

 

 

새로 업데이트 된 엘라스틱에서는 가능해졌는지는 모르겠지만,

내가 사용중인 버젼 spring boot 2.1.2, 

spring-data-elasticsearch:3.1.4 에서는 가능하지 않다.

 

아마 상위버젼에서도 불가 할 것이다.

jpa 에서는 @Colunm(name="name_field") 이런식으로 해결 가능한데, elastic 에는 이러한 어노테이션이 존재하지가 않아 되지 않는다.

 

구글링을 하면 @@JsonProperty("name_field") 이렇게 해결하라는 글이 하나 있는데, 동작하지 않는다. 찾아보면 다 동작이 안된다는 말이 더 많다.

 

 

처음 엘라스틱 데이터를 만들때 언더바, 언더스코어 등을 네이밍에 사용하지 말고 카멜케이스로 만들자..

 

 

참고

stackoverflow.com/questions/50528299/query-elasticsearch-using-spring-data-elasticsearch-with-fields-containing-under
반응형
반응형

RestTemplate이란

스프링에서 제공하는 http 통신에 유용하게 쓸 수 있는 템플릿

Spring 3부터 지원 되었고 REST API 호출이후 응답을 받을 때까지 기다리는 동기방식이다

 

AsyncRestTemplate

Spring 4에 추가된 비동기 RestTemplate이다

Spring 5.0에서는 deprecated 되었다

 

메소드

메서드

HTTP

설명

getForObject

GET

주어진 URL 주소로 HTTP GET 메서드로 객체로 결과를 반환받는다

getForEntity

GET

주어진 URL 주소로 HTTP GET 메서드로 결과는 ResponseEntity로 반환받는다

postForLocation

POST

POST 요청을 보내고 결과로 헤더에 저장된 URI를 결과로 반환받는다

postForObject

POST

POST 요청을 보내고 객체로 결과를 반환받는다

postForEntity

POST

POST 요청을 보내고 결과로 ResponseEntity로 반환받는다

delete

DELETE

주어진 URL 주소로 HTTP DELETE 메서드를 실행한다

headForHeaders

HEADER

헤더의 모든 정보를 얻을 수 있으면 HTTP HEAD 메서드를 사용한다

put

PUT

주어진 URL 주소로 HTTP PUT 메서드를 실행한다

patchForObject

PATCH

주어진 URL 주소로 HTTP PATCH 메서드를 실행한다

optionsForAllow

OPTIONS

주어진 URL 주소에서 지원하는 HTTP 메서드를 조회한다

exchange

any

HTTP 헤더를 새로 만들 수 있고 어떤 HTTP 메서드도 사용가능하다

execute

any

Request/Response 콜백을 수정할 수 있다

 

GET 메소드

getForObject()

Employee employee = restTemplate.getForObject(BASE_URL + "/{id}", Employee.class);

Employee 로의 매핑은 jackson-databind 가 기본적으로 담당하고 있다.

 

getForEntity()

응답을 ResponseEntity 객체로 받는다. getForObject()와 달리 HTTP 응답에 대한 추가 정보를 담고 있어서 GET 요청에 대한 응답 코드, 실제 데이터를 확인할 수 있다. 또한 ResponseEntity<T> 제네릭 타입에 따라서 응답을 String이나 Object 객체로 받을 수 있다.

 

ResponseEntity<String> responseEntity = restTemplate.getForEntity(BASE_URL + "/{id}", String.class, 25);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());

getForEntity()에 여러 값을 담은 params을 같이 넘겨주기

LinkedMultiValueMap 객체에 담아서 params로 넘겨줄 수 있다.

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("name", "Frank Oh");
params.add("country", "US");

ResponseEntity<Employee> responseEntity = restTemplate.getForEntity(BASE_URL + "/{name}/{country}", Employee.class, params);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());

 

get 요청에 header 값이 필요한 경우 

get 메소드에서는 header 를 추가 할 수 가 없다.

exchange 메소드를 사용해야 한다.

HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);

ResponseEntity<String> response = restTemplate.exchange(
  URL_PATH,
  HttpMethod.GET,
  request,
  String.class
);

 

get 요청에 header 값 과 쿼리스트링(query String, param)이 필요한 경우

post 처럼 HttpEntity 에 넣어서 요청할 수가 없다.

HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);
//adding the query params to the URL
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(URL_PATH)
	.queryParam("keywords", "11");
    .queryParam("name", "22");

ResponseEntity<String> response = restTemplate.exchange(
  uriBuilder.toUriString(),
  HttpMethod.GET,
  request,
  String.class
);

이렇게 UrlBuilder 를 사용해서 넣는 수 밖에 없다. post 방식과 달리 httpEntity 에 같이 넣거나 exchange 의 parameter 로 넘길 수가 없다.

사실 굳이 uriBuilder 를 써야 되나 싶기도 하다. 그냥 map 에 파라미터를 추가하고 map 을 parameter 로 변환해주는 메소드만 만들어서 사용하면 편할거 같다.


...
HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);

Map<String, String> params = new HashMap<String, String>();
params.put("query1", "test");
params.put("query2", "test2");

ResponseEntity<String> response = restTemplate.exchange(
  URL_PATH + "?" + this.mapToUrlParam(params),
  HttpMethod.GET,
  request,
  String.class
);
...

// 위에서 사용하려고 만든 util 메소드
private static String mapToUrlParam(Map<String, Object> params) {
  StringBuffer paramData = new StringBuffer();
  for (Map.Entry<String, Object> param : params.entrySet()) {
  	if (paramData.length() != 0) {
  		paramData.append('&');
  	}
    paramData.append(param.getKey());
    paramData.append('=');
    paramData.append(String.valueOf(param.getValue()));
  }
  return paramData.toString();
}

사실 굳이 uriBuilder 를 써야 되나 싶기도 하다.

단순하게 map 에 파라미터를 추가하고 map 을 parameter 문자열로 변환해주는 메소드만 만들어서 사용하면 편할거 같다.

 

 

postForObject() 메소드 header 값 없는 경우

Employee newEmployee = Employee.builder()
         .name("Frank")
         .address(Address.builder()
               .country("US")
               .build())
         .build();
 
   Employee employee = restTemplate.postForObject(BASE_URL + "/employee", newEmployee, Employee.class);

postForObject() 메소드 header 포함해서 보내기

Employee newEmployee = Employee.builder()
         .name("Frank")
         .address(Address.builder()
               .country("US")
               .build())
         .build();
 
   HttpHeaders headers = new HttpHeaders();
   headers.set("headerTest", "headerValue");
 
   HttpEntity<Employee> request = new HttpEntity<>(newEmployee, headers);
 
   Employee employee = restTemplate.postForObject(BASE_URL + "/employee", request, Employee.class);

postForEntity

getForEntity 와 동일해서 생략

 

post form data 사용

// 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

// 파라미터 세팅
MultiValueMap<String, String> map= new LinkedMultiValueMap<>();
map.add("id", "1");

// 요청 세팅 완료
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

// 실제 요청부
ResponseEntity<String> response = restTemplate.postForEntity(fooResourceUrl+"/form", request , String.class);

 

Timeout 설정하기

timeout 을 설정하려면 ClientHttpRequestFactory 와 같은 팩토리 메소드를 만들고 RestTemplate 의 생성자에 추가해야 한다.

RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());

private ClientHttpRequestFactory getClientHttpRequestFactory() {
    int timeout = 5000;
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
      = new HttpComponentsClientHttpRequestFactory();
    clientHttpRequestFactory.setConnectTimeout(timeout);
    return clientHttpRequestFactory;
}

 

참고사항으로 timeout 이 0 이면 무제한설정이다. (infinite timeout)

 

Execute()

 Execute()는 콜백을 통해 요청 준비와 응답 추출을 완벽하게 제어하여 요청을 수행하는 가장 일반적인 메서드를 RestTemplate에서 제공한다.

getForObject(), postForeObject() 등은 excute() 를 내부적으로 호출한다.

 

connection pool 적용

RestTemplate 은 기본적으로 connection pool 을 사용하지 않는다. 따라서 연결할 때 마다, 로컬 포트를 열고 tcp connection 을 맺는다. 이때 문제는 close() 이후에 사용된 소켓은 TIME_WAIT 상태가 되는데, 요청량이 많다면 이런 소켓들을 재사용하지 못하고 소켓이 오링나서 응답이 지연될 것이다.

 

 

참고문헌

www.baeldung.com/rest-template [샘플 문서]
stackoverflow.com/questions/31869193/using-spring-rest-template-either-creating-too-many-connections-or-slow/ [커넥션 많을 경우 pool 관리
반응형
반응형

스프링 부트 2.3 버젼부터 spring-data-elasticsearch 버젼도 4 버젼으로 올라가고, 여기서 부터 기존 설정 이 조금 달라졌다.

아래 내용은 버젼명시가 없다면 2.3 버젼부터 사용가능한 버젼이다.

 

소스코드 버젼

spring boot 2.4.3

spring-data-elasticsearch 4.1.3

 

pom.xml dependency 추가

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

 

 

config 설정소스

@Configuration
@EnableElasticsearchRepositories(basePackages = "com.baeldung.spring.data.es.repository")
@ComponentScan(basePackages = { "com.baeldung.spring.data.es.service" })
public class Config {

    @Bean
    public RestHighLevelClient client() {
        ClientConfiguration clientConfiguration 
            = ClientConfiguration.builder()
                .connectedTo("localhost:9200")
                .build();

        return RestClients.create(clientConfiguration).rest();
    }

    @Bean
    public ElasticsearchOperations elasticsearchTemplate() {
        return new ElasticsearchRestTemplate(client());
    }
}

spring-data-elasticsearch 4.0.0 이상부터는,
TransportClient 클래스가 Deprecated 되어 RestHighLevelClient 또는 ReactiveRestLevelClients 만 사용 가능하다.

 

예제 상에서는 대부분 

@EnableElasticsearchRepositories 어노테이션을 추가하지만 예제 소스를 직접 만들어본 결과 @EnableElasticsearchRepositories 를 따로 추가해 주지 않아도 됐다.

 

@ComponentScan 는 application 에 추가했다면 안해도 된다.

@EnableElasticsearchRepositories 도 추가 없이 동작확인했지만, 사용하려는 버젼에서 안된다면 추가하자.

 

Document 객체 설정하기

@Setter
@Document(indexName = "blog")
public class Blog {

    @Id
    private String id;
    private String title;
    private String content;

}

@Document 는 내가 넣을 index 테이블이다. @Entity 와 동일하다. 또한 몽고디비의 @Document 와 동일하다.

컬럼의 경우 @Field 를 사용한다.

@Field(type = FieldType.Date)
private Date log_date;

@Field(type = FieldType.Text)
private String log_text;

@Field(type = FieldType.Long)
private Long price;

필드별로 타입을 지정할 수 있다.

당연한 거지만 숫지데이터가 없을 때 null 을 넣어주고 싶은경우 Reference Type 을 넣어주면 된다. Long, Integer 등등

 

원래는 @Document 어노테이션에 매핑 Type 지정이 가능했지만, ES 7.0.0 부터는 Type 이 deprecated 되어 indexName만 설정가능하다.

어차피 6. 버젼에서도 하나의 index 에 하나의 type 만 가능했어서 상관없는 부분이다.

https://www.elastic.co/guide/en/elasticsearch/reference/7.x/removal-of-types.html [타입삭제 레퍼런스]

 

@ElasticsearchReposity 사용

JpaRepository 와 사용이 유사하다.

https://docs.spring.io/spring-data/elasticsearch/docs/4.0.0.M4/reference/html/#repositories [repository 문서]

@Repository
public interface BlogEsRepository extends ElasticsearchRepository<Blog, String> {
}

 

사용 예시

@Resource
BlogEsRepository blogEsRepository;

@Test
void test(){
    Blog blog = new Blog();
    blog.setId("1");
    blog.setContent("내용입니다.");
    blog.setTitle("제목입니다.");
    blogEsRepository.save(blog);
}

 

키바나 결과화면

 

엘라스틱 조회 결과

 

 

엘라스틱 구축 하는 방법은 

juntcom.tistory.com/121    [도커로 엘라스틱 구축]

를 참고하자.

 

 

참고문헌

https://docs.spring.io/spring-data/elasticsearch/docs/4.0.0.M4/reference/html/#preface [스프링 공식문서]
https://www.baeldung.com/spring-data-elasticsearch-tutorial [elastic 예제소스]

 

 

반응형

+ Recent posts

Buy Me A Coffee
방문해주셔서 감사합니다. 후원이 큰힘이 됩니다.