You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级到Spring Data Elasticsearch 5后如何正确执行Elasticsearch查询?

升级Spring Boot与Elasticsearch生态后的查询API兼容问题

升级背景

将Maven项目从Spring Boot 2.7升级到3.1.5,同步完成以下升级:

  • Spring Data Elasticsearch 至5.1版本
  • Elasticsearch 从7.17.2升级到8.7版本

核心问题1:新旧Query API不兼容

升级后,原org.elasticsearch.index.query.QueryBuilders所在依赖被移除,默认使用新的co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders构建查询,但新的Query类型与旧版本依赖的ElasticsearchOperations不兼容。

尝试切换到支持新Query类的co.elastic.clients.elasticsearch.ElasticsearchClient,自动注入后执行搜索时抛出**"Connection is closed"**错误,完整堆栈信息如下:

Unsatisfied dependency expressed through constructor parameter 4: Error creating bean with name 'myESRepository' defined in org.MyProject....MyESRepository defined in @EnableElasticsearchRepositories declared on IndexingConfiguration: Failed to instantiate [org.springframework.data.elasticsearch.repository.support.SimpleElasticsearchRepository]: Constructor threw exception
        at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:801)
        at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1352)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1189)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:950)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:616)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:738)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:440)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
        at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137)
        at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
        at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
        at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1406)
        at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:545)
        at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137)
        at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108)
        at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:187)
        at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:119)
        ... 73 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myESRepository' defined in org.blah.MyESRepository defined in @EnableElasticsearchRepositories declared on IndexingConfiguration: Failed to instantiate [org.springframework.data.elasticsearch.repository.support.SimpleElasticsearchRepository]: Constructor threw exception
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1770)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
        at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:910)
        at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:788)
        ... 97 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.elasticsearch.repository.support.SimpleElasticsearchRepository]: Constructor threw exception
        at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:224)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport.lambda$instantiateClass$5(RepositoryFactorySupport.java:571)
        at java.base/java.util.Optional.map(Optional.java:260)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport.instantiateClass(RepositoryFactorySupport.java:571)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport.getTargetRepositoryViaReflection(RepositoryFactorySupport.java:536)
        at org.springframework.data.elasticsearch.repository.support.ElasticsearchRepositoryFactory.getTargetRepository(ElasticsearchRepositoryFactory.java:79)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:317)
        at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:279)
        at org.springframework.data.util.Lazy.getNullable(Lazy.java:245)
        at org.springframework.data.util.Lazy.get(Lazy.java:114)
        at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:285)
        at org.springframework.data.elasticsearch.repository.support.ElasticsearchRepositoryFactoryBean.afterPropertiesSet(ElasticsearchRepositoryFactoryBean.java:69)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1817)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1766)
        ... 108 more
Caused by: org.springframework.dao.DataAccessResourceFailureException: Connection is closed
        at org.springframework.data.elasticsearch.client.elc.ElasticsearchExceptionTranslator.translateExceptionIfPossible(ElasticsearchExceptionTranslator.java:107)
        at org.springframework.data.elasticsearch.client.elc.ElasticsearchExceptionTranslator.translateException(ElasticsearchExceptionTranslator.java:63)
        at org.springframework.data.elasticsearch.client.elc.ChildTemplate.execute(ChildTemplate.java:73)
        at org.springframework.data.elasticsearch.client.elc.IndicesTemplate.doExists(IndicesTemplate.java:177)
        at org.springframework.data.elasticsearch.client.elc.IndicesTemplate.exists(IndicesTemplate.java:169)
        at org.springframework.data.elasticsearch.repository.support.SimpleElasticsearchRepository.<init>(SimpleElasticsearchRepository.java:83)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
        at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:211)
        ... 121 more
Caused by: java.lang.RuntimeException: Connection is closed
        at org.springframework.data.elasticsearch.client.elc.ElasticsearchExceptionTranslator.translateException(ElasticsearchExceptionTranslator.java:62)
        ... 131 more
Caused by: org.apache.http.ConnectionClosedException: Connection is closed
        at org.elasticsearch.client.RestClient.extractAndWrapCause(RestClient.java:920)
        at org.elasticsearch.client.RestClient.performRequest(RestClient.java:300)
        at org.elasticsearch.client.RestClient.performRequest(RestClient.java:288)
        at co.elastic.clients.transport.rest_client.RestClientTransport.performRequest(RestClientTransport.java:153)
        at co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient.exists(ElasticsearchIndicesClient.java:620)
        at org.springframework.data.elasticsearch.client.elc.IndicesTemplate.lambda$doExists$2(IndicesTemplate.java:177)
        at org.springframework.data.elasticsearch.client.elc.ChildTemplate.execute(ChildTemplate.java:71)
        ... 130 more
Caused by: org.apache.http.ConnectionClosedException: Connection is closed
        at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.endOfInput(HttpAsyncRequestExecutor.java:356)
        at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:261)
        at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:87)
        at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:40)
        at org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:114)
        at org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)
        at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)
        at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)
        at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)
        at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
        at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
        at java.base/java.lang.Thread.run(Thread.java:833)

测试环境配置

旧版本中通过@Autowired注入ElasticsearchOperations,集成测试共享Elasticsearch Testcontainer,实现以下接口:

@SpringBootTest
@Testcontainers
public interface ElasticsearchIntegrationTest {
    final DockerImageName ES_IMAGE = DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch:8.7.0");

    static ElasticsearchContainer elasticsearchContainer = new ElasticsearchContainer(ES_IMAGE);

    @DynamicPropertySource
    static void elasticsearchProperties(final DynamicPropertyRegistry dynamicPropertyRegistry) {
        final String username = "elastic";
        final String password = "password";
        elasticsearchContainer.withPassword(password);
        elasticsearchContainer.start();

        dynamicPropertyRegistry.add("spring.elasticsearch.username", () -> username);
        dynamicPropertyRegistry.add("spring.elasticsearch.password", () -> password);
        dynamicPropertyRegistry.add("spring.elasticsearch.uris",
                () -> List.of(elasticsearchContainer.getHttpHostAddress()));
    }
}

核心问题2:旧API路径走不通

手动添加被移除的Elasticsearch依赖后,仍可使用旧Query类型,但NativeQuery已被弃用:

<dependency>
  <groupId>org.elasticsearch</groupId>
  <artifactId>elasticsearch</artifactId>
  <version>8.11.3</version>
</dependency>

旧测试代码依赖NativeSearchQueryBuilder,但该类已在Spring Data 5中被移除:

class MyIndexIntegrationTest
        implements ElasticsearchIntegrationTest {

    @Autowired
    private ElasticsearchOperations elasticsearchOperations;

    @Test
    void testSearchMyIndex() {
         final String matchThis = "hello";
         final Query field1Query = new NativeSearchQueryBuilder() // Removed in Spring Data 5
                .withQuery(QueryBuilders.matchQuery("field1",
                        matchThis))
                .build();

          final List<MyIndex> actualIndexed = elasticsearchOperations
                .search(field1Query, MyIndex.class)
                .stream()
                .map(SearchHit::getContent)
                .toList();

          // assertions etc
     }

}

当前困境

  • 使用新API的QueryBuilders构建的Query无法兼容ElasticsearchOperations
  • 使用旧API则依赖已弃用的NativeQuery,不符合升级趋势
  • 切换到ElasticsearchClient后出现连接关闭错误

解决方案建议

方案1:使用Spring Data Elasticsearch 5.x的新Query API兼容ElasticsearchOperations

Spring Data Elasticsearch 5.x提供了基于Elasticsearch Client的ElasticsearchOperations实现,可直接使用新QueryBuilders构建查询,通过Query静态方法包装:

import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.Query;

// 注入ElasticsearchTemplate(ElasticsearchOperations的实现类)
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;

@Test
void testSearchWithNewAPI() {
    String matchThis = "hello";
    // 使用新QueryBuilders构建查询,并用Query.of包装
    Query query = Query.of(q -> q.match(m -> m.field("field1").query(matchThis)));

    List<MyIndex> actualIndexed = elasticsearchTemplate.search(query, MyIndex.class)
            .stream()
            .map(SearchHit::getContent)
            .toList();

    // 断言逻辑
}

方案2:修复ElasticsearchClient的连接关闭问题

连接关闭错误通常源于Testcontainer配置或客户端初始化问题:

  1. 确保容器完全就绪:在Testcontainer启动后添加健康检查等待,避免Spring上下文初始化时容器未就绪:
@DynamicPropertySource
static void elasticsearchProperties(final DynamicPropertyRegistry dynamicPropertyRegistry) {
    final String username = "elastic";
    final String password = "password";
    elasticsearchContainer.withPassword(password);
    elasticsearchContainer.start();
    // 等待Elasticsearch集群健康状态正常
    elasticsearchContainer.waitingFor(Wait.forHttp("/_cluster/health")
            .withBasicAuth(username, password)
            .forStatusCode(200));

    dynamicPropertyRegistry.add("spring.elasticsearch.username", () -> username);
    dynamicPropertyRegistry.add("spring.elasticsearch.password", () -> password);
    dynamicPropertyRegistry.add("spring.elasticsearch.uris",
            () -> List.of(elasticsearchContainer.getHttpHostAddress()));
}
  1. 依赖Spring自动配置:不要手动创建ElasticsearchClient Bean,使用Spring Boot自动配置的实例,避免连接池冲突。

方案3:临时兼容旧API(不推荐)

若需短期过渡,可使用新包路径下的NativeQuery,它是旧NativeQuery的替代类,支持旧版QueryBuilder:

import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.elasticsearch.index.query.QueryBuilders;

@Test
void testSearchWithLegacyAPI() {
    String matchThis = "hello";
    NativeQuery query = NativeQuery.builder()
            .withQuery(QueryBuilders.matchQuery("field1", matchThis))
            .build();

    List<MyIndex> actualIndexed = elasticsearchOperations.search(query, MyIndex.class)
            .stream()
            .map(SearchHit::getContent)
            .toList();
}

此方案仅作过渡使用,长期建议完全迁移到新API。


内容的提问来源于stack exchange,提问作者NoID

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 20:25:53