使用springboot操作es

  • 写在前面
  • 搭建项目环境和选择合适版本
  • 具体的代码实现(1)继承ProductInfoRepository
  • 具体的代码实现(2)使用ElasticsearchRestTemplate操作
  • 问题总结
  • 最后放个demo


写在前面

对于elasticsearch的搭建,前面写了一篇文章有简单描述如何搭建es,本次主要介绍如何在项目里使用,主要使用ElasticsearchRepository和ElasticsearchRestTemplate操作es。

搭建项目环境和选择合适版本

首先选择合适的项目组件版本,因为es版本和springboot版本有对应,如果不合适会报错。
这里以es7.6.x版本、springboot2.7.12,java8、maven3.8写出来的demo。
首先引入maven

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.7.12</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

主要还是第一个dependency配置,就是操作es的。

具体的代码实现(1)继承ProductInfoRepository

1.config类,主要用于配置扫描操作es所用的repository

@Configuration
@EnableElasticsearchRepositories(basePackages = "com.es.demo.repository")
public class ElasticsearchConfig {
}

2.对应的实体类ProductInfo

@Data
@Document(indexName = "product-info")
public class ProductInfo implements Serializable {

    @Id
    private Integer id;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_max_word", analyzer = "ik_smart")
    private String productName;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_max_word", analyzer = "ik_smart")
    private String description;
    @Field(type = FieldType.Keyword)
    private Date createTime;
    @Field(type = FieldType.Keyword)
    private BigDecimal price;
    @Field(type = FieldType.Integer)
    private Integer num;

}

3.创建repository包,这里防止所有的es对应的索引操作类。所有类继承org.springframework.data.elasticsearch.repository.ElasticsearchRepository,这里以ProductInfo为例:

@Component
public interface ProductInfoRepository extends ElasticsearchRepository<ProductInfo, Integer> {
}

第一个泛型是操作的索引类,第二个是唯一标识(id)对应的类型,
在这个类里按照一定的规则可以直接写一些方法名,不用写实现,ElasticsearchRepository会自动帮我们实现。具体我没有写,你们可以去网上搜一下怎么写。

4.因为ProductInfoRepository中实现了基本的增删改查,所以在这里可以直接使用

es 外表操作hive es在项目中如何使用_Elastic


如何使用:

public class ProductServiceImpl implements ProductService {
    @Autowired
    private ProductInfoRepository productInfoRepository;

    @Override
    public Boolean save(ProductInfo... productInfo) {
        productInfoRepository.saveAll(Arrays.asList(productInfo));
        return true;
    }

    @Override
    public Boolean delete(Integer id) {
        productInfoRepository.deleteById(id);
        return null;
    }

    @Override
    public ProductInfo getById(Integer id) {
        Optional<ProductInfo> byId = productInfoRepository.findById(id);
        return byId.orElse(null);
    }

    @Override
    public List<ProductInfo> getAll() {
        List<ProductInfo> list = new ArrayList<>();
        productInfoRepository.findAll().forEach(list::add);
        return list;
    }

具体的代码实现(2)使用ElasticsearchRestTemplate操作

1.对于一些复杂的查询我是直接使用ElasticsearchRestTemplate这个类来操作的。写了一个公共类,类似mybatisplus的service抽象,具体service接口和实现类可以直接继承。也是看别的开源项目这么写自己总结出来的。
首先是接口层BasicEsService:

public interface BasicEsService<T> {

    /**
     * 保存数据
     *
     * @param indexEnum 索引
     * @param ts        数据
     */
    void save(IndexEnum indexEnum, T... ts);

    /**
     * 删除数据
     *
     * @param indexEnum 索引
     * @param id        id
     */
    void delete(IndexEnum indexEnum, String id);

    /**
     * 查询单个数据
     *
     * @param indexEnum
     * @param id
     * @param clazz
     * @return
     */
    T getById(IndexEnum indexEnum, String id, T clazz);

    /**
     * 查询所有数据,es限制最多返回10000条
     *
     * @param indexEnum
     * @param clazz
     * @return
     */
    List<T> getAllData(IndexEnum indexEnum, T clazz);

    /**
     * 动态sql查询
     *
     * @param sql
     * @param clazz
     * @return
     */
    List<Map<String, Object>> query(String sql, T clazz);

    /**
     * 范围查询
     *
     * @param pageNo
     * @param pageSize
     * @param indexEnum
     * @param order
     * @param rangeParam
     * @return
     */
    Page<T> rangeQuery(Integer pageNo, Integer pageSize, IndexEnum indexEnum, Map<String, String> order, Map<String, Object[]> rangeParam, T clazz);

    /**
     * 分页查询,默认十条,支持多字段模糊匹配,多字段排序
     *
     * @param pageNo   从0开始
     * @param pageSize 每页记录数
     * @param keyword  关键词
     * @param clazz    转换的对象
     * @param order    排序集合
     * @param fields   搜索的列
     * @return
     */
    Page<T> pageList(Integer pageNo, Integer pageSize, String keyword, T clazz, Map<String, String> order, String... fields);

然后是实现类BasicEsServiceImpl

@Service
public class BasicEsServiceImpl<T> implements BasicEsService<T> {

    @Autowired
    protected ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Override
    public void save(IndexEnum indexEnum, T... ts) {
        T[] save = elasticsearchRestTemplate.save(ts, IndexCoordinates.of(indexEnum.getIndex()));
        return;
    }
    @Override
    public void delete(IndexEnum indexEnum, String id) {
        String delete = elasticsearchRestTemplate.delete(id, IndexCoordinates.of(indexEnum.getIndex()));
    }
    @Override
    public T getById(IndexEnum indexEnum, String id, T clazz) {
        elasticsearchRestTemplate.get(id, clazz.getClass(), IndexCoordinates.of(indexEnum.getIndex()));
        return null;
    }
    @Override
    public List<T> getAllData(IndexEnum indexEnum, T clazz) {
        Query query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchAllQuery()).build();
        SearchHits<T> searchHits = (SearchHits<T>) elasticsearchRestTemplate.search(query, clazz.getClass(), IndexCoordinates.of(indexEnum.getIndex()));
        return searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
    }
    @Override
    public List<Map<String, Object>> query(String sql, T clazz) {
        throw new AbstractMethodError();
    }
    @Override
    public Page<T> rangeQuery(Integer pageNo, Integer pageSize, IndexEnum indexEnum, Map<String, String> order, Map<String, Object[]> rangeParam, T clazz) {
        //范围查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        rangeParam.keySet().forEach(field -> {
            boolQueryBuilder.must(new RangeQueryBuilder(field).gt(rangeParam.get(field)[0]).lt(rangeParam.get(field)[1]));
        });
        PageRequest of = PageRequest.of(pageNo, pageSize);
        List<FieldSortBuilder> sortBuilderList = order.keySet().stream()
                .map(field -> SortBuilders.fieldSort(field).order(SortOrder.valueOf(order.get(field))))
                .collect(Collectors.toList());
        Query searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withPageable(of)
                .withSorts((SortBuilder<?>) sortBuilderList)
                .build();
        SearchHits<T> searchHits = (SearchHits<T>) elasticsearchRestTemplate.search(searchQuery, clazz.getClass());
        SearchPage<T> searchHits1 = SearchHitSupport.searchPageFor(searchHits, searchQuery.getPageable());
        return new PageImpl<>(searchHits.get().map(SearchHit::getContent).collect(Collectors.toList()), searchHits1.getPageable(), searchHits1.getTotalElements());
    }
    @Override
    public Page<T> pageList(Integer pageNo, Integer pageSize, String keyword, T clazz, Map<String, String> order, String... fields) {
        //分页,页码从0开始
        PageRequest of = PageRequest.of(pageNo, pageSize);
        List<FieldSortBuilder> collect = order.keySet().stream()
                .map(field -> SortBuilders.fieldSort(field).order(SortOrder.valueOf(order.get(field))))
                .collect(Collectors.toList());

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        Arrays.asList(fields).forEach(e -> boolQueryBuilder.should(QueryBuilders.fuzzyQuery(e, keyword)));
        Query searchQuery = new NativeSearchQueryBuilder()
                //条件
                .withQuery(boolQueryBuilder)
                //分页
                .withPageable(of)
                //排序
                .withSorts((SortBuilder<?>) collect)
                .build();
        SearchHits<T> searchHits = (SearchHits<T>) elasticsearchRestTemplate.search(searchQuery, clazz.getClass());
        SearchPage<T> searchHits1 = SearchHitSupport.searchPageFor(searchHits, searchQuery.getPageable());
        return new PageImpl<>(searchHits.get().map(SearchHit::getContent).collect(Collectors.toList()), searchHits1.getPageable(), searchHits1.getTotalElements());
    }
}

因为ElasticsearchRestTemplate这个操作需要传入具体的索引名称,所以我创建了一个公共枚举类存放es索引名称
IndexEnum

@Getter
@AllArgsConstructor
public enum IndexEnum {

    PRODUCT_INFO("product-info", "_doc");

    private String index;
    private String type;
}

使用的话就直接继承就行了
接口层

public interface ProductService extends BasicEsService<ProductInfo>{
}

实现层

@Service
@Slf4j
public class ProductServiceImpl extends BasicEsServiceImpl<ProductInfo> implements ProductService {
}

后面如果有公共的方法也可以抽出来放在公共类里。

问题总结

1.问题1:启动报错org.elasticsearch.client.ResponseException: method [PUT], host [http://10.0.180.100:9200], URI [/productInfo], status line [HTTP/1.1 406 Not Acceptable]

org.elasticsearch.client.ResponseException: method [PUT], host [http://10.0.180.100:9200], URI [/productInfo], status line [HTTP/1.1 406 Not Acceptable]

这是因为springboot和es版本不对应导致的,我最开始用的是springboot3和es7.6,后来把springboot版本降到了2.7.x。

问题2:“type”:“invalid_index_name_exception”

org.elasticsearch.client.ResponseException: method [PUT], host [http://10.0.180.100:9200], URI [/productInfo?master_timeout=30s&timeout=30s], status line [HTTP/1.1 400 Bad Request]
{"error":{"root_cause":[{"type":"invalid_index_name_exception","reason":"Invalid index name [productInfo], must be lowercase","index_uuid":"_na_","index":"productInfo"}],"type":"invalid_index_name_exception","reason":"Invalid index name [productInfo], must be lowercase","index_uuid":"_na_","index":"productInfo"},"status":400}

这是因为我索引名称用了大写字母,而es规定索引名称不能使用大写,所以修改配置
@Document(indexName = “productInfo”),替换成了@Document(indexName = “product-info”)

最后放个demo

最后在放一个我写的demo地址,有兴趣可以看下
https://gitee.com/wdvc/es-demo.git 唉,就这样吧,如果有问题请指出来我马上修改