Java向量数据库案例

wen java案例 2

本文目录导读:

Java向量数据库案例

  1. 搜索
  2. 获取集合信息

我来为您提供一个完整的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("---");
        });
    }
}

使用说明

  1. 启动Qdrant

    docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant
  2. 设置环境变量

    export OPENAI_API_KEY="your-openai-api-key"
  3. 运行应用

    mvn spring-boot:run
  4. 测试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等)。

抱歉,评论功能暂时关闭!