前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >04-SpringBoot集成Nebula Graph

04-SpringBoot集成Nebula Graph

作者头像
彼岸舞
发布2022-08-24 08:32:10
7530
发布2022-08-24 08:32:10
举报
文章被收录于专栏:java开发的那点事

SpringBoot集成Nebula

建议模块

在这里给大家推荐一种方式, 每引入一种新的技术,建议新建一个模块, 来适配这种技术,对外提供接口,在调用的地方应用就可以, 不用搞的到处都是, 防止如果后续替换这种技术, 还要到处修改, 这样的话, 只需要增加一个模块, 对外提供一样的接口, 并替换依赖就可以

添加Maven依赖

代码语言:javascript
复制
<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配置

代码语言:javascript
复制
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: 图空间

新增配置类

主机和端口

代码语言:javascript
复制
package com.jd.knowledgeextractionplatform.nebulagraph.config;

import lombok.Data;

@Data
public class NebulaAddress {
    private String host;
    private Integer port;
}

解析配置文件的模型

代码语言:javascript
复制
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;
}

基于配置初始化会话和连接池

代码语言:javascript
复制
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;
    }
}

常用常量(用于返回结果)

代码语言:javascript
复制
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;
    }
}

图数据库通用返回结果

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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);
    }

}

引入注解

类注解
代码语言:javascript
复制
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 "";

}
字段注解
代码语言:javascript
复制
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();

}

引入边的通用属性

代码语言:javascript
复制
package com.jd.knowledgeextractionplatform.nebulagraph.model;

import lombok.Data;

@Data
public class Edge {

    private String leftVid;

    private String rightVid;

}

使用方式

节点使用方式

在节点类上增加注解

代码语言:javascript
复制
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;

}
边使用方式

增加注解并继承边的通用属性

代码语言:javascript
复制
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]

代码语言:javascript
复制
@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的增删改问题

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringBoot集成Nebula
    • 建议模块
      • 添加Maven依赖
        • 增加yml配置
          • 新增配置类
            • 主机和端口
            • 解析配置文件的模型
            • 基于配置初始化会话和连接池
            • 常用常量(用于返回结果)
            • 图数据库通用返回结果
            • 为了防止每次都解析图数据库返回的结果,增加Template
          • 使用
            • 扩展
              • 引入SqlBuild
              • 引入SqlBuildUtils
              • 引入注解
              • 引入边的通用属性
              • 使用方式
            • 其他Demo[直接写NGQL]
            相关产品与服务
            数据库
            云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档