我一直在将 Spring Boot Elastic 应用程序从 2.x 升级到 3.x - 我正在努力解决以下错误,我已经能够在新的 Spring Boot ES 应用程序中重现该错误(来自 Spring Boot Initializer) )。正如您将看到的,代码是基本的。
在持久化文档时会出现此问题,并且在 Spring Boot 3.x(在 Spring Boot 3.1.4 上执行的测试)代码库中失败,但在 Spring Boot 2.7(针对 ES 7.11)上测试的相同代码却成功。
有什么想法吗? gitrepo 位于 https://github.com/kellizer/spring-boot-es-failure-3.1.4
错误
原因: co.elastic.clients.elasticsearch._types.ElasticsearchException: [es/index] 失败:[null] uri 的 HTTP 方法不正确 [/vaultdoc/_doc/?refresh=false] 和方法 [PUT],允许:[POST]
堆栈跟踪:
Caused by: co.elastic.clients.elasticsearch._types.ElasticsearchException: [es/index] failed: [null] Incorrect HTTP method for uri [/vaultdoc/_doc/?refresh=false] and method [PUT], allowed: [POST]
at co.elastic.clients.transport.rest_client.RestClientTransport.getHighLevelResponse(RestClientTransport.java:334)
at co.elastic.clients.transport.rest_client.RestClientTransport.performRequest(RestClientTransport.java:154)
at co.elastic.clients.elasticsearch.ElasticsearchClient.index(ElasticsearchClient.java:1116)
at org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate.lambda$doIndex$6(ElasticsearchTemplate.java:220)
at org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate.execute(ElasticsearchTemplate.java:623)
测试(Kotlin)
@Testcontainers
@SpringBootTest(classes = [TestApp::class], properties = ["logging.level.org.testcontainers=debug"])
@ContextConfiguration(initializers = [SetupContainers::class])
class VaultDocumentTest(@Autowired private val vaultDocumentRepo: VaultDocumentRepo) {
@Test
fun simpleTest() {
println("Count Call Works-->${vaultDocumentRepo.count()}")//this works - returns 0
println("findAll Works -->${vaultDocumentRepo.findAll().toList().size}") //this works - returns 0
vaultDocumentRepo.save(VaultDocument("Test101")) //save fails with error '[es/index] failed: [null] Incorrect HTTP method for uri [/vaultdoc/_doc/?refresh=false] and method [PUT], allowed: [POST]'
}
}
文档
@Document(indexName = "vaultdoc")
class VaultDocument(
@Field(type = FieldType.Text, store = true, fielddata = true)
val vaultDocumentName: String,
@org.springframework.data.annotation.Id
var id: String = "",
)
存储库
@Repository
interface VaultDocumentRepo : ElasticsearchRepository<VaultDocument, String> {
fun findByVaultDocumentName(vaultDocumentName: String): VaultDocument?
}
最后,测试容器是如何设置的。
class SetupContainers : ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(applicationContext: ConfigurableApplicationContext) {
elasticsearchContainer.withEnv(
mapOf(
"xpack.security.enabled" to "false",
"xpack.security.http.ssl.enabled" to "false",
"action.destructive_requires_name" to "false",
"reindex.remote.whitelist" to "localhost:9200"
)
)
elasticsearchContainer.start()
TestPropertyValues.of(
"spring.elasticsearch.uris=http://${elasticsearchContainer.httpHostAddress}",
).applyTo(applicationContext.environment)
}
companion object {
@Container
val elasticsearchContainer: ElasticsearchContainer = ElasticsearchContainer(
DockerImageName
.parse("docker.elastic.co/elasticsearch/elasticsearch")
.withTag("8.10.0")
)
}
}
下载错误来自过于严格的配置漏洞,阻止了“downloads.gradle.org”。
至于
PUT
和POST
:我查看了IndexRequest
的Elasticsearch客户端代码。当索引 id 不是 PUT
的文档时,它将发送 null
,否则发送 POST
:
// Request method
request -> {
final int _index = 1 << 0;
final int _id = 1 << 1;
int propsSet = 0;
propsSet |= _index;
if (request.id() != null)
propsSet |= _id;
if (propsSet == (_index | _id))
return "PUT";
if (propsSet == (_index))
return "POST";
throw SimpleEndpoint.noPathTemplateFound("method");
},
在代码中,您将 id 设置为空字符串的默认值,当然它不为空
@Document(indexName = "vaultdoc")
class VaultDocument(
@Field(type = FieldType.Text, store = true, fielddata = true)
val vaultDocumentName: String,
@org.springframework.data.annotation.Id
var id: String = "",
)
并且在您的测试中您没有指定 id,因此我们有一个空字符串:
vaultDocumentRepo.save(VaultDocument("Test101"))
因此发送了
PUT
,但在 url /vaultdoc/_doc/<empty id here>?refresh=false
中放入了空 id,但这是对没有 id 的新文档的请求,应该被 POST
ed。
您可以将 id 设为可为空并将默认值设置为
null
:
var id: String? = null
或者您删除默认的空字符串并为您保存的每个文档提供一个值。
为什么 Spring Data Elasticsearch 2 没有失败? 我没有挖掘 Elasticsearch 的旧代码,但我认为旧的
RestHighLevelClient
不仅检查 null,还检查空字符串