我正在将 Maven 项目从 Spring Boot 2.7 升级到 Spring Boot 3.1.5,并将其迁移到 Spring Data Elasticsearch 5.1 和 Elasticsearch 7.17.2 到 8.7。
在更新 Spring Data Elasticsearch 后,他们删除了依赖项,其中包含:
org.elasticsearch.index.query.QueryBuilders;
因此,它默认附带一组新的查询对象:
co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
但是,新的查询类型与
Elasticsearchoperations
不兼容,而这正是我在以前版本的 Elasticsearch 中能够使用的。
我尝试将其替换为:
co.elastic.clients.elasticsearch.ElasticsearchClient;
因为这使用了新的
Query
类,但正如评论者所说,我似乎已经搞错了。如果我尝试 @Autowire
ElasticsearchClient 并运行搜索,我会收到错误“连接已关闭”:
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)
在之前的版本中,我可以在 ElasticsearchOperations 上使用
@Autowired
,并且在我的集成测试中,它将正确连接到正在运行的 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()));
}
}
如果我选择手动包含从 Spring Data Elasticsearch 中删除的依赖项(感觉不对):
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>8.11.3</version>
</dependency>
然后我仍然可以使用旧的查询类型,除了
NativeQuery
,它已被弃用。
因此,如果我使用新 API 编写查询,我将无法使用 Elasticsearchoperations,但如果我坚持使用旧 API,我将无法使用 NativeQuery。
我的测试课
Elasticsearchoperations
看起来像这样:
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
}
}
这在 Spring Data Elasticsearch 的早期版本中有效,但现在 NativeSearchQueryBuilder 已被删除。如果我能找到一种方法将 matchQuery 转换为 Elasticsearchoperations.search 可以作为其第一个参数的标准查询,我就有可能绕过它,但这似乎不可能,而且依赖旧的查询似乎是错误的API(我什至不知道 Elasticsearch 8 容器是否可以使用该 API)。
那我该怎么办?如何使用已弃用的 NativeQuery 运行搜索?或者,我如何使用新 API 中的新 QueryBuilder 进行搜索?
ElasticsearchOperations
将 Query
作为参数,它是 Spring Data Elasticsearch 中定义的接口。其中一个实现是在版本 NativeSearchQuery
中,它已被 NativeQuery
取代。顾名思义,这些实现使用底层 Elasticsearch 客户端库的本机函数,这在版本 5 中发生了变化。因此,您需要使用 Elasticsearch Java 客户端的方法重新创建查询。
或者,如果您想独立于客户端代码,请查看是否可以使用
CriteriaQuery
或 Spring Data 存储库以及类似 findByField1(String)
的匹配方法
“连接已关闭”错误确实来自 SSL 强制执行。我可以通过将其添加到我的 DynamicPropertySource 来解决这个问题:
elasticsearchContainer.getEnvMap().put("xpack.security.enabled", "false");
至于
Elasticsearchoperations
,我始终无法让它与最新的 API 一起工作,但我发现我完全可以不用它,只需向我的 ElasticsearchRepository 接口添加方法来执行我需要的搜索即可。在上面的例子中,它是:
@Repository
public interface MyIndexRepository extends ElasticsearchRepository<MyIndex, UUID> {
List<MyIndex> findByField1(String field1);
}
还有测试班:
final List<MyIndex> actualIndexed = myIndexRepository.findByField1("hello");
这可以通过手动包含 org.elasticsearch.elasticsearch 依赖项(甚至高达 8.11.2)来保留原始 API 的 Query 对象,或者实际上通过使用
co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders
中的新 Query API