驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
【ELK】ES JPA 上
/  

【ELK】ES JPA 上

基础集成

引入项目依赖,下面只引入关键依赖。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.1</version>
    <relativePath/>
    <!-- lookup parent from repository -->
</parent>

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

不同的版本有不同的依赖,请选择自己需要的版本,下图是具体的信息。

| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Boot |
| - | - | - | - |
| - |
| 2020.0.0[1] | 4.1.x[1] | 7.9.3 | 2.4.x[1] |
| - | - | - | - |
| Neumann | 4.0.x | 7.6.2 | 2.3.x |
| Moore | 3.2.x | 6.8.12 | 2.2.x |
| Lovelace | 3.1.x | 6.2.2 | 2.1.x |
| Kay[2] | 3.0.x[2] | 5.5.0 | 2.0.x[2] |
| Ingalls[2] | 2.1.x[2] | 2.4.0 | 1.5.x[2] |

对应文档:https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/

在当前的 SpringBoot 2.4 + 版本,已经不再使用 TransportClientElasticsearchTemplate ,而是推荐使用:ElasticsearchRestTemplate ,同时此时不需要手动引入关键类,只需要在 application.peroperties中配置如下信息即可。

spring.elasticsearch.rest.uris=http://localhost:9200

使用的时候,通过 Autowired 注入<span>ElasticsearchRestTemplate</span> 即可。

假如有一个实体类,我们定义如下:

@Setting(settingPath = "es/es-setting.json")
@Document(indexName = "book")
public class Book {
    
    @Id
    private String id;

    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ue-ngram")
    private String author;

    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String name;

    @Field(type = FieldType.Date, format = DateFormat.date)
    private LocalDate publishDate;

    @Field(type = FieldType.Date, format = DateFormat.date_time)
    private LocalDateTime publishDateTime;


    //get set toString 省略
}
  • @Setting表示的是ES的配置信息,包括自定义的索引器、分片等信息。不知道如何定义的可以参考文章:自定义分词器案例
  • @Documen中 indexName 是索引的名称
  • @Filed中的信息重点讲解searchAnalyzer和analyzer
  • <span>searchAnalyzer</span> 表示的是查询时候该单词如何分词
  • <span>analyzer</span> 表示的是索引(保存)的时候如何分词
  • @Filed中的type表示的是该字段类型,重点强调下时间类型
  • 使用注解type = FieldType.Date
  • 如果是LocalDate 那么format=DateFormat.date
  • 如果是LocalDateTime ,使用format = DateFormat.date_time

JPA是什么

其作用有主要有 2 个

  • 内置 通用的 CRUD 进行简单的基础操作
  • 通过方法命名的形式让框架自动生成操作代码:参考链接

当然我个人认为其存在几个缺点

  • 通过方法命名进行扩展的做法可能导致部分情况方法名太长了
  • 不太符合中文分词的搜索,比如你要查包含不能Contains ,而应该使用Eqauls【后面会谈到】

内置 API

image.png

关键接口是:CrudRepositoryPagingAndSortingRepository

  • CrudRepository集成了基本的 CRUD,如下图所示

image.png

  • PagingAndSortingRepositoryCrudRepository的基础上定义了**分页和排序 **操作。

使用的时候,可以直接使用带分页的PagingAndSortingRepository

public interface BookEsMapper extends PagingAndSortingRepository<Book, String> {
}

比如定义了上述接口,并且在配置类获取启动类上添加了注解@EnableElasticsearchRepositories(basePackages = "club.hicode.dockerhi")

那么就可以使用了,下面给出一个插入的代码

@Test
    public void testBookSave() {
        Book book = new Book();
        book.setName("Kotlin开发手册");
        book.setAuthor("文在寅");
        book.setId("012");
        book.setPublishDate(LocalDate.now());
        book.setPublishDateTime(LocalDateTime.now());
        Book result = bookEsMapper.save(book);
        System.out.println(result);
    }


    @Test
    public void testBaseApi() {
        Optional<Book> optBook = bookEsMapper.findById("012");
        optBook.ifPresent(System.out::println);
        Iterable<Book> all = bookEsMapper.findAll();
        all.forEach(System.out::println);
        bookEsMapper.deleteById("012");
        System.out.println("删除 ok");
    }

执行成功后,通过 DevTools 的指令 GET /book 可以查看到如下信息

{
  "book" : {
    "aliases" : { },
    "mappings" : {
      "properties" : {
        "_class" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "author" : {
          "type" : "text",
          "analyzer" : "ue-ngram"
        },
        "id" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "name" : {
          "type" : "text",
          "analyzer" : "ik_max_word",
          "search_analyzer" : "ik_smart"
        },
        "publishDate" : {
          "type" : "date",
          "format" : "date"
        },
        "publishDateTime" : {
          "type" : "date",
          "format" : "date_time"
        }
      }
    },
    "settings" : {
      "index" : {
        "routing" : {
          "allocation" : {
            "include" : {
              "_tier_preference" : "data_content"
            }
          }
        },
        "number_of_shards" : "1",
        "provided_name" : "book",
        "creation_date" : "1611655951204",
        "analysis" : {
          "filter" : {
            "my_pinyin" : {
              "keep_joined_full_pinyin" : "true",
              "lowercase" : "true",
              "none_chinese_pinyin_tokenize" : "false",
              "keep_original" : "true",
              "keep_none_chinese_together" : "true",
              "keep_first_letter" : "true",
              "trim_whitespace" : "true",
              "type" : "pinyin",
              "keep_none_chinese" : "true",
              "keep_full_pinyin" : "false"
            }
          },
          "char_filter" : {
            "ue_char_filter" : {
              "type" : "mapping",
              "mappings" : [
                "- => ,",
                "— => ,"
              ]
            }
          },
          "analyzer" : {
            "ue_ik_pinyin_analyzer" : {
              "filter" : [
                "my_pinyin"
              ],
              "char_filter" : [
                "html_strip",
                "ue_char_filter"
              ],
              "type" : "custom",
              "tokenizer" : "ik_max_word"
            },
            "ue-ngram" : {
              "type" : "custom",
              "char_filter" : [
                "html_strip",
                "ue_char_filter"
              ],
              "tokenizer" : "ngram_tokenizer"
            }
          },
          "tokenizer" : {
            "ngram_tokenizer" : {
              "token_chars" : [
                "letter",
                "digit"
              ],
              "min_gram" : "2",
              "type" : "ngram",
              "max_gram" : "3"
            }
          }
        },
        "number_of_replicas" : "1",
        "uuid" : "ipVtuyH6Qsy422WWQ39gQw",
        "version" : {
          "created" : "7100199"
        }
      }
    }
  }
}

描述下 settings 的内容就是上文代码中@Setting(settingPath = "es/es-setting.json")设置中的内容。

扩展 API

下面给出实例代码

public interface BookEsMapper extends PagingAndSortingRepository<Book, String> {

    List<Book> findByAuthorContains(String author);

    List<Book> findByAuthorEquals(String author);

    List<Book> findByNameContaining(String name);

    List<Book> findByNameEquals(String name);
}

当我们调用这些代码的时候,为了更方便的进行调试和展示 Query语句,建议在配置文件application.properties添加

logging.level.tracer=TRACE

个人不建议使用 在中文语义下使用 JPA 进行Text的查询,因为这其实挺让人迷糊的,我在学习的时候踩了不少坑。

举个例子:

Book类中定义了 name字段的索引分词是 ik_max_word,检索分词用的是 ik_smart

对于kotlin 开发手册,索引的时候分词为:kotlin/开发/手册

但是如果使用findByNameContaining("发手")的时候,虽然在语义上是发手是包含于开发手册,但是因为分词的缘故,检索不到。

综上,个人建议 JPA检索上的使用更应该是基于 Term的查询和英文检索。新增、修改、删除是没有问题的。

总结

本文主要是 2 个点:

  • 实体类构建:关键注解、Setting 设置
  • JPA的基本 API和简单 API,更重要的是用于检索的时候,因为 API语义问题,可能给初学者造成误解。
骐骥一跃,不能十步。驽马十驾,功在不舍。