mirror of
https://gitee.com/dromara/MilvusPlus.git
synced 2025-12-07 17:38:24 +08:00
构建MilvusMapper及使用demo
This commit is contained in:
parent
3a16cd3098
commit
0ad0915ab9
20
README.cn.md
20
README.cn.md
@ -82,15 +82,25 @@ public class Face {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
public static void main(String[] args) {
|
@Component
|
||||||
MilvusWrapper<Face> wrapper=new MilvusWrapper();
|
public class FaceMilvusMapper extends MilvusMapper<Face> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ApplicationRunnerTest implements ApplicationRunner {
|
||||||
|
@Autowired
|
||||||
|
private FaceMilvusMapper mapper;
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) throws Exception {
|
||||||
List<Float> vector = Lists.newArrayList(0.1f,0.2f,0.3f);
|
List<Float> vector = Lists.newArrayList(0.1f,0.2f,0.3f);
|
||||||
MilvusResp<Face> resp = wrapper.lambda()
|
MilvusResp<Face> resp = mapper.lambda()
|
||||||
.eq(Face::getPersonId,1l)
|
.eq(Face::getPersonId,1l)
|
||||||
.addVector(vector)
|
.vector(vector)
|
||||||
|
.limit(100l)
|
||||||
.query();
|
.query();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
20
README.md
20
README.md
@ -82,15 +82,25 @@ public class Face {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
public static void main(String[] args) {
|
@Component
|
||||||
MilvusWrapper<Face> wrapper=new MilvusWrapper();
|
public class FaceMilvusMapper extends MilvusMapper<Face> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ApplicationRunnerTest implements ApplicationRunner {
|
||||||
|
@Autowired
|
||||||
|
private FaceMilvusMapper mapper;
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) throws Exception {
|
||||||
List<Float> vector = Lists.newArrayList(0.1f,0.2f,0.3f);
|
List<Float> vector = Lists.newArrayList(0.1f,0.2f,0.3f);
|
||||||
MilvusResp<Face> resp = wrapper.lambda()
|
MilvusResp<Face> resp = mapper.lambda()
|
||||||
.eq(Face::getPersonId,1l)
|
.eq(Face::getPersonId,1l)
|
||||||
.addVector(vector)
|
.vector(vector)
|
||||||
|
.limit(100l)
|
||||||
.query();
|
.query();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
package io.github.javpower.milvus.demo;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import io.github.javpower.milvus.demo.model.Face;
|
||||||
|
import io.github.javpower.milvus.demo.test.FaceMilvusMapper;
|
||||||
|
import io.github.javpower.milvus.plus.model.MilvusResp;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ApplicationRunnerTest implements ApplicationRunner {
|
||||||
|
@Autowired
|
||||||
|
private FaceMilvusMapper mapper;
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) throws Exception {
|
||||||
|
List<Float> vector = Lists.newArrayList(0.1f,0.2f,0.3f);
|
||||||
|
MilvusResp<Face> resp = mapper.lambda()
|
||||||
|
.eq(Face::getPersonId,1l)
|
||||||
|
.vector(vector)
|
||||||
|
.limit(100l)
|
||||||
|
.query();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package io.github.javpower.milvus.demo.test;
|
||||||
|
|
||||||
|
import io.github.javpower.milvus.demo.model.Face;
|
||||||
|
import io.github.javpower.milvus.plus.core.mapper.MilvusMapper;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class FaceMilvusMapper extends MilvusMapper<Face> {
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,21 +0,0 @@
|
|||||||
//package io.github.javpower.milvus.demo.test;
|
|
||||||
//
|
|
||||||
//import com.google.common.collect.Lists;
|
|
||||||
//import io.github.javpower.milvus.plus.core.conditions.MilvusWrapper;
|
|
||||||
//import io.github.javpower.milvus.plus.model.MilvusResp;
|
|
||||||
//
|
|
||||||
//import java.util.List;
|
|
||||||
//
|
|
||||||
//public class TestWrapper {
|
|
||||||
// public static void main(String[] args) {
|
|
||||||
// MilvusWrapper<Face> wrapper=new MilvusWrapper();
|
|
||||||
// List<Float> vector = Lists.newArrayList(0.1f,0.2f,0.3f);
|
|
||||||
// MilvusResp<Face> resp = wrapper.lambda()
|
|
||||||
// .eq(Face::getPersonId,1l)
|
|
||||||
// .addVector(vector)
|
|
||||||
// .query();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
@ -2,4 +2,5 @@ server:
|
|||||||
port: 8131
|
port: 8131
|
||||||
milvus:
|
milvus:
|
||||||
uri: localhost:8999
|
uri: localhost:8999
|
||||||
token: sss
|
token: sss
|
||||||
|
enable: false
|
||||||
@ -19,7 +19,7 @@ public class MilvusCollectionConfig implements ApplicationRunner {
|
|||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
private final MilvusCollectionService milvusCollectionService;
|
private final MilvusCollectionService milvusCollectionService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired(required = false)
|
||||||
public MilvusCollectionConfig(ApplicationContext applicationContext, MilvusCollectionService milvusCollectionService) {
|
public MilvusCollectionConfig(ApplicationContext applicationContext, MilvusCollectionService milvusCollectionService) {
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
this.milvusCollectionService = milvusCollectionService;
|
this.milvusCollectionService = milvusCollectionService;
|
||||||
@ -33,6 +33,8 @@ public class MilvusCollectionConfig implements ApplicationRunner {
|
|||||||
.map(applicationContext::getType)
|
.map(applicationContext::getType)
|
||||||
.toArray(Class<?>[]::new);
|
.toArray(Class<?>[]::new);
|
||||||
// 调用业务处理服务
|
// 调用业务处理服务
|
||||||
milvusCollectionService.performBusinessLogic(Arrays.asList(annotatedClasses));
|
if(milvusCollectionService!=null){
|
||||||
|
milvusCollectionService.performBusinessLogic(Arrays.asList(annotatedClasses));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,7 +2,6 @@ package io.github.javpower.milvus.plus.config;
|
|||||||
|
|
||||||
import io.milvus.v2.client.ConnectConfig;
|
import io.milvus.v2.client.ConnectConfig;
|
||||||
import io.milvus.v2.client.MilvusClientV2;
|
import io.milvus.v2.client.MilvusClientV2;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
/**
|
/**
|
||||||
@ -10,10 +9,17 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
**/
|
**/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class MilvusConfig {
|
public class MilvusConfig {
|
||||||
@Autowired
|
private final MilvusProperties properties;
|
||||||
private MilvusProperties properties;
|
|
||||||
|
public MilvusConfig(MilvusProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public MilvusClientV2 milvusClientV2() {
|
public MilvusClientV2 milvusClientV2() {
|
||||||
|
if(!properties.getEnable()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
ConnectConfig connectConfig = ConnectConfig.builder()
|
ConnectConfig connectConfig = ConnectConfig.builder()
|
||||||
.uri(properties.getUri())
|
.uri(properties.getUri())
|
||||||
.token(properties.getToken())
|
.token(properties.getToken())
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import org.springframework.stereotype.Component;
|
|||||||
@ConfigurationProperties(prefix = "milvus")
|
@ConfigurationProperties(prefix = "milvus")
|
||||||
@Component
|
@Component
|
||||||
public class MilvusProperties {
|
public class MilvusProperties {
|
||||||
|
|
||||||
|
private Boolean enable;
|
||||||
private String uri;
|
private String uri;
|
||||||
private String token;
|
private String token;
|
||||||
}
|
}
|
||||||
@ -1,8 +1,171 @@
|
|||||||
package io.github.javpower.milvus.plus.core;
|
package io.github.javpower.milvus.plus.core;
|
||||||
/**
|
|
||||||
* @author xgc
|
import io.github.javpower.milvus.plus.annotation.MilvusField;
|
||||||
**/
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.invoke.SerializedLambda;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface FieldFunction<T, R> {
|
public interface FieldFunction<T,R> extends Function<T,R>, Serializable {
|
||||||
R apply(T entity);
|
|
||||||
|
//默认配置
|
||||||
|
String defaultSplit = "";
|
||||||
|
Integer defaultToType = 0;
|
||||||
|
|
||||||
|
default String getFieldName() {
|
||||||
|
String methodName = getMethodName();
|
||||||
|
if (methodName.startsWith("get")) {
|
||||||
|
methodName = methodName.substring(3);
|
||||||
|
}
|
||||||
|
return changeFirstCharCase(methodName,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实体类的字段名称(实体声明的字段名称)
|
||||||
|
*/
|
||||||
|
default String getFieldNameLine() {
|
||||||
|
return getFieldName(this, defaultSplit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实体类的字段名称
|
||||||
|
*/
|
||||||
|
|
||||||
|
default String getFieldName(FieldFunction<T, ?> fn) {
|
||||||
|
return getFieldName(fn, defaultSplit, defaultToType);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取实体类的字段名称
|
||||||
|
*
|
||||||
|
* @param split 分隔符,多个字母自定义分隔符
|
||||||
|
*/
|
||||||
|
default String getFieldName(FieldFunction<T, ?> fn, String split) {
|
||||||
|
return getFieldName(fn, split, defaultToType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实体类的字段名称
|
||||||
|
*
|
||||||
|
* @param split 分隔符,多个字母自定义分隔符
|
||||||
|
* @param toType 转换方式,多个字母以大小写方式返回 0.不做转换 1.大写 2.小写
|
||||||
|
*/
|
||||||
|
default String getFieldName(FieldFunction<T, ?> fn, String split, Integer toType) {
|
||||||
|
SerializedLambda serializedLambda = getSerializedLambdaOne(fn);
|
||||||
|
|
||||||
|
// 从lambda信息取出method、field、class等
|
||||||
|
String fieldName = serializedLambda.getImplMethodName().substring("get".length());
|
||||||
|
fieldName = fieldName.replaceFirst(fieldName.charAt(0) + "", (fieldName.charAt(0) + "").toLowerCase());
|
||||||
|
Field field;
|
||||||
|
try {
|
||||||
|
field = Class.forName(serializedLambda.getImplClass().replace("/", ".")).getDeclaredField(fieldName);
|
||||||
|
} catch (ClassNotFoundException | NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
// 从field取出字段名
|
||||||
|
MilvusField collectionField = field.getAnnotation(MilvusField.class);
|
||||||
|
if (collectionField != null && StringUtils.isNotBlank(collectionField.name())) {
|
||||||
|
return collectionField.name();
|
||||||
|
}else {
|
||||||
|
//0.不做转换 1.大写 2.小写
|
||||||
|
switch (toType) {
|
||||||
|
case 1:
|
||||||
|
return fieldName.replaceAll("[A-Z]", split + "$0").toUpperCase();
|
||||||
|
case 2:
|
||||||
|
return fieldName.replaceAll("[A-Z]", split + "$0").toLowerCase();
|
||||||
|
default:
|
||||||
|
return fieldName.replaceAll("[A-Z]", split + "$0");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default String getMethodName() {
|
||||||
|
return getSerializedLambda().getImplMethodName();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Class<?> getFieldClass() {
|
||||||
|
return getReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
default SerializedLambda getSerializedLambda() {
|
||||||
|
Method method;
|
||||||
|
try {
|
||||||
|
method = getClass().getDeclaredMethod("writeReplace");
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
try {
|
||||||
|
return (SerializedLambda) method.invoke(this);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default SerializedLambda getSerializedLambdaOne(FieldFunction<T, ?> fn) {
|
||||||
|
// 从function取出序列化方法
|
||||||
|
Method writeReplaceMethod;
|
||||||
|
try {
|
||||||
|
writeReplaceMethod = fn.getClass().getDeclaredMethod("writeReplace");
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从序列化方法取出序列化的lambda信息
|
||||||
|
boolean isAccessible = writeReplaceMethod.isAccessible();
|
||||||
|
writeReplaceMethod.setAccessible(true);
|
||||||
|
SerializedLambda serializedLambda;
|
||||||
|
try {
|
||||||
|
serializedLambda = (SerializedLambda) writeReplaceMethod.invoke(fn);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
writeReplaceMethod.setAccessible(isAccessible);
|
||||||
|
return serializedLambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Class<?> getReturnType() {
|
||||||
|
SerializedLambda lambda = getSerializedLambda();
|
||||||
|
Class<?> className;
|
||||||
|
try {
|
||||||
|
className = Class.forName(lambda.getImplClass().replace("/", "."));
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
Method method;
|
||||||
|
try {
|
||||||
|
method = className.getMethod(getMethodName());
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return method.getReturnType();
|
||||||
|
}
|
||||||
|
default String changeFirstCharCase(String str, boolean capitalize) {
|
||||||
|
if (str == null || str.isEmpty()) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
char baseChar = str.charAt(0);
|
||||||
|
char updatedChar;
|
||||||
|
if (capitalize) {
|
||||||
|
updatedChar = Character.toUpperCase(baseChar);
|
||||||
|
} else {
|
||||||
|
updatedChar = Character.toLowerCase(baseChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseChar == updatedChar) {
|
||||||
|
return str;
|
||||||
|
} else {
|
||||||
|
char[] chars = str.toCharArray();
|
||||||
|
chars[0] = updatedChar;
|
||||||
|
return new String(chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,357 @@
|
|||||||
|
package io.github.javpower.milvus.plus.core.conditions;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import io.github.javpower.milvus.plus.cache.ConversionCache;
|
||||||
|
import io.github.javpower.milvus.plus.converter.SearchRespConverter;
|
||||||
|
import io.github.javpower.milvus.plus.core.FieldFunction;
|
||||||
|
import io.github.javpower.milvus.plus.model.MilvusResp;
|
||||||
|
import io.github.javpower.milvus.plus.service.MilvusClient;
|
||||||
|
import io.milvus.exception.MilvusException;
|
||||||
|
import io.milvus.v2.common.ConsistencyLevel;
|
||||||
|
import io.milvus.v2.service.vector.request.SearchReq;
|
||||||
|
import io.milvus.v2.service.vector.response.SearchResp;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索构建器内部类,用于构建搜索请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Slf4j
|
||||||
|
public class LambdaSearchWrapper<T> {
|
||||||
|
private ConversionCache<?, ?> conversionCache;
|
||||||
|
private Class<T> entityType;
|
||||||
|
private String collectionName;
|
||||||
|
private String annsField;
|
||||||
|
private int topK;
|
||||||
|
private List<String> filters = new ArrayList<>();
|
||||||
|
private List<List<Float>> vectors = new ArrayList<>();
|
||||||
|
private long offset;
|
||||||
|
private long limit;
|
||||||
|
private int roundDecimal;
|
||||||
|
private String searchParams;
|
||||||
|
private long guaranteeTimestamp;
|
||||||
|
private ConsistencyLevel consistencyLevel;
|
||||||
|
private boolean ignoreGrowing;
|
||||||
|
private MilvusClient client;
|
||||||
|
|
||||||
|
public LambdaSearchWrapper(String collectionName, MilvusClient client,ConversionCache<?, ?> conversionCache,Class<T> entityType) {
|
||||||
|
this.collectionName = collectionName;
|
||||||
|
this.client = client;
|
||||||
|
this.conversionCache=conversionCache;
|
||||||
|
this.entityType=entityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// addVector
|
||||||
|
public LambdaSearchWrapper<T> addVector(List<Float> vector) {
|
||||||
|
vectors.add(vector);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public LambdaSearchWrapper<T> vector(List<Float> vector) {
|
||||||
|
vectors.add(vector);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common comparison operations
|
||||||
|
public LambdaSearchWrapper<T> eq(String fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, "==", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> ne(String fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, "!=", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> gt(String fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, ">", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> ge(String fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, ">=", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> lt(String fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, "<", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> le(String fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, "<=", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range operation
|
||||||
|
public LambdaSearchWrapper<T> between(String fieldName, Object start, Object end) {
|
||||||
|
String filter = String.format("%s >= %s && %s <= %s", fieldName, convertValue(start), fieldName, convertValue(end));
|
||||||
|
filters.add(filter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null check
|
||||||
|
public LambdaSearchWrapper<T> isNull(String fieldName) {
|
||||||
|
filters.add(fieldName + " == null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> isNotNull(String fieldName) {
|
||||||
|
filters.add(fieldName + " != null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In operator
|
||||||
|
public LambdaSearchWrapper<T> in(String fieldName, List<?> values) {
|
||||||
|
String valueList = values.stream()
|
||||||
|
.map(this::convertValue)
|
||||||
|
.collect(Collectors.joining(", ", "[", "]"));
|
||||||
|
filters.add(fieldName + " in " + valueList);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like operator
|
||||||
|
public LambdaSearchWrapper<T> like(String fieldName, String value) {
|
||||||
|
filters.add(fieldName + " like '%" + value + "%'");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON array operations
|
||||||
|
public LambdaSearchWrapper<T> jsonContains(String fieldName, Object value) {
|
||||||
|
filters.add("JSON_CONTAINS(" + fieldName + ", " + convertValue(value) + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> jsonContainsAll(String fieldName, List<?> values) {
|
||||||
|
String valueList = convertValues(values);
|
||||||
|
filters.add("JSON_CONTAINS_ALL(" + fieldName + ", " + valueList + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> jsonContainsAny(String fieldName, List<?> values) {
|
||||||
|
String valueList = convertValues(values);
|
||||||
|
filters.add("JSON_CONTAINS_ANY(" + fieldName + ", " + valueList + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array operations
|
||||||
|
public LambdaSearchWrapper<T> arrayContains(String fieldName, Object value) {
|
||||||
|
filters.add("ARRAY_CONTAINS(" + fieldName + ", " + convertValue(value) + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> arrayContainsAll(String fieldName, List<?> values) {
|
||||||
|
String valueList = convertValues(values);
|
||||||
|
filters.add("ARRAY_CONTAINS_ALL(" + fieldName + ", " + valueList + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> arrayContainsAny(String fieldName, List<?> values) {
|
||||||
|
String valueList = convertValues(values);
|
||||||
|
filters.add("ARRAY_CONTAINS_ANY(" + fieldName + ", " + valueList + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> arrayLength(String fieldName, int length) {
|
||||||
|
filters.add(fieldName + ".length() == " + length);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> eq(FieldFunction<T,?> fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, "==", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> ne(FieldFunction<T,?> fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, "!=", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> gt(FieldFunction<T,?> fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, ">", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> ge(FieldFunction<T,?> fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, ">=", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> lt(FieldFunction<T,?> fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, "<", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> le(FieldFunction<T,?> fieldName, Object value) {
|
||||||
|
return addFilter(fieldName, "<=", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range operation
|
||||||
|
public LambdaSearchWrapper<T> between(FieldFunction<T,?> fieldName, Object start, Object end) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
String filter = String.format("%s >= %s && %s <= %s", fn, convertValue(start), fn, convertValue(end));
|
||||||
|
filters.add(filter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null check
|
||||||
|
public LambdaSearchWrapper<T> isNull(FieldFunction<T,?> fieldName) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
filters.add(fn + " == null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> isNotNull(FieldFunction<T,?> fieldName) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
filters.add(fn + " != null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In operator
|
||||||
|
public LambdaSearchWrapper<T> in(FieldFunction<T,?> fieldName, List<?> values) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
String valueList = values.stream()
|
||||||
|
.map(this::convertValue)
|
||||||
|
.collect(Collectors.joining(", ", "[", "]"));
|
||||||
|
filters.add(fn + " in " + valueList);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like operator
|
||||||
|
public LambdaSearchWrapper<T> like(FieldFunction<T,?> fieldName, String value) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
filters.add(fn + " like '%" + value + "%'");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON array operations
|
||||||
|
public LambdaSearchWrapper<T> jsonContains(FieldFunction<T,?> fieldName, Object value) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
filters.add("JSON_CONTAINS(" + fn + ", " + convertValue(value) + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> jsonContainsAll(FieldFunction<T,?> fieldName, List<?> values) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
String valueList = convertValues(values);
|
||||||
|
filters.add("JSON_CONTAINS_ALL(" + fn + ", " + valueList + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> jsonContainsAny(FieldFunction<T,?> fieldName, List<?> values) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
String valueList = convertValues(values);
|
||||||
|
filters.add("JSON_CONTAINS_ANY(" + fn + ", " + valueList + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array operations
|
||||||
|
public LambdaSearchWrapper<T> arrayContains(FieldFunction<T,?> fieldName, Object value) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
filters.add("ARRAY_CONTAINS(" + fn + ", " + convertValue(value) + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> arrayContainsAll(FieldFunction<T,?> fieldName, List<?> values) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
String valueList = convertValues(values);
|
||||||
|
filters.add("ARRAY_CONTAINS_ALL(" + fn + ", " + valueList + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public LambdaSearchWrapper<T> arrayContainsAny(FieldFunction<T,?> fieldName, List<?> values) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
String valueList = convertValues(values);
|
||||||
|
filters.add("ARRAY_CONTAINS_ANY(" + fn + ", " + valueList + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> arrayLength(FieldFunction<T,?> fieldName, int length) {
|
||||||
|
String fn = getFieldName(fieldName);
|
||||||
|
filters.add(fn + ".length() == " + length);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic operations
|
||||||
|
public LambdaSearchWrapper<T> and(LambdaSearchWrapper<T> other) {
|
||||||
|
filters.add("(" + String.join(" && ", other.filters) + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> or(LambdaSearchWrapper<T> other) {
|
||||||
|
filters.add("(" + String.join(" || ", other.filters) + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> not() {
|
||||||
|
filters.add("not (" + String.join(" && ", filters) + ")");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaSearchWrapper<T> limit(Long limit) {
|
||||||
|
this.setLimit(limit);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public LambdaSearchWrapper<T> topK(Integer topK) {
|
||||||
|
this.setTopK(topK);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
private String convertValue(Object value) {
|
||||||
|
if (value instanceof String) {
|
||||||
|
return "'" + value.toString().replace("'", "\\'") + "'";
|
||||||
|
}
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertValues(List<?> values) {
|
||||||
|
return values.stream()
|
||||||
|
.map(this::convertValue)
|
||||||
|
.collect(Collectors.joining(", ", "[", "]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private LambdaSearchWrapper<T> addFilter(String fieldName, String op, Object value) {
|
||||||
|
filters.add(fieldName + " " + op + " " + convertValue(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
private LambdaSearchWrapper<T> addFilter(FieldFunction<T, ?> fieldFunction, String op, Object value) {
|
||||||
|
String fieldName = getFieldName(fieldFunction);
|
||||||
|
filters.add(fieldName + " " + op + " " + convertValue(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
private String getFieldName(FieldFunction<T, ?> fieldFunction) {
|
||||||
|
return fieldFunction.getFieldName(fieldFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建完整的搜索请求
|
||||||
|
* @return 搜索请求对象
|
||||||
|
*/
|
||||||
|
private SearchReq build() {
|
||||||
|
SearchReq.SearchReqBuilder<?, ?> builder = SearchReq.builder()
|
||||||
|
.collectionName(collectionName)
|
||||||
|
.annsField(annsField)
|
||||||
|
.topK(topK);
|
||||||
|
if (!vectors.isEmpty()) {
|
||||||
|
builder.data(vectors);
|
||||||
|
}
|
||||||
|
String filterStr = filters.stream().collect(Collectors.joining(" && "));
|
||||||
|
if (filterStr != null && !filterStr.isEmpty()) {
|
||||||
|
builder.filter(filterStr);
|
||||||
|
}
|
||||||
|
// Set other parameters as needed
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行搜索
|
||||||
|
* @return 搜索响应对象
|
||||||
|
*/
|
||||||
|
public MilvusResp<T> query() throws MilvusException {
|
||||||
|
SearchReq searchReq = build();
|
||||||
|
log.info("build query param-->{}", JSON.toJSONString(searchReq));
|
||||||
|
SearchResp search = client.client.search(searchReq);
|
||||||
|
MilvusResp<T> tMilvusResp = SearchRespConverter.convertSearchRespToMilvusResp(search, entityType);
|
||||||
|
return tMilvusResp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,380 +0,0 @@
|
|||||||
package io.github.javpower.milvus.plus.core.conditions;
|
|
||||||
|
|
||||||
import io.github.javpower.milvus.plus.annotation.MilvusCollection;
|
|
||||||
import io.github.javpower.milvus.plus.cache.ConversionCache;
|
|
||||||
import io.github.javpower.milvus.plus.cache.FieldFunctionCache;
|
|
||||||
import io.github.javpower.milvus.plus.cache.MilvusCache;
|
|
||||||
import io.github.javpower.milvus.plus.cache.PropertyCache;
|
|
||||||
import io.github.javpower.milvus.plus.converter.SearchRespConverter;
|
|
||||||
import io.github.javpower.milvus.plus.core.FieldFunction;
|
|
||||||
import io.github.javpower.milvus.plus.model.MilvusResp;
|
|
||||||
import io.github.javpower.milvus.plus.service.MilvusClient;
|
|
||||||
import io.github.javpower.milvus.plus.util.SpringUtils;
|
|
||||||
import io.milvus.exception.MilvusException;
|
|
||||||
import io.milvus.v2.common.ConsistencyLevel;
|
|
||||||
import io.milvus.v2.service.vector.request.SearchReq;
|
|
||||||
import io.milvus.v2.service.vector.response.SearchResp;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
/**
|
|
||||||
* @author xgc
|
|
||||||
**/
|
|
||||||
public class MilvusWrapper<T> {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建搜索构建器实例
|
|
||||||
* @param collectionName 集合名称
|
|
||||||
* @return 返回搜索构建器
|
|
||||||
*/
|
|
||||||
public LambdaSearchWrapper<T> lambda() {
|
|
||||||
// 获取实例化的类的类型参数T
|
|
||||||
Type type = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
|
||||||
Class<T> entityType = (Class<T>) type;
|
|
||||||
// 从实体类上获取@MilvusCollection注解
|
|
||||||
MilvusCollection collectionAnnotation = entityType.getAnnotation(MilvusCollection.class);
|
|
||||||
if (collectionAnnotation == null) {
|
|
||||||
throw new IllegalStateException("Entity type " + entityType.getName() + " is not annotated with @MilvusCollection.");
|
|
||||||
}
|
|
||||||
ConversionCache<?, ?> conversionCache = MilvusCache.milvusCache.get(entityType);
|
|
||||||
String collectionName = conversionCache.getCollectionName();
|
|
||||||
// 使用SpringUtil获取MilvusClient实例
|
|
||||||
MilvusClient client = SpringUtils.getBean(MilvusClient.class);
|
|
||||||
// 使用注解中的集合名称创建LambdaSearchWrapper实例
|
|
||||||
return new LambdaSearchWrapper<>(collectionName, client,conversionCache,entityType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 搜索构建器内部类,用于构建搜索请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class LambdaSearchWrapper<T> {
|
|
||||||
private ConversionCache<?, ?> conversionCache;
|
|
||||||
private Class<T> entityType;
|
|
||||||
private String collectionName;
|
|
||||||
private String annsField;
|
|
||||||
private int topK;
|
|
||||||
private List<String> filters = new ArrayList<>();
|
|
||||||
private List<List<Float>> vectors = new ArrayList<>();
|
|
||||||
private long offset;
|
|
||||||
private long limit;
|
|
||||||
private int roundDecimal;
|
|
||||||
private String searchParams;
|
|
||||||
private long guaranteeTimestamp;
|
|
||||||
private ConsistencyLevel consistencyLevel;
|
|
||||||
private boolean ignoreGrowing;
|
|
||||||
private MilvusClient client;
|
|
||||||
|
|
||||||
public LambdaSearchWrapper(String collectionName, MilvusClient client,ConversionCache<?, ?> conversionCache,Class<T> entityType) {
|
|
||||||
this.collectionName = collectionName;
|
|
||||||
this.client = client;
|
|
||||||
this.conversionCache=conversionCache;
|
|
||||||
this.entityType=entityType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// addVector
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> addVector(List<Float> vector) {
|
|
||||||
vectors.add(vector);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common comparison operations
|
|
||||||
public LambdaSearchWrapper<T> eq(String fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, "==", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> ne(String fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, "!=", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> gt(String fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, ">", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> ge(String fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, ">=", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> lt(String fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, "<", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> le(String fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, "<=", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range operation
|
|
||||||
public LambdaSearchWrapper<T> between(String fieldName, Object start, Object end) {
|
|
||||||
String filter = String.format("%s >= %s && %s <= %s", fieldName, convertValue(start), fieldName, convertValue(end));
|
|
||||||
filters.add(filter);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Null check
|
|
||||||
public LambdaSearchWrapper<T> isNull(String fieldName) {
|
|
||||||
filters.add(fieldName + " == null");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> isNotNull(String fieldName) {
|
|
||||||
filters.add(fieldName + " != null");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In operator
|
|
||||||
public LambdaSearchWrapper<T> in(String fieldName, List<?> values) {
|
|
||||||
String valueList = values.stream()
|
|
||||||
.map(this::convertValue)
|
|
||||||
.collect(Collectors.joining(", ", "[", "]"));
|
|
||||||
filters.add(fieldName + " in " + valueList);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like operator
|
|
||||||
public LambdaSearchWrapper<T> like(String fieldName, String value) {
|
|
||||||
filters.add(fieldName + " like '%" + value + "%'");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON array operations
|
|
||||||
public LambdaSearchWrapper<T> jsonContains(String fieldName, Object value) {
|
|
||||||
filters.add("JSON_CONTAINS(" + fieldName + ", " + convertValue(value) + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> jsonContainsAll(String fieldName, List<?> values) {
|
|
||||||
String valueList = convertValues(values);
|
|
||||||
filters.add("JSON_CONTAINS_ALL(" + fieldName + ", " + valueList + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> jsonContainsAny(String fieldName, List<?> values) {
|
|
||||||
String valueList = convertValues(values);
|
|
||||||
filters.add("JSON_CONTAINS_ANY(" + fieldName + ", " + valueList + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array operations
|
|
||||||
public LambdaSearchWrapper<T> arrayContains(String fieldName, Object value) {
|
|
||||||
filters.add("ARRAY_CONTAINS(" + fieldName + ", " + convertValue(value) + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> arrayContainsAll(String fieldName, List<?> values) {
|
|
||||||
String valueList = convertValues(values);
|
|
||||||
filters.add("ARRAY_CONTAINS_ALL(" + fieldName + ", " + valueList + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> arrayContainsAny(String fieldName, List<?> values) {
|
|
||||||
String valueList = convertValues(values);
|
|
||||||
filters.add("ARRAY_CONTAINS_ANY(" + fieldName + ", " + valueList + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> arrayLength(String fieldName, int length) {
|
|
||||||
filters.add(fieldName + ".length() == " + length);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> eq(FieldFunction<T,?> fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, "==", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> ne(FieldFunction<T,?> fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, "!=", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> gt(FieldFunction<T,?> fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, ">", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> ge(FieldFunction<T,?> fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, ">=", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> lt(FieldFunction<T,?> fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, "<", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> le(FieldFunction<T,?> fieldName, Object value) {
|
|
||||||
return addFilter(fieldName, "<=", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range operation
|
|
||||||
public LambdaSearchWrapper<T> between(FieldFunction<T,?> fieldName, Object start, Object end) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
String filter = String.format("%s >= %s && %s <= %s", fn, convertValue(start), fn, convertValue(end));
|
|
||||||
filters.add(filter);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Null check
|
|
||||||
public LambdaSearchWrapper<T> isNull(FieldFunction<T,?> fieldName) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
filters.add(fn + " == null");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> isNotNull(FieldFunction<T,?> fieldName) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
filters.add(fn + " != null");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In operator
|
|
||||||
public LambdaSearchWrapper<T> in(FieldFunction<T,?> fieldName, List<?> values) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
String valueList = values.stream()
|
|
||||||
.map(this::convertValue)
|
|
||||||
.collect(Collectors.joining(", ", "[", "]"));
|
|
||||||
filters.add(fn + " in " + valueList);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like operator
|
|
||||||
public LambdaSearchWrapper<T> like(FieldFunction<T,?> fieldName, String value) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
filters.add(fn + " like '%" + value + "%'");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON array operations
|
|
||||||
public LambdaSearchWrapper<T> jsonContains(FieldFunction<T,?> fieldName, Object value) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
filters.add("JSON_CONTAINS(" + fn + ", " + convertValue(value) + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> jsonContainsAll(FieldFunction<T,?> fieldName, List<?> values) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
String valueList = convertValues(values);
|
|
||||||
filters.add("JSON_CONTAINS_ALL(" + fn + ", " + valueList + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> jsonContainsAny(FieldFunction<T,?> fieldName, List<?> values) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
String valueList = convertValues(values);
|
|
||||||
filters.add("JSON_CONTAINS_ANY(" + fn + ", " + valueList + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array operations
|
|
||||||
public LambdaSearchWrapper<T> arrayContains(FieldFunction<T,?> fieldName, Object value) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
filters.add("ARRAY_CONTAINS(" + fn + ", " + convertValue(value) + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> arrayContainsAll(FieldFunction<T,?> fieldName, List<?> values) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
String valueList = convertValues(values);
|
|
||||||
filters.add("ARRAY_CONTAINS_ALL(" + fn + ", " + valueList + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public LambdaSearchWrapper<T> arrayContainsAny(FieldFunction<T,?> fieldName, List<?> values) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
String valueList = convertValues(values);
|
|
||||||
filters.add("ARRAY_CONTAINS_ANY(" + fn + ", " + valueList + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> arrayLength(FieldFunction<T,?> fieldName, int length) {
|
|
||||||
String fn = getFieldName(fieldName);
|
|
||||||
filters.add(fn + ".length() == " + length);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logic operations
|
|
||||||
public LambdaSearchWrapper<T> and(LambdaSearchWrapper<T> other) {
|
|
||||||
filters.add("(" + String.join(" && ", other.filters) + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> or(LambdaSearchWrapper<T> other) {
|
|
||||||
filters.add("(" + String.join(" || ", other.filters) + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LambdaSearchWrapper<T> not() {
|
|
||||||
filters.add("not (" + String.join(" && ", filters) + ")");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper methods
|
|
||||||
private String convertValue(Object value) {
|
|
||||||
if (value instanceof String) {
|
|
||||||
return "'" + value.toString().replace("'", "\\'") + "'";
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String convertValues(List<?> values) {
|
|
||||||
return values.stream()
|
|
||||||
.map(this::convertValue)
|
|
||||||
.collect(Collectors.joining(", ", "[", "]"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private LambdaSearchWrapper<T> addFilter(String fieldName, String op, Object value) {
|
|
||||||
filters.add(fieldName + " " + op + " " + convertValue(value));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
private LambdaSearchWrapper<T> addFilter(FieldFunction<T, ?> fieldFunction, String op, Object value) {
|
|
||||||
String fieldName = getFieldName(fieldFunction);
|
|
||||||
filters.add(fieldName + " " + op + " " + convertValue(value));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
private String getFieldName(FieldFunction<T, ?> fieldFunction) {
|
|
||||||
FieldFunctionCache<?, ?> fieldFunctionCache = conversionCache.getFieldFunctionCache();
|
|
||||||
String fn = fieldFunctionCache.getFieldName(fieldFunction);
|
|
||||||
PropertyCache propertyCache = conversionCache.getPropertyCache();
|
|
||||||
String fieldName = propertyCache.functionToPropertyMap.get(fn);
|
|
||||||
return fieldName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建完整的搜索请求
|
|
||||||
* @return 搜索请求对象
|
|
||||||
*/
|
|
||||||
private SearchReq build() {
|
|
||||||
SearchReq.SearchReqBuilder<?, ?> builder = SearchReq.builder()
|
|
||||||
.collectionName(collectionName)
|
|
||||||
.annsField(annsField)
|
|
||||||
.topK(topK);
|
|
||||||
if (!vectors.isEmpty()) {
|
|
||||||
builder.data(vectors);
|
|
||||||
}
|
|
||||||
String filterStr = filters.stream().collect(Collectors.joining(" && "));
|
|
||||||
if (filterStr != null && !filterStr.isEmpty()) {
|
|
||||||
builder.filter(filterStr);
|
|
||||||
}
|
|
||||||
// Set other parameters as needed
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行搜索
|
|
||||||
* @return 搜索响应对象
|
|
||||||
* @throws MilvusException 如果搜索执行失败
|
|
||||||
*/
|
|
||||||
public MilvusResp<T> query() throws MilvusException {
|
|
||||||
SearchReq searchReq = build();
|
|
||||||
SearchResp search = client.client.search(searchReq);
|
|
||||||
MilvusResp<T> tMilvusResp = SearchRespConverter.convertSearchRespToMilvusResp(search, entityType);
|
|
||||||
return tMilvusResp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package io.github.javpower.milvus.plus.core.mapper;
|
||||||
|
|
||||||
|
import io.github.javpower.milvus.plus.annotation.MilvusCollection;
|
||||||
|
import io.github.javpower.milvus.plus.cache.ConversionCache;
|
||||||
|
import io.github.javpower.milvus.plus.cache.MilvusCache;
|
||||||
|
import io.github.javpower.milvus.plus.core.conditions.LambdaSearchWrapper;
|
||||||
|
import io.github.javpower.milvus.plus.service.MilvusClient;
|
||||||
|
import io.github.javpower.milvus.plus.util.SpringUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author xgc
|
||||||
|
**/
|
||||||
|
@Slf4j
|
||||||
|
public class MilvusMapper<T> {
|
||||||
|
/**
|
||||||
|
* 创建搜索构建器实例
|
||||||
|
* @return 返回搜索构建器
|
||||||
|
*/
|
||||||
|
public LambdaSearchWrapper<T> lambda() {
|
||||||
|
// 获取实例化的类的类型参数T
|
||||||
|
Type type = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||||
|
Class<T> entityType = (Class<T>) type;
|
||||||
|
// 从实体类上获取@MilvusCollection注解
|
||||||
|
MilvusCollection collectionAnnotation = entityType.getAnnotation(MilvusCollection.class);
|
||||||
|
if (collectionAnnotation == null) {
|
||||||
|
throw new IllegalStateException("Entity type " + entityType.getName() + " is not annotated with @MilvusCollection.");
|
||||||
|
}
|
||||||
|
ConversionCache<?, ?> conversionCache = MilvusCache.milvusCache.get(entityType);
|
||||||
|
String collectionName = conversionCache==null?null:conversionCache.getCollectionName();
|
||||||
|
// 使用SpringUtil获取MilvusClient实例
|
||||||
|
MilvusClient client = SpringUtils.getBean(MilvusClient.class);
|
||||||
|
// 使用注解中的集合名称创建LambdaSearchWrapper实例
|
||||||
|
return new LambdaSearchWrapper<>(collectionName,client,conversionCache,entityType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,18 +2,15 @@ package io.github.javpower.milvus.plus.service;
|
|||||||
|
|
||||||
|
|
||||||
import io.milvus.v2.client.MilvusClientV2;
|
import io.milvus.v2.client.MilvusClientV2;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
/**
|
/**
|
||||||
* @author xgc
|
* @author xgc
|
||||||
**/
|
**/
|
||||||
@Service
|
@Service
|
||||||
public class MilvusClient implements AutoCloseable {
|
public class MilvusClient implements AutoCloseable {
|
||||||
|
@Autowired(required = false)
|
||||||
public final MilvusClientV2 client;
|
public MilvusClientV2 client;
|
||||||
|
|
||||||
public MilvusClient(MilvusClientV2 client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws InterruptedException {
|
public void close() throws InterruptedException {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user