Tagbangers Blog

Hibernate Search + Elasticsearch を試してみる (Amazon Elasticsearch Service 版)

前回の記事では Hibernate Search を使って Docker コンテナ上の Elasticsearch と連携してみました。
今回は AWS のフルマネージドな Amazon Elasticsearch Service と連携させてみましょう。

記事中のサンプルコードはここにあります。

環境:
Mac OS X
Hibernate Search 5.8.0.CR1
Amazon Elasticsearch Service (Elasticsearch version 5.5)

1. Elasticsearch Service を作成する

AWS マネージメントコンソールの Elasticsearch Service メニューから新しいドメインを作成します。

2. pom.xml の作成

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>sample</groupId>
   <artifactId>hibernate-aws-hosted-elasticsearch-sample</artifactId>
   <version>1.0-SNAPSHOT</version>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
   </properties>

   <build>
      <plugins>
         <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.0.0</version>
            <executions>
               <execution>
                  <id>add-test-source</id>
                  <phase>process-resources</phase>
                  <goals>
                     <goal>add-test-source</goal>
                  </goals>
                  <configuration>
                     <sources>
                        <source>src/it/java</source>
                     </sources>
                  </configuration>
               </execution>
            </executions>
         </plugin>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.20</version>
            <executions>
               <execution>
                  <goals>
                     <goal>integration-test</goal>
                     <goal>verify</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>

   <dependencies>
      <dependency>
         <groupId>org.hibernate</groupId>
         <artifactId>hibernate-search-orm</artifactId>
         <version>5.8.0.CR1</version>
      </dependency>

      <dependency>
         <groupId>org.hibernate</groupId>
         <artifactId>hibernate-search-elasticsearch</artifactId>
         <version>5.8.0.CR1</version>
      </dependency>

      <!-- To use Amazon’s proprietary IAM authentication mechanism to access your Elasticsearch cluster -->
      <dependency>
         <groupId>org.hibernate</groupId>
         <artifactId>hibernate-search-elasticsearch-aws</artifactId>
         <version>5.8.0.CR1</version>
      </dependency>

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

      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.16.18</version>
         <scope>provided</scope>
      </dependency>

      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.12</version>
         <scope>test</scope>
      </dependency>
   </dependencies>
</project>

hibernate-search-elasticsearch-aws ... Hibernate Search 5.8.0.CR1 から追加されたもので、IAM の認証情報をプロパティベースで記述できるようになりました

3. エンティティの定義

Amazon Elasticsearch Service は最初から kuromoji が利用できるので、トークンナイザーには kuromoji_tokenizer を指定します。

src/main/java/sample/Tweet.java

@Entity
@Indexed
@AnalyzerDef(
      name = "japanese",
      tokenizer = @TokenizerDef(
            factory = ElasticsearchTokenizerFactory.class,
            params = {
                  @Parameter(name = "type", value = "kuromoji_tokenizer")
            }
      )
)
@Analyzer(definition = "japanese")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Tweet implements Serializable {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private long id;

   @Field
   private String content;

   @Override
   public String toString() {
      return content;
   }
}

4. integration-test を使って検証コードを書く

src/it/java/sample/TweetIT.java

public class TweetIT {

   private SessionFactory sessionFactory;

   private Session session;

   @Before
   public void before() {
      StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
            .applySetting(AvailableSettings.URL, "jdbc:h2:./db/sample")
            .applySetting(AvailableSettings.USER, "sa")
            .applySetting(AvailableSettings.PASS, "sa")
            .applySetting(AvailableSettings.DRIVER, "org.h2.Driver")
            .applySetting(AvailableSettings.DIALECT, H2Dialect.class)
            .applySetting(AvailableSettings.HBM2DDL_AUTO, "update")
            .applySetting(AvailableSettings.SHOW_SQL, true)
            .applySetting(AvailableSettings.FORMAT_SQL, true)
            .applySetting("hibernate.search.default.indexmanager", "elasticsearch")
            .applySetting("hibernate.search.default.elasticsearch.index_schema_management_strategy", "drop-and-create")

            .applySetting("hibernate.search.default.elasticsearch.host", "[YOUR HOST]")
            .applySetting("hibernate.search.default.elasticsearch.aws.signing.enabled", "true")
            .applySetting("hibernate.search.default.elasticsearch.aws.access_key", "[YOUR ACCESS KEY]")
            .applySetting("hibernate.search.default.elasticsearch.aws.secret_key", "[YOUR SECRET KEY]")
            .applySetting("hibernate.search.default.elasticsearch.aws.region", "[YOUR ACCESS REGION]")

            // In development, set this value to yellow if the number of nodes started is below the number of expected replicas.
            .applySetting("hibernate.search.default.elasticsearch.required_index_status", "yellow")

            // This is useful in unit tests to ensure that a write is visible by a query immediately without delay.
            .applySetting("hibernate.search.default.elasticsearch.refresh_after_write", "true")

            .build();

      Metadata metadata = new MetadataSources(standardRegistry)
            .addAnnotatedClass(Tweet.class)
            .getMetadataBuilder()
            .applyImplicitNamingStrategy(ImplicitNamingStrategyJpaCompliantImpl.INSTANCE)
            .build();

      SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder();
      sessionFactory = sessionFactoryBuilder.build();
      session = sessionFactory.openSession();

      session.getTransaction().begin();

      Tweet tweet1 = Tweet.builder().content("絶対は絶対にない").build();
      Tweet tweet2 = Tweet.builder().content("努力だ。勉強だ。それが天才だ").build();
      Tweet tweet3 = Tweet.builder().content("ゆっくりと急げ").build();

      session.save(tweet1);
      session.save(tweet2);
      session.save(tweet3);

      session.getTransaction().commit();
   }

   @After
   public void after() {
      session.getTransaction().begin();

      session.createQuery("delete from Tweet").executeUpdate();

      session.getTransaction().commit();

      session.close();
      sessionFactory.close();
   }

   @Test
   public void test() {
      FullTextSession fullTextSession = Search.getFullTextSession(session);
      QueryDescriptor query = ElasticsearchQueries.fromQueryString("content:絶対");
      List<Tweet> result = fullTextSession.createFullTextQuery(query, Tweet.class).list();

      Assert.assertEquals(1, result.size());
      Assert.assertEquals("絶対は絶対にない", result.get(0).getContent());
   }
}

各種プロパティについて

  • hibernate.search.default.indexmanager
    • elasticsearch を指定
  • hibernate.search.default.elasticsearch.index_schema_management_strategy
    • drop-and-create を指定(デフォルトは create)
  • hibernate.search.default.elasticsearch.host
    • Elasticsearch Service のエンドポイントを指定します
  • hibernate.search.default.elasticsearch.aws.signing.enabled
    • AWS のアクセスキーとシークレットキーで認証する場合は true
  • hibernate.search.default.elasticsearch.aws.access_key
    • AWS のアクセスキー
  • hibernate.search.default.elasticsearch.aws.secret_key
    • AWS のシークレットキー
  • hibernate.search.default.elasticsearch.aws.region
    • AWS のリージョン
  • hibernate.search.default.elasticsearch.required_index_status
    • Elasticsearch のレプリカを作らない場合はステータスが green にならないので yellow を指定
  • hibernate.search.default.elasticsearch.refresh_after_write
    • Elasticsearch は通常非同期で動作するので今回のようにユニットテストで即座に結果を判定したい場合は true にしておきます(ちょっとはまりました)

5. integration-test を実行する

./mvnw clean verify
...
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
...

きちんと動いてますね!

おまけ1 - Hibernate Search の気になる制限

@IndexedEmbedded の indexNullAs 設定が機能しない

関連イシュー:

おまけ2 - Amazon Elasticsearch Service の気になる制限

ユーザ辞書が使えない

VPC に配備できない