Spring Boot2.4.2整合Elasticsearch7.10.2

Spring Boot整合Elasticsearch

目录

Spring Boot整合Elasticsearch

Spring Data Repositories方式

编写实体类

编写Repository

ElasticsearchRestTemplate方式

CURD

另类查询方式-sql

jdbc方式

api方式


Elasticsearch版本7.10.2

Spring Boot版本2.4.2

使用Spring Boot操作Elasticsearch有两种方式,一种是Spring Data Repositories的方式,一种是使用ElasticsearchRestTemplate方式。

Spring Data Repositories方式

先放Spring Boot文档https://docs.spring.io/spring-data/elasticsearch/docs/4.1.3/reference/html/#repositories.core-concepts

编写实体类

编写实体类主要会用到的注解

  1. @Document(必写)

    属性名说明
    indexName索引名,支持SpEl
    typedeprecated since Elasticsearch 4.0
    shards分片
    replicas每个分区备份数
    refreshIntervall刷新间隔,默认1s
    indexStoreType索引文件存储类型,默认fs
    versionType配置版本管理,默认EXTERNAL
  2. @Id(必写)

  3. @Field

    属性名说明
    name将在Elasticsearch文档中表示的字段名称,如果未设置,则使用Java字段名称
    type属性类型
    store标记是否原始字段值应存储在Elasticsearch中,默认值为false。

demo

@Data
@Document(indexName = "stu", shards = 3, replicas = 0)
public class StuEntity {
    @Id
    private Long stuId;
​
    @Field(store = true)
    private String name;
​
    @Field(store = true)
    private Integer age;
​
    @Field(store = true, type = FieldType.Keyword)
    private String sign;
​
    @Field(store = true)
    private String description;
}

编写Repository

编写一个借口继承ElasticsearchRepository<T, ID>

public interface StuEntityRepository extends ElasticsearchRepository<Book, String> {
    List<Book> findAllByName(String name);
}

注意:ElasticsearchRepository接口中的方式都是@Deprecated

编写curd

编写curd有两种:关键字拼接查询条件、@Query注解查询

关键字拼接查询条件

和关系型数据库一样直接使用就好,Elasticsearch有特殊的关键字,用到了自己查询官方文档。

KeywordSampleElasticsearch Query String
AndfindByNameAndPrice{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }}
OrfindByNameOrPrice{ "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }}
IsfindByName{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }}
NotfindByNameNot{ "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }}
BetweenfindByPriceBetween{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}
LessThanfindByPriceLessThan{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }}
LessThanEqualfindByPriceLessThanEqual{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}
GreaterThanfindByPriceGreaterThan{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }}
GreaterThanEqualfindByPriceGreaterThan{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }}
BeforefindByPriceBefore{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}
AfterfindByPriceAfter{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }}
LikefindByNameLike{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}
StartingWithfindByNameStartingWith{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}
EndingWithfindByNameEndingWith{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}
Contains/ContainingfindByNameContaining{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}
In (when annotated as FieldType.Keyword)findByNameIn(Collection<String>names){ "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }}
InfindByNameIn(Collection<String>names){ "query": {"bool": {"must": [{"query_string":{"query": "\"?\" \"?\"", "fields": ["name"]}}]}}}
NotIn (when annotated as FieldType.Keyword)findByNameNotIn(Collection<String>names){ "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }}
NotInfindByNameNotIn(Collection<String>names){"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}
NearfindByStoreNearNot Supported Yet !
TruefindByAvailableTrue{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }}
FalsefindByAvailableFalse{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }}
OrderByfindByAvailableTrueOrderByNameDesc{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] }

@Query注解查询

public interface StuEntityRepository extends ElasticsearchRepository<StuEntity, Long> {
    List<StuEntity> findAllByName(String name);
​
    @Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
    Page<StuEntity> findByName(String name, Pageable pageable);
}

ElasticsearchRestTemplate方式

ElasticsearchRestTemplate是在spring data 中操作Elasticsearch的模板类,其中实现了对Elasticsearch 操作的各类操作方法。例如创建索引、创建别名、创建映射,以及数据的查询和其他操作。

放官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/4.1.3/reference/html/#elasticsearch.operations

主要有以下操作

  • IndexOperations定义索引级别的操作,如创建或删除索引。

  • DocumentOperations定义基于实体 ID 存储、更新和检索实体的操作。

  • SearchOperations定义使用查询搜索多个实体的操作

 

ElasticsearchRestTemplate类
ElasticsearchRestTemplate类结构图

CURD

下面demo中的StuEntity为我们之前创建的那个类

  1. 新增

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    ​
    @Test
    public void add() {
        StuEntity stu = new StuEntity();
        stu.setStuId(1005L);
        stu.setName("iron man");
        stu.setAge(54);
        stu.setSign("I am iron man");
        stu.setDescription("I have a iron army");
        elasticsearchRestTemplate.save(stu);
    }

    直接调用elasticsearchRestTemplateDocumentOperations的save方法就可以了,save方法有几个重载方法,详情看api或源码

  2. 修改

    @Test
    public void update() {
        StuEntity stu = new StuEntity();
        stu.setStuId(1005L);
        stu.setName("iron manss");
        stu.setAge(100);
        stu.setSign("I am iron man");
        stu.setDescription("I have a iron army");
        System.out.println(JSON.toJSONString(stu));
        // 创建Document对象
        // 第一种方式
        Document document = Document.create();
        // 将修改的内容塞进去
        document.putAll(JSON.parseObject(JSON.toJSONString(stu), Map.class));
    ​
        // 第二种方式
        Document document1 = Document.parse(JSON.toJSONString(stu));
    ​
        // 第三种方式
        Document document2 = Document.from(JSON.parseObject(JSON.toJSONString(stu), Map.class));
    ​
        // 构造updateQuery
        UpdateQuery updateQuery = UpdateQuery.builder("1")
            // 如果不存在就新增,默认为false
            .withDocAsUpsert(true)
            .withDocument(Document.parse(JSON.toJSONString(stu)))
            .build();
        elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("stu"));
    }

    直接调用elasticsearchRestTemplateDocumentOperations的update方法,详情看api或者源码

  3. 删除

    @Test
    public void delete() {
        StuEntity stu = new StuEntity();
        stu.setStuId(1005L);
        stu.setName("iron man");
        stu.setAge(54);
        stu.setSign("I am iron man");
        stu.setDescription("I have a iron army");
        elasticsearchRestTemplate.delete(stu);
    }

    直接调用elasticsearchRestTemplateDocumentOperations的delete方法,delete有好几个重载方法,具体使用哪个详情看api或者源码

  4. 查询

    查询就调用elasticsearchRestTemplate中SearchOperations`的search方法。

    在search的各种方法中都需要传入Query。Spring Data Elasticsearch中Query的实现类CriteriaQuery, StringQuery and NativeSearchQuery

  • CriteriaQuery基于查询的查询允许创建查询来搜索数据,而无需了解 Elasticsearch 查询的语法或基础知识。它们允许用户通过简单地链接和组合指定搜索文档必须满足的条件的对象来生成查询

  • StringQuery使用json字符串来构建查询条件。就和Repository中@Query注解中的那个json字符串一样。

  • NativeSearchQuery用于复杂查询。

    使用CriteriaQuery来构建查询

    @Test
    public void search1() {
        Criteria criteria = new Criteria("name").is("iron man");
        Query query = new CriteriaQuery(criteria);
        SearchHits searchHits = elasticsearchRestTemplate.search(query, StuEntity.class);
        System.out.println(searchHits.getSearchHits());
    }

    使用StringQuery构建查询

    @Test
    public void search2() {
        Query query = new StringQuery("{\n" +
                                      "    \"match\": { \n" +
                                      "      \"age\": { \"query\": \"54\" } \n" +
                                      "    } \n" +
                                      "  }");
        SearchHits<StuEntity> searchHits = elasticsearchRestTemplate.search(query, StuEntity.class);
        System.out.println(searchHits.getSearchHits());
    }

    NativeSearchQuery查询网上的例子就很多了,以后再写篇文章写。

另类查询方式-sql

使用elasticsearch-sql插件像写sql一样查询Elasticsearch。

elasticsearch-sql的github地址

jdbc方式

  1. 添加依赖

    <dependency>
                    <groupId>org.nlpcn</groupId>
                <artifactId>elasticsearch-sql</artifactId>
                <version>7.8.0.1</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.elasticsearch.client/x-pack-transport -->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>x-pack-transport</artifactId>
                <version>7.10.2</version>
                <exclusions>
                    <exclusion>
                        <artifactId>elasticsearch-core</artifactId>
                        <groupId>org.elasticsearch</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>elasticsearch-ssl-config</artifactId>
                        <groupId>org.elasticsearch</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.elasticsearch.plugin/x-pack-core -->
            <dependency>
                <groupId>org.elasticsearch.plugin</groupId>
                <artifactId>x-pack-core</artifactId>
                <version>7.10.2</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>7.10.2</version>
            </dependency>
    ​
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>transport</artifactId>
                <version>7.10.2</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch.plugin</groupId>
                <artifactId>transport-netty4-client</artifactId>
                <version>7.10.2</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.16</version>
            </dependency>

    Elasticsearch是7.10.2。现在elasticsearch-sql还没有7.10.2所以就用能用到的最新的7.8.0.1。druid是1.1.16。

    @Test
    public void search4() throws Exception {
        Properties properties = new Properties();
        properties.put("url", "jdbc:elasticsearch://192.168.1.123:9300/");
        DruidDataSource dds = (DruidDataSource) ElasticSearchDruidDataSourceFactory.createDataSource(properties);
        Connection connection = dds.getConnection();
        PreparedStatement ps = connection.prepareStatement("SELECT * from stu");
        ResultSet resultSet = ps.executeQuery();
        ps.close();
        connection.close();
        dds.close();
    }

    当然为了查询方便,封装成NamedParameterJdbcTemplate。

     

api方式

这个方式是我在一次调试的时候发现的,官方文档中并没有介绍。仅供参考。

controller

@RestController
@RequestMapping("/elasticsearchSqlApiController")
public class ElasticsearchSqlApiController {
​
    @Autowired
    private ElasticsearchApiDao elasticsearchApiDao;
​
    /**
     * 使用es-sql插件的api方式查询sql
     * @param sql 要查询的sql 例如:select * from student where name='小憨'
     * @return
     */
    @GetMapping("/search")
    public ActionResult search(String sql) {
        return ActionResult.success(elasticsearchApiDao.search(sql));
    }
​
    /**
     * 将sql解析为 DSL语句
     * @param sql
     * @return
     */
    @GetMapping("/explain")
    public ActionResult explain(String sql) {
//        String sql = "select * from a_icd_person where PERNAME='王晓光'";
        return ActionResult.success(elasticsearchApiDao.explain(sql));
    }
}

service

@Slf4j
@Service
public class ElasticsearchApiDaoImpl implements ElasticsearchApiDao {
​
    @Autowired
    private TransportClient transportClient;
​
    @Override
    public EsSearchResultDTO search(String sql) {
        EsSearchResultDTO resultDTO = new EsSearchResultDTO();
        try {
            long before = System.currentTimeMillis();
            SearchDao searchDao = new SearchDao(transportClient);
            QueryAction queryAction = searchDao.explain(sql);
            Object execution = QueryActionElasticExecutor.executeAnyAction(searchDao.getClient(), queryAction);
            ObjectResult result = getObjectResult(execution, true, false, false, true, false, queryAction);
            resultDTO.setResultColumns(Sets.newHashSet(result.getHeaders()));
            List<IndexRowData> indexRowDatas = new ArrayList<>();
            for (List<Object> line : result.getLines()) {
                IndexRowData indexRowData = new IndexRowData();
                for (int i = 0; i < result.getHeaders().size(); i++) {
                    indexRowData.build(result.getHeaders().get(i), line.get(i));
                }
                indexRowDatas.add(indexRowData);
            }
            resultDTO.setResultSize(indexRowDatas.size());
            if (execution instanceof SearchHits) {
                resultDTO.setTotal(((SearchHits) execution).getTotalHits());
            } else {
                resultDTO.setTotal(indexRowDatas.size());
            }
            resultDTO.setResult(indexRowDatas);
            resultDTO.setTime((System.currentTimeMillis() - before) / 1000);
            log.info("查询数据结果集: {}", JSONObject.toJSONString(resultDTO));
        } catch (Exception e) {
            throw new ElasticsearchException("根据ES-SQL查询数据异常: {}", e, e.getMessage());
        }
        return resultDTO;
    }
​
    /**
     * 解析sql
     * @param sql
     * @return
     */
    @Override
    public String explain(String sql) {
        SearchDao searchDao = new SearchDao(transportClient);
        QueryAction queryAction = null;
        try {
            queryAction = searchDao.explain(sql);
            return queryAction.explain().explain();
        } catch (SqlParseException e) {
            throw new RuntimeException(e);
        } catch (SQLFeatureNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
​
    private ObjectResult getObjectResult(Object execution, boolean flat, boolean includeScore, boolean includeType, boolean includeId, boolean incluedScrollId, QueryAction queryAction) throws Exception {
        return (new ObjectResultsExtractor(includeScore, includeType, includeId, incluedScrollId, queryAction)).extractResults(execution, flat);
    }
}
​

配置类

@Slf4j
@Configuration
@PropertySource("classpath:elasticsearch.properties")
public class ElasticsearchConfig {
    @Value("${elasticSearch.host}")
    private String[] ipAddress;
    @Value("${elasticSearch.maxRetryTimeout}")
    private Integer maxRetryTimeout;
    @Value("${elasticSearch.sql.host}")
    private String[] esSqlAddress;
​
    @Bean
    public TransportClient transportClient() {
​
        Settings settings = Settings.builder()
                // 不允许自动刷新地址列表
                .put("client.transport.sniff", false)
                .put("client.transport.ignore_cluster_name", true)
                .build();
​
        // 初始化地址
        TransportAddress[] transportAddresses = new TransportAddress[esSqlAddress.length];
        for (int i = 0; i < esSqlAddress.length; i++) {
            String[] addressItems = esSqlAddress[i].split(":");
            try {
                transportAddresses[i] = new TransportAddress(InetAddress.getByName(addressItems[0]),
                        Integer.valueOf(addressItems[1]));
            } catch (UnknownHostException e) {
                log.error(e.toString());
            }
        }
​
        PreBuiltTransportClient preBuiltTransportClient = new PreBuiltTransportClient(settings);
​
        TransportClient client = preBuiltTransportClient
                .addTransportAddresses(transportAddreses);
        return client;
    }
}

 

最后:

sql的方式虽然不是Elasticsearch官方推荐的方式,但是上手快,会写sql就能写查询。Elasticsearch官方在6.3.0之后就开始支持sql方式了,但不是免费的,所以当时有个项目就用的这个elasticsearch-sql这个jar包。总体来说还行,但是有缺陷,基本的查询没有问题,但是高级操作就不如用官方推荐的了。这个仓库的star数从1k多到现在6.2k了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值