基本数据查询

1.数据查询

1.1 普通查询

通过ElasticsearchRepository定义的接口查询

Article articleByIdTmp  = articleRepository.findOne(article.getId());

通过自己定义的接口查询,通过title去查询数据,这里是全部匹配查询,而不是模糊查询

Article articleByTitle  = articleRepository.findByTitle(article.getTitle());

基本的查询都能够通过jpa的CrudRepository完成

public interface CrudRepository<T, ID extends Serializable>
    extends Repository<T, ID> {
    //保存
    <S extends T> S save(S entity); 
//通过id查找
    T findOne(ID primaryKey);       
//获取所有对象
    Iterable<T> findAll();          
//获取的count
    Long count();                   
//删除
    void delete(T entity);          
//判断是否存在
    boolean exists(ID primaryKey);  

    // … more functionality omitted.
}

1.2 复杂分页查询

这里主要是处理一些用方法名和一些基础的增删改差不能解决的,withFilter是原生里面的方法,跟query差不多,但是一般情况都会使用Filter,因为Filter的性能比query的高,这里主要是使用的andQuery,来配合多个条件的查询,同时andQuery里面也可以嵌套orQuery。同时添加PageRequest对象来进行分页.

Page<Article> page = articleRepository.search(new NativeSearchQueryBuilder()
                .withFilter(
                        andQuery(multiMatchQuery("content", "我是中国"),termQuery("title","我是标题")))
                .withPageable(new PageRequest(0,10))
                .build());

通过上述拿到的Page对象包含了如下属性,这些属性都是加入查询条件进行筛选后的

public interface Page<T> extends Slice<T> {

    /**
     * Returns the number of total pages.
     * 返回此次查询所有的页数
     * @return the number of total pages
     */
    int getTotalPages();

    /**
     * Returns the total amount of elements.
     * 返回所有元素的数量
     * @return the total amount of elements
     */
    long getTotalElements();

    /**
     * Returns a new {@link Page} with the content of the current one mapped by the given {@link Converter}.
     * 
     * @param converter must not be {@literal null}.
     * @return a new {@link Page} with the content of the current one mapped by the given {@link Converter}.
     * @since 1.10
     */
    <S> Page<S> map(Converter<? super T, ? extends S> converter);
}

Page对象实现了Slice接口,当然也实现了相应的方法获取如下属性

public interface Slice<T> extends Iterable<T> {

    /**
     * Returns the number of the current {@link Slice}. Is always non-negative.
     * 
     * @return the number of the current {@link Slice}.
     */
    int getNumber();

    /**
     * Returns the size of the {@link Slice}.
     * 
     * @return the size of the {@link Slice}.
     */
    int getSize();

    /**
     * Returns the number of elements currently on this {@link Slice}.
     * 
     * @return the number of elements currently on this {@link Slice}.
     */
    int getNumberOfElements();

    /**
     * Returns the page content as {@link List}.
     * 
     * @return
     */
    List<T> getContent();

    /**
     * Returns whether the {@link Slice} has content at all.
     * 
     * @return
     */
    boolean hasContent();

    /**
     * Returns the sorting parameters for the {@link Slice}.
     * 
     * @return
     */
    Sort getSort();

    /**
     * Returns whether the current {@link Slice} is the first one.
     * 
     * @return
     */
    boolean isFirst();

    /**
     * Returns whether the current {@link Slice} is the last one.
     * 
     * @return
     */
    boolean isLast();

    /**
     * Returns if there is a next {@link Slice}.
     * 
     * @return if there is a next {@link Slice}.
     */
    boolean hasNext();

    /**
     * Returns if there is a previous {@link Slice}.
     * 
     * @return if there is a previous {@link Slice}.
     */
    boolean hasPrevious();

    /**
     * Returns the {@link Pageable} to request the next {@link Slice}. Can be {@literal null} in case the current
     * {@link Slice} is already the last one. Clients should check {@link #hasNext()} before calling this method to make
     * sure they receive a non-{@literal null} value.
     * 
     * @return
     */
    Pageable nextPageable();

    /**
     * Returns the {@link Pageable} to request the previous {@link Slice}. Can be {@literal null} in case the current
     * {@link Slice} is already the first one. Clients should check {@link #hasPrevious()} before calling this method make
     * sure receive a non-{@literal null} value.
     * 
     * @return
     */
    Pageable previousPageable();

    /**
     * Returns a new {@link Slice} with the content of the current one mapped by the given {@link Converter}.
     * 
     * @param converter must not be {@literal null}.
     * @return a new {@link Slice} with the content of the current one mapped by the given {@link Converter}.
     * @since 1.10
     */
    <S> Slice<S> map(Converter<? super T, ? extends S> converter);
}

2 数据保存

2.1 普通保存方式

使用了Spring Data Elasticsearch,封装了一系列的对象处理操作,把数据保存到Elasticsearch,只需要调用Repository的save方法即可,当然这个对象需要指定具体的索引和需要被存储的字段。如果使用原生的方式保存数据就比较麻烦,首先需要判断索引是否存在,然后建立的相应的mapping。才能够很好的去保存数据。

public class MainApp {
    public static void main(String args[]) throws IOException, NoSuchMethodException {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-es.xml");
        ElasticsearchTemplate elasticsearchTemplate = (ElasticsearchTemplate) classPathXmlApplicationContext.getBean("elasticsearchTemplate");
        ArticleRepository articleRepository = classPathXmlApplicationContext.getBean(ArticleRepository.class);
        Article article = new Article();
        article.setContent("我是中国人,我不是日本人");
        article.setTitle("我是标题");
        article.setId(133);
        articleRepository.save(article);
        Page<Article> page = articleRepository.search(new NativeSearchQueryBuilder()
                .withQuery(termQuery("content", "我是中国"))
                .build());
    }
}
@Document(indexName = "coffee_test")
public class Article {
    @Id
    private int id;
    @Field(type = FieldType.String, index = FieldIndex.analyzed, store = true)
    private String content;
    @Field(type = FieldType.String, index = FieldIndex.not_analyzed, store = false)
    private String title;
    @Field(type = FieldType.Date, index = FieldIndex.not_analyzed, store = true)
    private Date updateDate;
}

2.2 动态索引

首先为什么会有这个需求,因为在开发中会存在多个环境的问题,往往集群只有一个,同时还需要实现环境的数据隔离,只能通过建立不同的索引来实现。当然如果用原生的api就不用过多考虑这个问题,因为本身的索引就是可以动态指定的。但是Spring Data Elasticsearch是通过注解的形式来指定索引的,这个时候脑海中浮现三个方案:

  1. 通过发布系统的模板(公司大神写的发布系统,感觉是基于travis ci二次开发的,因为界面真的是一样的,但是实现机制已经完全不一样了,采用的jenkins+mesos+Marathon进行的底层实现。估计大神只是觉得travis ci界面比较好看而已),编译的时候动态的改变java的注解。
  2. 通过配置,动态的去改变注解的字节码,这个比较麻烦,还需要添加第三方jar包。
  3. 通过原生api写代码,比较麻烦,不能为了这一个需求去妥协用原生恶心的代码。
  4. 使用ElasticsearchTemplate,来实现动态索引。

目前我能想到的也就4种方案,我选择的是第4中方案,因为比较简单实用。能用两句代码解决的问题,绝对不复杂化。这里也需要使用解析注解的方式,但是唯一好的是Spring Data Elasticsearch已经实现了解析注解,但是没有公开出来,我这里干了一件事情,就是把解析部分copy出来。具体代码链接

核心代码如下:

public static void createIndexIfNotExists(ElasticsearchTemplate elasticsearchTemplate, Class clazz, String indexName) {
      //判断索引是否存在
        boolean ex = elasticsearchTemplate.indexExists(indexName);
        if (!ex) {
           //创建索引
            elasticsearchTemplate.createIndex(indexName);
            //建立mapping
            elasticsearchTemplate.putMapping(indexName,
                    XContentBuilderUtils.getPersistentEntityFor(clazz, elasticsearchTemplate).getIndexType()
                    , XContentBuilderUtils.getXContentBuilder(clazz, elasticsearchTemplate)

            );
        }

    }

如果使用elasticsearchTemplate,也是需要自己去处理索引的状态,这里封装了对象操作,并且可以动态的改变索引名字,XContentBuilderUtils主要是可以根据对象的field注解自动的去生成原生的XContentBuilder来实现mapping操作,具体可以尝试下原生的mapping操作就知道了,

results matching ""

    No results matching ""