MybatisPlus + PostgreSQL 实现向量检索

1.前置条件

已经安装好postgresql,并且数据库安装了pgvector插件,可以创建带有向量字段的数据表

2.创建表 带有向量字段vector, vector类型 维度1024

CREATE TABLE document_chunk
(
    id          VARCHAR(64) PRIMARY KEY,
    document_id VARCHAR(64),
    chunk       VARCHAR(1024),
    chunk_index INT,
    vector      vector(1024)
);

COMMENT ON TABLE document_chunk IS '文档分片表';
COMMENT ON COLUMN document_chunk.id IS '主键id';
COMMENT ON COLUMN document_chunk.document_id IS '所属文档id';
COMMENT ON COLUMN document_chunk.chunk IS '文档分片内容';
COMMENT ON COLUMN document_chunk.chunk_index IS '分片索引';
COMMENT ON COLUMN document_chunk.vector IS '向量';

3.postgresql vector类型的理解,存储

postgresql 的 vector类型 本质上就是一个 向量字符串

所以存储的时候存储的向量字符串 '[-0.0123, 0.0456, ...]'

1.需要自定义一个类型转换器 来将 我们 java实体中的 List<Double>(这里也可以写 float[],看你喜欢使用哪种类型去存储向量) 转为为  '[-0.0123, 0.0456, ...]' 这种向量字符串字符串

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.postgresql.util.PGobject;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 由于 MyBatis-Plus 不直接支持自定义的扩展类型(如 pgvector),你需要自定义一个 TypeHandler 来处理 vector 类型的序列化和反序列化。
 * TypeHandler 字段类型处理器:在 MyBatis 中,类型处理器(TypeHandler)扮演着 JavaType 与 JdbcType 之间转换的桥梁角色。它们用于在执行 SQL 语句时,将 Java 对象的值设置到 PreparedStatement 中,或者从 ResultSet 或 CallableStatement 中取出值。
 *
 **/
public class PGVectorTypeHandler extends BaseTypeHandler<List<Double>> {

    /**
     * 将 Java 对象的值设置到 PreparedStatement 中
     * java的值写到数据库中去
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<Double> parameter, JdbcType jdbcType) throws SQLException {
        PGobject vector = new PGobject();
        vector.setType("vector");
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int j = 0; j < parameter.size(); j++) {
            if (j > 0) sb.append(",");
            sb.append(parameter.get(j));
        }
        sb.append("]");
        vector.setValue(sb.toString());
        ps.setObject(i, vector);
    }

    /**
     * 从 ResultSet 或 CallableStatement 中取出值
     * 从数据库中的值读取到java对象中
     */
    @Override
    public List<Double> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String vecStr = rs.getString(columnName);
        return parseVector(vecStr);
    }

    /**
     * 从 ResultSet 或 CallableStatement 中取出值
     * 从数据库中的值读取到java对象中
     */
    @Override
    public List<Double> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String vecStr = rs.getString(columnIndex);
        return parseVector(vecStr);
    }

    /**
     * 从 CallableStatement 中取出值
     * 从数据库中的值读取到java对象中
     */
    @Override
    public List<Double> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String vecStr = cs.getString(columnIndex);
        return parseVector(vecStr);
    }

    /**
     * 将值设置到 PreparedStatement 中
     */
    private List<Double> parseVector(String vecStr) {
        if (vecStr == null || vecStr.isEmpty()) return null;
        // 去掉字符串首尾的 [ ] 只获取中间数字
        vecStr = vecStr.substring(1, vecStr.length() - 1);
        String[] parts = vecStr.split(",");
        List<Double> vector = new ArrayList<>(parts.length);
        for (int i = 0; i < parts.length; i++) {
            vector.add(Double.parseDouble(parts[i].trim()));
        }
        return vector;
    }
}

2.Entity实体类中使用上述 类型转换器

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.List;

import xxx.xxx.xxx.PGVectorTypeHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;


@Data
@EqualsAndHashCode(callSuper = false)
@TableName("document_chunk")
public class DocumentChunkEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键id
     */
    @TableId("id")
    private String id;

    /**
     * 所属文档id
     */
    @TableField("document_id")
    private String documentId;

    /**
     * 文档分片内容
     */
    @TableField("chunk")
    private String chunk;

    /**
     * 分片索引
     */
    @TableField("chunk_index")
    private Integer chunkIndex;

    /**
     * 向量
     * <p>
     * typeHandler 配置 字段处理器
     */
    @TableField(value = "vector", typeHandler = PGVectorTypeHandler.class)
    private List<Double> vector;


}

3.sql xml 文件中使用上述 类型解析器

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xxx.xxx.xxx.DocumentChunkMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="xxx.xxx.xxx.DocumentChunkEntity">
        <id column="id" property="id"/>
        <result column="document_id" property="documentId"/>
        <result column="chunk" property="chunk"/>
        <result column="chunk_index" property="chunkIndex"/>
        <!-- XML 配置 字段处理器 -->
        <result column="vector" property="vector" typeHandler="xxx.xxx.xxx.PGVectorTypeHandler"/>
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, document_id, chunk, chunk_index, vector
    </sql>

    <!--
        1.欧几里得距离:欧几里得距离是一种用于度量两点之间“直线距离”的方式。以下查询会返回与给定向量 [3, 3, 3] 欧几里得距离最小的记录:
        SELECT id, embedding FROM items ORDER BY embedding <-> '[3, 3, 3]' LIMIT 1;
        <-> 运算符用于计算向量之间的欧几里得距离,并按照从小到大的顺序排序结果。

        2. 余弦相似度
        余弦相似度用于衡量两个向量的夹角,余弦相似度越接近 1,表示两个向量越相似。以下查询会返回与给定向量 [3, 3, 3] 余弦相似度最高的记录:
        SELECT id, embedding FROM items ORDER BY embedding <=> '[3, 3, 3]' LIMIT 1;
        <=> 运算符用于计算余弦相似度,并按降序排列结果。

        3. 内积
        内积(Dot Product)用于测量两个向量在相同方向上的相似性,常用于推荐系统。以下查询会返回与给定向量 [3, 3, 3] 内积最大的记录
        SELECT id, embedding FROM items ORDER BY embedding <#> '[3, 3, 3]' LIMIT 1;

        错误:operator does not exist: vector <=> real[]
        原因:未正确将向量转换为 VECTOR 类型。
        解决方案:
        确保检索时传入的向量是字符串格式(如 '[1,2,3]')。
        在 SQL 中使用 CAST(#{vector} AS vector) 或 #{vector}::vector 显式转换类型。
    -->

    <!-- 使用pgvector的 -> operator 进行向量搜索 -->
    <select id="searchByVector" resultMap="BaseResultMap">
        SELECT
        <include refid="Base_Column_List"/>
        FROM document_chunk
        ORDER BY vector <![CDATA[<=>]]> #{queryVector}::vector
        LIMIT 5
    </select>
</mapper>

4.mapper 中添加 检索方法

import xxx.xxx.xxx.DocumentChunkEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * <p>
 * 文档分片表 Mapper 接口
 * </p>
 *
 */
@Mapper
public interface DocumentChunkMapper extends BaseMapper<DocumentChunkEntity> {

    /**
     * 向量余弦相似度搜索
     * @param queryVector
     * @return
     */
    List<DocumentChunkEntity> searchByVector(@Param("queryVector") String queryVector);

}

4.插入向量数据

这边使用bge-m3向量模型对文档的片段进行向量处理,然后存储到数据库中

import com.plexpt.chatgpt.ChatGPT;
import com.plexpt.chatgpt.entity.embedding.EmbeddingData;
import com.plexpt.chatgpt.entity.embedding.EmbeddingRequest;
import com.plexpt.chatgpt.entity.embedding.EmbeddingResult;

private final ChatGPT chatGPT = ChatGPT.builder()
		.apiHost("http://localhost:11434/")
		.apiKeyList(ListUtils.of("sk-xxx"))
		.build()
		.init();



EmbeddingRequest embeddingRequest = EmbeddingRequest.builder()
		.model("bge-m3")
		.input(chunks)
		.build();
EmbeddingResult embeddingResult = chatGPT.createEmbeddings(embeddingRequest);
List<EmbeddingData> embeddings = embeddingResult.getData();

List<DocumentChunkEntity> documentChunkEntityList = new ArrayList<>();
for (int i = 0; i < chunks.size(); i++) {
	DocumentChunkEntity documentChunk = new DocumentChunkEntity();
	documentChunk.setId(IdUtils.getSnowflakeNextIdStr());
	documentChunk.setChunk(chunks.get(i));
	documentChunk.setDocumentId(document.getId());
	documentChunk.setChunkIndex(i);
	// 向量 List<Double>
	documentChunk.setVector(embeddings.get(i).getEmbedding());
	documentChunkEntityList.add(documentChunk);
}
documentChunkService.saveBatch(documentChunkEntityList);

5.向量检索

一样使用bge-m3对输入进行向量化,然后检索出近似最高的几个chunk

import com.plexpt.chatgpt.ChatGPT;
import com.plexpt.chatgpt.entity.embedding.EmbeddingData;
import com.plexpt.chatgpt.entity.embedding.EmbeddingRequest;
import com.plexpt.chatgpt.entity.embedding.EmbeddingResult;

private final ChatGPT chatGPT = ChatGPT.builder()
		.apiHost("http://localhost:11434/")
		.apiKeyList(ListUtils.of("sk-xxx"))
		.build()
		.init();



public List<String> search(String prompt) {
	// embedding 请求获取向量
	EmbeddingRequest embeddingRequest = EmbeddingRequest.builder()
			.input(ListUtils.of(prompt))
			.model("bge-m3")
			.build();
	List<Double> embeddings = chatGPT.createEmbeddings(embeddingRequest)
			.getData().get(0).getEmbedding();

	List<DocumentChunkEntity> chunkEntities = documentChunkMapper.searchByVector(embeddings.toString());

	log.info("chunkEntities:{} 数量", chunkEntities.size());

	return chunkEntities.stream().map(DocumentChunkEntity::getChunk).collect(Collectors.toList());
}

6.总结

使用mybatis-plus和postgresql实现向量检索

主要是要自己写一个类型转换器 PGVectorTypeHandler

以及注意 搜索的时候  将 java的List<Double> 类型转换为 字符串的类型 '[1,2,3]'

<think>嗯,用户需要开发一个地图集信息管理的接口,技术栈是Java、Spring Boot、MyBatis-Plus和PostgreSQL。他们希望生成四层代码:Controller、Service、ServiceImpl和Mapper。首先,我得确认标准的Spring Boot应用结构,通常包括这些层次。用户提到使用MyBatis-Plus,所以Mapper层应该用MyBatis-Plus的BaseMapper。 接下来,数据库是在source库的atlas表,需要确认表结构。用户没提供表字段,可能需要假设一些常用字段,比如id、name、description、create_time等。或者可能需要提醒用户补充表结构信息,但根据任务描述,可能用户已经有表结构,所以生成代码时用常见字段作为示例。 然后,各层的职责要明确。Controller处理HTTP请求,Service定义接口,ServiceImpl实现业务逻辑,Mapper负责数据库操作。需要确保注解正确,比如@RestController、@Service、@Mapper。MyBatis-Plus的注解如@TableName和@TableId也要注意。 考虑是否需要分页查询,MyBatis-Plus的Page对象可以方便分页。在Service层可能会有分页查询的方法。另外,实体类需要对应数据库表,字段名和类型要匹配。可能用户使用的PostgreSQL,表名和字段是否大小写敏感?可能需要使用@TableName指定表名,如果表名是atlas,那么实体类名Atlas对应,或者根据实际情况调整。 用户可能需要代码示例,所以生成各层的代码模板,包括基本的CRUD操作。例如,Controller中的增删改查方法,Service调用Mapper的方法。注意MyBatis-Plus的BaseMapper已经提供了大部分CRUD方法,所以ServiceImpl可以直接注入BaseMapper,或者创建自定义的Mapper接口继承BaseMapper。 另外,要检查依赖是否正确,比如pom.xml中的Spring Boot、MyBatis-Plus和PostgreSQL驱动依赖。可能需要提醒用户添加相关依赖,但用户可能已经配置好了。 最后,考虑事务管理,比如在Service层添加@Transactional注解。还有异常处理,可能需要全局异常处理,但用户没提到,暂时不包含在代码中。 总结:生成四层代码的示例,包括实体类、Mapper、Service、ServiceImpl和Controller,使用MyBatis-Plus的特性,确保各层之间的调用正确,并处理基本的CRUD操作。同时,提醒用户根据实际表结构调整实体类字段。</think>我将为您分解地图集信息管理接口的4层代码实现(Entity/Mapper/Service/Controller),基于Spring Boot + MyBatis-Plus技术栈: --- ### 一、Entity层(实体类) ```java // src/main/java/com/example/demo/entity/Atlas.java package com.example.demo.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("atlas") // 对应source数据库中的atlas表 public class Atlas { @TableId(type = IdType.AUTO) private Long id; private String name; private String description; private String coordinateSystem; private String dataFormat; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @Version private Integer version; // 乐观锁字段 } ``` --- ### 二、Mapper层(数据访问) ```java // src/main/java/com/example/demo/mapper/AtlasMapper.java package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.demo.entity.Atlas; public interface AtlasMapper extends BaseMapper<Atlas> { // 继承BaseMapper后已包含基础CRUD方法 // 可在此添加自定义SQL方法 } ``` --- ### 三、Service层(服务接口+实现) #### 3.1 服务接口 ```java // src/main/java/com/example/demo/service/AtlasService.java package com.example.demo.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.example.demo.entity.Atlas; public interface AtlasService extends IService<Atlas> { Page<Atlas> searchByName(String name, Integer pageNum, Integer pageSize); } ``` #### 3.2 服务实现 ```java // src/main/java/com/example/demo/service/impl/AtlasServiceImpl.java package com.example.demo.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.entity.Atlas; import com.example.demo.mapper.AtlasMapper; import com.example.demo.service.AtlasService; import org.springframework.stereotype.Service; @Service public class AtlasServiceImpl extends ServiceImpl<AtlasMapper, Atlas> implements AtlasService { @Override public Page<Atlas> searchByName(String name, Integer pageNum, Integer pageSize) { return this.page( new Page<>(pageNum, pageSize), new LambdaQueryWrapper<Atlas>() .like(Atlas::getName, name) ); } } ``` --- ### 四、Controller层(REST接口) ```java // src/main/java/com/example/demo/controller/AtlasController.java package com.example.demo.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.demo.common.R; import com.example.demo.entity.Atlas; import com.example.demo.service.AtlasService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/atlas") public class AtlasController { @Autowired private AtlasService atlasService; @PostMapping public R<String> add(@RequestBody Atlas atlas) { atlasService.save(atlas); return R.success("添加成功"); } @GetMapping("/{id}") public R<Atlas> detail(@PathVariable Long id) { return R.success(atlasService.getById(id)); } @GetMapping("/search") public R<Page<Atlas>> search( @RequestParam String name, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { return R.success(atlasService.searchByName(name, page, size)); } @PutMapping public R<String> update(@RequestBody Atlas atlas) { atlasService.updateById(atlas); return R.success("更新成功"); } @DeleteMapping("/{id}") public R<String> delete(@PathVariable Long id) { atlasService.removeById(id); return R.success("删除成功"); } } ``` --- ### 补充说明: 1. **数据库配置**:需在`application.yml`中配置PostgreSQL连接 ```yaml spring: datasource: url: jdbc:postgresql://localhost:5432/source username: your_username password: your_password driver-class-name: org.postgresql.Driver ``` 2. **依赖建议**:需确保`pom.xml`包含: ```xml <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> ``` 3. **字段调整**:实体类字段需根据实际表结构修改 是否需要补充说明其他实现细节?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值