04-SpringBoot集成Nebula Graph
SpringBoot集成Nebula
建议模块
在这里给大家推荐一种方式, 每引入一种新的技术,建议新建一个模块, 来适配这种技术,对外提供接口,在调用的地方应用就可以, 不用搞的到处都是, 防止如果后续替换这种技术, 还要到处修改, 这样的话, 只需要增加一个模块, 对外提供一样的接口, 并替换依赖就可以
添加Maven依赖
<dependency> <groupId>com.vesoft</groupId> <artifactId>client</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency>
配合FastJson一起用比较方便, 为了解决fastjson漏洞问题, 使用1.2.83及其以上版本
增加yml配置
nebula: address[0]: host: 192.168.247.130 port: 9669 username: root password: 123456 reconnect: false space: knowledge_extraction_platform
address[0]: 因为是开发, 所以配置为单机的, 等部署生产的话, 可以增加address[1-..]来完成集群的配置
space: 图空间
新增配置类
主机和端口
package com.jd.knowledgeextractionplatform.nebulagraph.config; import lombok.Data; @Data public class NebulaAddress { private String host; private Integer port; }
解析配置文件的模型
package com.jd.knowledgeextractionplatform.nebulagraph.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import java.util.List; @Data @Configuration @ConfigurationProperties(prefix = "nebula") public class NebulaProperties { private List<NebulaAddress> address; private String username; private String password; private boolean reconnect; private String space; }
基于配置初始化会话和连接池
package com.jd.knowledgeextractionplatform.nebulagraph.config; import com.jd.knowledgeextractionplatform.nebulagraph.constant.NebulaConstant; import com.sun.javafx.binding.StringFormatter; import com.vesoft.nebula.client.graph.NebulaPoolConfig; import com.vesoft.nebula.client.graph.data.HostAddress; import com.vesoft.nebula.client.graph.net.NebulaPool; import com.vesoft.nebula.client.graph.net.Session; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import java.util.stream.Collectors; @Slf4j @Configuration public class NebulaConfig { @Bean public NebulaPool nebulaPool(NebulaProperties nebulaProperties) throws Exception { NebulaPool pool = new NebulaPool(); NebulaPoolConfig nebulaPoolConfig = new NebulaPoolConfig(); nebulaPoolConfig.setMaxConnSize(1000); boolean init = pool.init(nebulaProperties.getAddress().stream().map(d -> new HostAddress(d.getHost(), d.getPort())).collect(Collectors.toList()), nebulaPoolConfig); if (!init){ throw new RuntimeException("NebulaGraph init err !"); }else { log.info("NebulaGraph init Success !"); } return pool; } @Bean @Scope(scopeName = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS) public Session session(NebulaPool nebulaPool, NebulaProperties nebulaProperties) { try { Session session = nebulaPool.getSession(nebulaProperties.getUsername(), nebulaProperties.getPassword(), nebulaProperties.isReconnect()); session.execute(StringFormatter.concat(NebulaConstant.USE, nebulaProperties.getSpace(), NebulaConstant.SEMICOLON).getValue()); return session; } catch (Exception e) { log.error("get nebula session err , {} ", e.toString()); } return null; } }
常用常量(用于返回结果)
package com.jd.knowledgeextractionplatform.nebulagraph.constant; import lombok.AllArgsConstructor; import lombok.Getter; public class NebulaConstant { public static final String USE = "USE "; public static final String SEMICOLON = "; "; public static final String ERROR_CODE = "-1"; @Getter @AllArgsConstructor public enum NebulaJson{ ERRORS("errors"), CODE("code"), MESSAGE("message"), RESULTS("results"), COLUMNS("columns"), DATA("data"), ROW("row"); private String key; } }
图数据库通用返回结果
package com.jd.knowledgeextractionplatform.nebulagraph.result; import lombok.Data; import java.util.List; @Data public class NebulaResult<T> { private Integer code; private String message; private List<T> data; public boolean isSuccessed(){ return code == 0; } }
为了防止每次都解析图数据库返回的结果,增加Template
package com.jd.knowledgeextractionplatform.nebulagraph.template; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.jd.knowledgeextractionplatform.nebulagraph.constant.NebulaConstant; import com.jd.knowledgeextractionplatform.nebulagraph.result.NebulaResult; import com.vesoft.nebula.client.graph.data.ResultSet; import com.vesoft.nebula.client.graph.exception.IOErrorException; import com.vesoft.nebula.client.graph.net.Session; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.*; import java.util.stream.Collectors; @Slf4j @Component public class NebulaTemplate { @Resource Session session; public <T> NebulaResult<T> queryObject(String stmt, Class<T> tClass) { NebulaResult<T> nebulaResult = executeObject(stmt); if (Objects.isNull(nebulaResult.getData())) { return nebulaResult; } Optional.ofNullable(nebulaResult.getData()).ifPresent(data -> nebulaResult.setData(data.stream().map(d -> JSONObject.toJavaObject(((JSONObject) d), tClass)).collect(Collectors.toList()))); return nebulaResult; } public NebulaResult executeObject(String stmt) { JSONObject jsonObject = executeJson(stmt); return JSONObject.toJavaObject(jsonObject, NebulaResult.class); } public JSONObject executeJson(String stmt) { JSONObject restJson = new JSONObject(); try { JSONObject jsonObject = JSON.parseObject(Objects.requireNonNull(session).executeJson(stmt)); JSONObject errors = jsonObject.getJSONArray(NebulaConstant.NebulaJson.ERRORS.getKey()).getJSONObject(0); restJson.put(NebulaConstant.NebulaJson.CODE.getKey(), errors.getInteger(NebulaConstant.NebulaJson.CODE.getKey())); if (errors.getInteger(NebulaConstant.NebulaJson.CODE.getKey()) != 0) { restJson.put(NebulaConstant.NebulaJson.MESSAGE.getKey(), errors.getString(NebulaConstant.NebulaJson.MESSAGE.getKey())); return restJson; } JSONObject results = jsonObject.getJSONArray(NebulaConstant.NebulaJson.RESULTS.getKey()).getJSONObject(0); JSONArray columns = results.getJSONArray(NebulaConstant.NebulaJson.COLUMNS.getKey()); if (Objects.isNull(columns)) { return restJson; } JSONArray data = results.getJSONArray(NebulaConstant.NebulaJson.DATA.getKey()); if (Objects.isNull(data)) { return restJson; } List<JSONObject> resultList = new ArrayList<>(); data.stream().map(d -> (JSONObject) d).forEach(d -> { JSONArray row = d.getJSONArray(NebulaConstant.NebulaJson.ROW.getKey()); JSONObject map = new JSONObject(); for (int i = 0; i < columns.size(); i++) { map.put(columns.getString(i), row.get(i)); } resultList.add(map); }); restJson.put(NebulaConstant.NebulaJson.DATA.getKey(), resultList); } catch (Exception e) { restJson.put(NebulaConstant.NebulaJson.CODE.getKey(), NebulaConstant.ERROR_CODE); restJson.put(NebulaConstant.NebulaJson.MESSAGE.getKey(), e.toString()); log.error("nebula execute err:", e); } return restJson; } }
使用
在Service中引入依赖
为了放置每次都构建nGQL, 虽然灵活, 但是使用困难较大, 有学习成本, 我建立了一个SqlBuildUtils的工具类
扩展
引入SqlBuild
package com.jd.knowledgeextractionplatform.nebulagraph.utils; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.constraints.NotNull; @Data @AllArgsConstructor @NoArgsConstructor public class SqlBuild { private Long id; private String name; private String field; private String values; }
引入SqlBuildUtils
package com.jd.knowledgeextractionplatform.nebulagraph.utils; import com.jd.knowledgeextractionplatform.nebulagraph.annotation.ClassAutoMapping; import com.jd.knowledgeextractionplatform.nebulagraph.annotation.FieldAutoMapping; import com.jd.knowledgeextractionplatform.nebulagraph.model.Edge; import com.jd.knowledgeextractionplatform.nebulagraph.model.ModelAndClass; import com.jd.knowledgeextractionplatform.nebulagraph.model.ProjectModelAndClass; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Locale; @Component public class SqlBuildUtils { private static final String methodPre = "get"; private static final String insertTagSqlTemplate = "insert vertex %s(%s) values \"%s\":(%s);"; private static final String insertEdgeSqlTemplate = "insert edge %s(%s) values \"%s\" -> \"%s\":(%s);"; private static final String deleteTagSqlTemplate = "delete tag %s from \"%s\";"; private static final String deleteEdgeSqlTemplate = "delete edge %s \"%s\" -> \"%s\"@0;"; private static final String deleteVertexSqlTemplate = "delete vertex \"%s\" with edge;"; private static final String updateVertexSqlTemplate = "update vertex on %s \"%s\" set %s;"; private static final String createTag = "create tag if not exists %s(id int64, name String,description String) comment = \"%s\";"; private static final String insertDefaultVertex = "insert vertex if not exists %s(id,name,description) values \"%s\":(%s,\"%s\",\"%s\");"; private static final String insertDefaultEdge = "insert edge %s(id,name) values \"%s\" -> \"%s\":(%s,\"%s\");"; private static final String dropTagSqlTemplate = "drop tag if exists %s;"; private static final String dropEdgeSqlTemplate = "drop edge if exists %s;"; private static final String createEdgeSqlTemplate = "create edge if not exists %s(id int64,name String) comment = \"%s\""; public static <T> String buildInsert(T t) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { SqlBuild tag = parse(t); return String.format(insertTagSqlTemplate, tag.getName(), tag.getField(), tag.getId(), tag.getValues()); } public static <T extends Edge> String buildEdge(T t) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { SqlBuild edge = parse(t); return String.format(insertEdgeSqlTemplate, edge.getName(), edge.getField(), t.getLeftVid(), t.getRightVid(), edge.getValues()); } public static <T> String deleteTag(T t) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { SqlBuild tag = parse(t); return String.format(deleteTagSqlTemplate,tag.getName(),tag.getId()); } public static String deleteEdge(String edgeName, Long pid, Long subId){ return String.format(deleteEdgeSqlTemplate,edgeName,pid,subId); } public static <T> String deleteVertex(Long id){ return String.format(deleteVertexSqlTemplate,id); } public static <T> String updateVertex(T t) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { Class<?> clazz = t.getClass(); ClassAutoMapping annotation = clazz.getAnnotation(ClassAutoMapping.class); String tagName = annotation.value(); Field[] declaredFields = clazz.getDeclaredFields(); StringBuilder set = new StringBuilder(); Long id = null; for (int i = 0; i < declaredFields.length; i++) { Field declaredField = declaredFields[i]; // 获取属性名称 String name = declaredField.getName(); // 获取自定义注解 FieldAutoMapping FieldAutoMapping autoMapping = declaredField.getAnnotation(FieldAutoMapping.class); if(null == autoMapping){ continue; } String methodName = autoMapping.method(); String type = autoMapping.type(); Method getMethod = clazz.getDeclaredMethod(methodName); Object value = getMethod.invoke(t); Object valueFormat = format(value, type); if ("id".equals(name)) { id = (Long) value; continue; } set.append(name).append("=").append(valueFormat); if (i != declaredFields.length - 1) { set.append(","); } } return String.format(updateVertexSqlTemplate,tagName,id,set.toString()); } public static String updateDefault(String tagName,Long vid,String name, String description){ String setCall = "name="+format(name,"String")+",description="+format(description,"String"); return String.format(updateVertexSqlTemplate,tagName,vid,setCall); } private static <T> SqlBuild parse(T t) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class<?> clazz = t.getClass(); ClassAutoMapping annotation = clazz.getAnnotation(ClassAutoMapping.class); String tagName = annotation.value(); Field[] declaredFields = clazz.getDeclaredFields(); StringBuilder filedString = new StringBuilder(); StringBuilder valueString = new StringBuilder(); Long id = null; for (int i = 0; i < declaredFields.length; i++) { Field declaredField = declaredFields[i]; // 获取属性名称 String name = declaredField.getName(); // 获取自定义注解 FieldAutoMapping FieldAutoMapping autoMapping = declaredField.getAnnotation(FieldAutoMapping.class); if(null == autoMapping){ continue; } String methodName = autoMapping.method(); String type = autoMapping.type(); Method getMethod = clazz.getDeclaredMethod(methodName); Object value = getMethod.invoke(t); filedString.append(name); Object valueFormat = format(value, type); if ("id".equals(name)) { id = (Long) value; } valueString.append(valueFormat); if (i != declaredFields.length - 1) { filedString.append(","); valueString.append(","); } } return new SqlBuild(id,tagName,filedString.toString(),valueString.toString()); } public static String createTag(String tagName,String comment){ return String.format(createTag,tagName,comment); } public static String createEdge(String edgeName,String comment){ return String.format(createEdgeSqlTemplate,edgeName,comment); } public static String insertDefaultVertex(String tagName,Long vid,String name,String description){ return String.format(insertDefaultVertex,tagName,vid,vid,name,description); } public static String dropTag(String tagName){ return String.format(dropTagSqlTemplate,tagName); } public static String dropEdge(String edgeName){ return String.format(dropEdgeSqlTemplate,edgeName); } public static String insertDefaultEdge(String edgeCode,Long pid,Long id,Long preId,String edgeName){ return String.format(insertDefaultEdge,edgeCode,pid,id,preId,edgeName); } /** * 首字母转大写 * * @param name 字符串 * @return 转大写 */ private static String firstCharacterToUppercase(String name) { String startName = name.substring(0, 1); String endName = name.substring(1); return startName.toUpperCase(Locale.ROOT) + endName; } private static Object format(Object value, String type) { if ("String".equals(type)) { String s = value + ""; return "\"" + s + "\""; } return value; } public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { // 测试点生成 ModelAndClass modelAndClass = new ModelAndClass(); modelAndClass.setId(123123123123L); modelAndClass.setName("模式"); modelAndClass.setPid(132313231313123L); modelAndClass.setDescription("描述"); String s = buildInsert(modelAndClass); System.out.println(s); // 测试边生成 ProjectModelAndClass projectModelAndClass = new ProjectModelAndClass(); projectModelAndClass.setStartId(181314024706908160L); projectModelAndClass.setEndId(123123123123L); projectModelAndClass.setLeftVid("181314024706908160"); projectModelAndClass.setRightVid("123123123123"); String s1 = buildEdge(projectModelAndClass); System.out.println(s1); } }
引入注解
类注解
package com.jd.knowledgeextractionplatform.nebulagraph.annotation; import java.lang.annotation.*; @Documented @Target({ElementType.TYPE,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ClassAutoMapping { String value() default ""; }
字段注解
package com.jd.knowledgeextractionplatform.nebulagraph.annotation; import java.lang.annotation.*; @Documented @Target({ElementType.TYPE,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface FieldAutoMapping { String method() default ""; String type(); }
引入边的通用属性
package com.jd.knowledgeextractionplatform.nebulagraph.model; import lombok.Data; @Data public class Edge { private String leftVid; private String rightVid; }
使用方式
节点使用方式
在节点类上增加注解
package com.jd.knowledgeextractionplatform.nebulagraph.model; import com.jd.knowledgeextractionplatform.nebulagraph.annotation.ClassAutoMapping; import com.jd.knowledgeextractionplatform.nebulagraph.annotation.FieldAutoMapping; import lombok.Data; import java.io.Serializable; @Data @ClassAutoMapping("modelandclass") public class ModelAndClass implements Serializable { /** * id vid */ @FieldAutoMapping(method = "getId", type = "Long") private Long id; /** * 父级ID */ @FieldAutoMapping(method = "getPid", type = "Long") private Long pid;/** * 名称 */ @FieldAutoMapping(method = "getName", type = "String") private String name; /** * 描述 */ @FieldAutoMapping(method = "getDescription", type = "String") private String description; }
边使用方式
增加注解并继承边的通用属性
package com.jd.knowledgeextractionplatform.nebulagraph.model; import com.jd.knowledgeextractionplatform.nebulagraph.annotation.ClassAutoMapping; import com.jd.knowledgeextractionplatform.nebulagraph.annotation.FieldAutoMapping; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor @ClassAutoMapping("project_attributeandrelationship") public class ProjectAttributeAndRelationship extends Edge implements Serializable { @FieldAutoMapping(method = "getStartId",type = "Long") private Long startId; @FieldAutoMapping(method = "getEndId",type = "Long") private Long endId; }
剩余的在Controller中传入模型的数据就可以了
其他Demo[直接写NGQL]
@RestController public class TestController { @Resource NebulaTemplate nebulaTemplate; @GetMapping("/addVertex") public Object addJSON() throws IOErrorException { String sql = "insert vertex team(team_name, persion_num) values \"team_2\":(\"team_2\", 43);"; NebulaResult nebulaResult = nebulaTemplate.executeObject(sql); return nebulaResult; } @GetMapping("/findVertex") public Object findJson2() throws IOErrorException { String sql = "lookup on team yield id(vertex) AS id,properties(vertex).persion_num AS persion_num,properties(vertex).team_name AS team_name;"; NebulaResult<Info> infoNebulaResult = nebulaTemplate.queryObject(sql, Info.class); return infoNebulaResult; } }
查询的语句, 要自己写, 这个SqlBuildUtils只能统一解决节点tag和边edge的增删改问题
分类:
Nebula Graph
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2021-08-23 11-SpringCloud Hystrix
2021-08-23 10-SpringCloud OpenFeign