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的增删改问题