本文目录导读:

我来为您提供一个完整的Java向量数据库案例,使用Qdrant作为向量数据库,并集成OpenAI的Embedding API。
项目结构
vector-demo/
├── pom.xml
├── src/main/java/com/example/vectordemo/
│ ├── VectorDemoApplication.java
│ ├── config/
│ │ └── VectorConfig.java
│ ├── service/
│ │ ├── EmbeddingService.java
│ │ ├── VectorService.java
│ │ └── DocumentService.java
│ ├── model/
│ │ ├── Document.java
│ │ └── SearchResult.java
│ └── controller/
│ └── VectorController.java
Maven依赖 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>vector-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Qdrant Client -->
<dependency>
<groupId>io.qdrant</groupId>
<artifactId>client</artifactId>
<version>1.9.0</version>
</dependency>
<!-- OpenAI API -->
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>service</artifactId>
<version>0.18.2</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Jackson for JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置类 (VectorConfig.java)
package com.example.vectordemo.config;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class VectorConfig {
@Value("${qdrant.host:localhost}")
private String qdrantHost;
@Value("${qdrant.port:6334}")
private int qdrantPort;
@Bean
public QdrantClient qdrantClient() {
QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(
qdrantHost, qdrantPort, true
).build();
return new QdrantClient(grpcClient);
}
@Bean
public EmbeddingService embeddingService() {
return new EmbeddingService();
}
}
Embedding服务 (EmbeddingService.java)
package com.example.vectordemo.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
@Service
public class EmbeddingService {
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
@Value("${openai.api.key}")
private String apiKey;
@Value("${openai.embedding.model:text-embedding-ada-002}")
private String model;
public EmbeddingService() {
this.httpClient = HttpClient.newHttpClient();
this.objectMapper = new ObjectMapper();
}
/**
* 将文本转换为向量
*/
public List<Float> createEmbedding(String text) {
try {
String requestBody = objectMapper.writeValueAsString(
Map.of(
"model", model,
"input", text
)
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.openai.com/v1/embeddings"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey)
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
if (response.statusCode() == 200) {
JsonNode root = objectMapper.readTree(response.body());
return objectMapper.convertValue(
root.path("data").get(0).path("embedding"),
new ArrayList<Float>().getClass()
);
} else {
throw new RuntimeException("Embedding API error: " + response.body());
}
} catch (Exception e) {
throw new RuntimeException("Failed to create embedding", e);
}
}
private record Map(String s, String s2) {
}
}
文档模型 (Document.java)
package com.example.vectordemo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Document {
private String id;
private String content;
private String metadata;
private List<Float> vector = new ArrayList<>();
public Document(String id, String content, String metadata) {
this.id = id;
this.content = content;
this.metadata = metadata;
}
}
向量服务 (VectorService.java)
package com.example.vectordemo.service;
import com.example.vectordemo.model.Document;
import com.example.vectordemo.model.SearchResult;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.JsonWithInt;
import io.qdrant.client.grpc.Points;
import io.qdrant.client.grpc.Collections;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ExecutionException;
@Service
public class VectorService {
private final QdrantClient qdrantClient;
private final EmbeddingService embeddingService;
private static final String COLLECTION_NAME = "documents";
private static final int VECTOR_SIZE = 1536; // text-embedding-ada-002的向量维度
public VectorService(QdrantClient qdrantClient, EmbeddingService embeddingService) {
this.qdrantClient = qdrantClient;
this.embeddingService = embeddingService;
initializeCollection();
}
/**
* 初始化向量集合
*/
private void initializeCollection() {
try {
boolean exists = qdrantClient.collectionExistsAsync(COLLECTION_NAME).get();
if (!exists) {
Collections.VectorParams vectorParams = Collections.VectorParams.newBuilder()
.setSize(VECTOR_SIZE)
.setDistance(Collections.Distance.Cosine)
.build();
Collections.CreateCollection createCollection = Collections.CreateCollection.newBuilder()
.setCollectionName(COLLECTION_NAME)
.setVectorsConfig(
Collections.VectorsConfig.newBuilder()
.setParams(vectorParams)
)
.build();
qdrantClient.createCollectionAsync(createCollection).get();
System.out.println("Collection created: " + COLLECTION_NAME);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 添加文档
*/
public void addDocument(Document document) {
try {
// 生成向量
List<Float> embedding = embeddingService.createEmbedding(document.getContent());
document.setVector(embedding);
// 构建payload
Map<String, JsonWithInt> payload = new HashMap<>();
payload.put("content", JsonWithInt.from(document.getContent()));
payload.put("metadata", JsonWithInt.from(document.getMetadata()));
Points.PointStruct point = Points.PointStruct.newBuilder()
.setId(Points.PointId.newBuilder()
.setUuid(document.getId()))
.addAllVectors(
Collections.Vectors.newBuilder()
.putData(COLLECTION_NAME,
Points.Vectors.newBuilder()
.setVector(Points.Vector.newBuilder()
.addAllData(document.getVector())
.build())
.build())
.build())
.putAllPayload(payload)
.build();
qdrantClient.upsertAsync(
Points.UpsertPoints.newBuilder()
.setCollectionName(COLLECTION_NAME)
.addPoints(point)
.build()
).get();
System.out.println("Document added: " + document.getId());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 批量添加文档
*/
public void addDocuments(List<Document> documents) {
documents.forEach(this::addDocument);
}
/**
* 搜索相似文档
*/
public List<SearchResult> searchSimilar(String query, int limit) {
try {
// 生成查询向量
List<Float> queryVector = embeddingService.createEmbedding(query);
// 构建搜索请求
Points.SearchPoints searchPoints = Points.SearchPoints.newBuilder()
.setCollectionName(COLLECTION_NAME)
.addAllVector(queryVector)
.setLimit(limit)
.setWithPayload(true)
.build();
Points.SearchResponse response = qdrantClient.searchAsync(searchPoints).get();
List<SearchResult> results = new ArrayList<>();
for (Points.ScoredPoint scoredPoint : response.getResultList()) {
SearchResult result = new SearchResult();
result.setScore(scoredPoint.getScore());
result.setContent(scoredPoint.getPayloadOrThrow("content").getStringValue());
result.setMetadata(scoredPoint.getPayloadOrThrow("metadata").getStringValue());
results.add(result);
}
return results;
} catch (Exception e) {
e.printStackTrace();
return Collections.emptyList();
}
}
/**
* 删除文档
*/
public void deleteDocument(String id) {
try {
Points.DeletePoints deletePoints = Points.DeletePoints.newBuilder()
.setCollectionName(COLLECTION_NAME)
.addPoints(
Points.PointId.newBuilder()
.setUuid(id)
)
.build();
qdrantClient.deleteAsync(deletePoints).get();
System.out.println("Document deleted: " + id);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取集合统计信息
*/
public Map<String, Object> getCollectionInfo() {
Map<String, Object> info = new HashMap<>();
try {
Collections.CollectionInfo collectionInfo =
qdrantClient.getCollectionInfoAsync(COLLECTION_NAME).get()
.getResult();
info.put("name", collectionInfo.getConfig().getParams().getVectorsConfig());
info.put("points_count", collectionInfo.getPointsCount());
info.put("status", collectionInfo.getStatus().name());
} catch (Exception e) {
e.printStackTrace();
}
return info;
}
}
搜索结果模型 (SearchResult.java)
package com.example.vectordemo.model;
import lombok.Data;
@Data
public class SearchResult {
private String content;
private String metadata;
private float score;
}
控制器 (VectorController.java)
package com.example.vectordemo.controller;
import com.example.vectordemo.model.Document;
import com.example.vectordemo.model.SearchResult;
import com.example.vectordemo.service.VectorService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/vector")
public class VectorController {
private final VectorService vectorService;
public VectorController(VectorService vectorService) {
this.vectorService = vectorService;
}
/**
* 添加单个文档
*/
@PostMapping("/documents")
public ResponseEntity<String> addDocument(@RequestBody Document document) {
vectorService.addDocument(document);
return ResponseEntity.ok("Document added successfully: " + document.getId());
}
/**
* 批量添加文档
*/
@PostMapping("/documents/batch")
public ResponseEntity<String> addDocuments(@RequestBody List<Document> documents) {
vectorService.addDocuments(documents);
return ResponseEntity.ok("Added " + documents.size() + " documents");
}
/**
* 搜索相似文档
*/
@GetMapping("/search")
public ResponseEntity<List<SearchResult>> search(
@RequestParam String query,
@RequestParam(defaultValue = "10") int limit) {
List<SearchResult> results = vectorService.searchSimilar(query, limit);
return ResponseEntity.ok(results);
}
/**
* 删除文档
*/
@DeleteMapping("/documents/{id}")
public ResponseEntity<String> deleteDocument(@PathVariable String id) {
vectorService.deleteDocument(id);
return ResponseEntity.ok("Document deleted: " + id);
}
/**
* 获取集合信息
*/
@GetMapping("/info")
public ResponseEntity<Map<String, Object>> getCollectionInfo() {
return ResponseEntity.ok(vectorService.getCollectionInfo());
}
}
主应用类 (VectorDemoApplication.java)
package com.example.vectordemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class VectorDemoApplication {
public static void main(String[] args) {
SpringApplication.run(VectorDemoApplication.class, args);
}
}
配置文件 (application.yml)
server:
port: 8080
spring:
application:
name: vector-demo
qdrant:
host: localhost
port: 6334
openai:
api:
key: ${OPENAI_API_KEY}
embedding:
model: text-embedding-ada-002
测试示例
// 测试数据导入和搜索
@Component
public class VectorDataLoader implements CommandLineRunner {
@Autowired
private VectorService vectorService;
@Override
public void run(String... args) {
// 创建示例文档
List<Document> documents = List.of(
new Document(
UUID.randomUUID().toString(),
"Java是一种面向对象的编程语言,由Sun Microsystems开发",
"category:programming"
),
new Document(
UUID.randomUUID().toString(),
"Spring Boot是一个基于Java的开源框架,用于简化Spring应用的开发",
"category:framework"
),
new Document(
UUID.randomUUID().toString(),
"向量数据库专门用于存储和检索高维向量数据的数据库系统",
"category:database"
),
new Document(
UUID.randomUUID().toString(),
"OpenAI的人工智能模型可以生成高质量的文本嵌入向量",
"category:AI"
)
);
// 批量添加文档
vectorService.addDocuments(documents);
// 执行搜索测试
List<SearchResult> results = vectorService.searchSimilar(
"什么数据库适合存储向量数据?", 5
);
System.out.println("=== 搜索结果 ===");
results.forEach(result -> {
System.out.println("相似度: " + result.getScore());
System.out.println("内容: " + result.getContent());
System.out.println("---");
});
}
}
使用说明
-
启动Qdrant:
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant
-
设置环境变量:
export OPENAI_API_KEY="your-openai-api-key"
-
运行应用:
mvn spring-boot:run
-
测试API:
# 添加文档 curl -X POST http://localhost:8080/api/vector/documents \ -H "Content-Type: application/json" \ -d '{"id":"doc-1","content":"Java编程语言","metadata":"programming"}'
搜索
curl "http://localhost:8080/api/vector/search?query=编程语言&limit=5"
获取集合信息
curl http://localhost:8080/api/vector/info
这个案例展示了如何使用Java与向量数据库(Qdrant)集成,包括:
- 文档向量化处理
- 相似度搜索
- 文档管理
- REST API提供外部服务
您可以根据实际需求,调整向量维度、距离计算方式,或切换到其他向量数据库(如Milvus、Pinecone等)。