如何疾速、即时、合乎直觉地去解决 Nebula Java Client 中的数据解析?读这一篇就够了。

图数据库 NebulaGraph 的论坛和微信群里,有不少用户问及了 Java 客户端数据解析的问题。在本文教你一种简略的形式同返回后果交互,疾速、即时地拿到解析数据。

欢快、洁净的 Java 交互环境

本文最为关键步骤之一,便是用几行代码,筹备一个洁净的交互式 NebulaGraph Java REPL 环境。

多亏了 Java-REPL,咱们能够很不便地(像 iPython 那样)去实时交互地调试、剖析 NebulaGraph Java 客户端。

上面,开始实操。

先用 Docker 镜像筹备环境:

docker pull albertlatacz/java-repldocker run --rm -it \    --network=nebula-net \    -v ~:/root \    albertlatacz/java-repl \    bashapt update -y && apt install ca-certificates -ywget https://dlcdn.apache.org/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.tar.gz --no-check-certificatetar xzvf apache-maven-3.8.6-bin.tar.gzwget https://github.com/vesoft-inc/nebula-java/archive/refs/tags/v3.0.0.tar.gztar xzvf v3.0.0.tar.gzcd nebula-java-3.0.0/../apache-maven-3.8.6/bin/mvn dependency:copy-dependencies../apache-maven-3.8.6/bin/mvn -B package -Dmaven.test.skip=truejava -jar ../javarepl/javarepl.jar

在执行完下面的 java -jar ../javarepl/javarepl.jar 之后,咱们就进入了交互式的 Java Shell(REPL)。咱们不必再做编译、执行、print 这样的慢反馈来调试和钻研咱们的代码了,是不是很不便?

root@a2e26ba62bb6:/javarepl/nebula-java-3.0.0# java -jar ../javarepl/javarepl.jarWelcome to JavaREPL version 428 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111)Type expression to evaluate, :help for more options or press tab to auto-complete.Connected to local instance at http://localhost:43707java> System.out.println("Hello, World!");Hello, World!java>

首先咱们在 java> 提示符下,这些来把必须的类门路和导入:

:cp /javarepl/nebula-java-3.0.0/client/target/client-3.0.0.jar:cp /javarepl/nebula-java-3.0.0/client/target/dependency/fastjson-1.2.78.jar:cp /javarepl/nebula-java-3.0.0/client/target/dependency/slf4j-api-1.7.25.jar:cp /javarepl/nebula-java-3.0.0/client/target/dependency/slf4j-log4j12-1.7.25.jar:cp /javarepl/nebula-java-3.0.0/client/target/dependency/commons-pool2-2.2.jar:cp /javarepl/nebula-java-3.0.0/client/target/dependency/log4j-1.2.17.jarimport com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.vesoft.nebula.ErrorCode;import com.vesoft.nebula.client.graph.NebulaPoolConfig;import com.vesoft.nebula.client.graph.data.CASignedSSLParam;import com.vesoft.nebula.client.graph.data.HostAddress;import com.vesoft.nebula.client.graph.data.ResultSet;import com.vesoft.nebula.client.graph.data.SelfSignedSSLParam;import com.vesoft.nebula.client.graph.data.ValueWrapper;import com.vesoft.nebula.client.graph.net.NebulaPool;import com.vesoft.nebula.client.graph.net.Session;import java.io.UnsupportedEncodingException;import java.util.Arrays;import java.util.List;import java.util.concurrent.TimeUnit;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.lang.reflect.*;

咱们能够从这 Java 环境连贯到 NebulaGraph。在上面的例子中,我用了本人的 graphd 的 IP 和端口作为例子:

NebulaPoolConfig nebulaPoolConfig = new NebulaPoolConfig();nebulaPoolConfig.setMaxConnSize(10);List<HostAddress> addresses = Arrays.asList(new HostAddress("192.168.8.127", 9669));NebulaPool pool = new NebulaPool();pool.init(addresses, nebulaPoolConfig);Session session = pool.getSession("root", "nebula", false);

通过调用 execute 办法取得不太容易懂的 ResultSet 对象

刚接触 NebulaGraph Java 客户端的大家肯定对这个 ResultSet 对象有些愁。别放心,借助咱们的环境,十分钟把它搞通。这里咱们执行一个简略的返回 vertex 顶点的后果看看:

ResultSet resp = session.execute("USE basketballplayer;MATCH (n:player) WHERE n.name==\"Tim Duncan\" RETURN n");

咱们能够参考 ResultSet 的代码:client/graph/data/ResultSet.java

其实能够先不看,跟着教程往下走。一般来说,查问后果都是二维表,ResultSet 针对行和列提供了常见的解决办法。通常,咱们会获取每一行后果,再解析它,而要害的问题是每一个值要怎么解决。

java> resp.isSucceeded()java.lang.Boolean res9 = truejava> resp.rowsSize()java.lang.Integer res16 = 1java> rows = resp.getRows()java.util.ArrayList rows = [Row (  values : [    <Value vVal:Vertex (        vid : <Value sVal:70 6c 61 79 65 72 31 30 30>,        tags : [          Tag (              name : 70 6C 61 79 65 72,              props : {                [B@5264a468 : <Value iVal:42>                [B@496b8e10 : <Value sVal:54 69 6d 20 44 75 6e 63 61 6e>              }            )        ]      )>  ])]    java> row0 = resp.rowValues(0)java.lang.Iterable<com.vesoft.nebula.client.graph.data.ValueWrapper> res10 = ColumnName: [n], Values: [("player100" :player {name: "Tim Duncan", age: 42})]

回到本次 query 语句,它其实是在返回一个 vertex 顶点:

(root@nebula) [basketballplayer]> match (n:player) WHERE n.name == "Tim Duncan" return n+----------------------------------------------------+| n                                                  |+----------------------------------------------------+| ("player100" :player{age: 42, name: "Tim Duncan"}) |+----------------------------------------------------+Got 1 rows (time spent 2116/44373 us)

通过下面的几个办法,咱们其实可能取得这个顶点的值:

v = Class.forName("com.vesoft.nebula.Value")v.getDeclaredMethods()

然而,这个 com.vesoft.nebula.Value 的值的类提供的办法特地原始,这也是让大家犯愁数据解析的起因。所以,在这个教程中最重要的一个带走的教训(除了利用 REPL 之外)就是:非必要不要去取这个原始的类,咱们应该去获得 ValueWrapper 封装之后的值!!!

留神:其实咱们有更轻松地办法,就是用 executeJson 间接取得 JSON string。别放心,会在前面提到,不过这个办法要 2.6 之后才反对。

那么问题来了,如何应用 ValueWrapper 封装呢?其实答案曾经在下面了,大家能够回去看看,resp.rowValues(0) 的类型正是 ValueWrapper 的可迭代对象!

所以,正确打开方式是迭它!迭它!迭它!其实这个就是代码库里的 GraphClientExample 的一部分例子了,咱们把它迭代取出来,放到 wrappedValueList 里缓缓把玩:

import java.util.ArrayList;import java.util.List;List<ValueWrapper> wrappedValueList = new ArrayList<>();for (int i = 0; i < resp.rowsSize(); i++) {    ResultSet.Record record = resp.rowValues(i);    for (ValueWrapper value : record.values()) {        wrappedValueList.add(value);        if (value.isLong()) {            System.out.printf("%15s |", value.asLong());        }        if (value.isBoolean()) {            System.out.printf("%15s |", value.asBoolean());        }        if (value.isDouble()) {            System.out.printf("%15s |", value.asDouble());        }        if (value.isString()) {            System.out.printf("%15s |", value.asString());        }        if (value.isTime()) {            System.out.printf("%15s |", value.asTime());        }        if (value.isDate()) {            System.out.printf("%15s |", value.asDate());        }        if (value.isDateTime()) {            System.out.printf("%15s |", value.asDateTime());        }        if (value.isVertex()) {            System.out.printf("%15s |", value.asNode());        }        if (value.isEdge()) {            System.out.printf("%15s |", value.asRelationship());        }        if (value.isPath()) {            System.out.printf("%15s |", value.asPath());        }        if (value.isList()) {            System.out.printf("%15s |", value.asList());        }        if (value.isSet()) {            System.out.printf("%15s |", value.asSet());        }        if (value.isMap()) {            System.out.printf("%15s |", value.asMap());        }    }    System.out.println();}

上边这些很丑的 if 就是要害了,咱们晓得 query 的返回值可能是多种类型的,他们分为:

  • 图语义的:点、边、门路
  • 数据类型:String,日期,列表,汇合…等等

这里的要害是,咱们要应用 ValueWrapper 为咱们筹备好 asXxx 办法。如果这个值是一个顶点,那么这个 Xxx 就是 Node。同理如果是边的话,这个 Xxx 就是 Relationship。

所以,我给大家看看咱们这个返回点后果的状况下的 asNode() 办法:

java> v = wrappedValueList.get(0)com.vesoft.nebula.client.graph.data.ValueWrapper v = ("player100" :player {name: "Tim Duncan", age: 42})java> v.asNode()com.vesoft.nebula.client.graph.data.Node res16 = ("player100" :player {name: "Tim Duncan", age: 42})java> node = v.asNode()com.vesoft.nebula.client.graph.data.Node node = ("player100" :player {name: "Tim Duncan", age: 42})

顺便说一下,借助于 Java 的反射 reflection,咱们能够在这个交互程序里做相似于 Python 里 dir() 的事件:实时地去获取一个类反对的办法。像这样,省去了查代码的工夫。

java> rClass=Class.forName("com.vesoft.nebula.client.graph.data.ResultSet")java.lang.Class r = class com.vesoft.nebula.client.graph.data.ResultSetjava> rClass.getDeclaredMethods()java.lang.reflect.Method[] res20 = [public java.util.List com.vesoft.nebula.client.graph.data.ResultSet.getColumnNames(), public int com.vesoft.nebula.client.graph.data.ResultSet.rowsSize(), public com.vesoft.nebula.client.graph.data.ResultSet$Record com.vesoft.nebula.client.graph.data.ResultSet.rowValues(int), public java.util.List com.vesoft.nebula.client.graph.data.ResultSet.colValues(java.lang.String), public java.lang.String com.vesoft.nebula.client.graph.data.ResultSet.getErrorMessage(), public boolean com.vesoft.nebula.client.graph.data.ResultSet.isSucceeded(), public int com.vesoft.nebula.client.graph.data.ResultSet.getErrorCode(), public java.lang.String com.vesoft.nebula.client.graph.data.ResultSet.getSpaceName(), public int com.vesoft.nebula.client.graph.data.ResultSet.getLatency(), public com.vesoft.nebula.graph.PlanDescription com.vesoft.nebula.client.graph.data.ResultSet.getPlanDesc(), public java.util.List com.vesoft.nebula.client.graph.data.ResultSet.getRows(), public java.lang.String com.vesoft.nebula.client.graph.data.ResultSet.getComment(), public java.lang.String com.vesoft.nebula.client.graph.data.ResultSet.toString(), public boolean com.vesoft.nebula.client.graph.data.ResultSet.isEmpty(), public java.util.List com.vesoft.nebula.client.graph.data.ResultSet.keys()]

这样:

java> nodeClass=Class.forName("com.vesoft.nebula.client.graph.data.Node")java.lang.Class nodeClass = class com.vesoft.nebula.client.graph.data.Nodejava> nodeClass.getDeclaredMethods()java.lang.reflect.Method[] res20 = [public boolean com.vesoft.nebula.client.graph.data.Node.hasTagName(java.lang.String), public boolean com.vesoft.nebula.client.graph.data.Node.hasLabel(java.lang.String), public java.util.List com.vesoft.nebula.client.graph.data.Node.tagNames(), public java.util.HashMap com.vesoft.nebula.client.graph.data.Node.properties(java.lang.String) throws java.io.UnsupportedEncodingException, public java.util.List com.vesoft.nebula.client.graph.data.Node.labels(), public boolean com.vesoft.nebula.client.graph.data.Node.equals(java.lang.Object), public java.lang.String com.vesoft.nebula.client.graph.data.Node.toString(), public java.util.List com.vesoft.nebula.client.graph.data.Node.values(java.lang.String), public int com.vesoft.nebula.client.graph.data.Node.hashCode(), public com.vesoft.nebula.client.graph.data.ValueWrapper com.vesoft.nebula.client.graph.data.Node.getId(), public java.util.List com.vesoft.nebula.client.graph.data.Node.keys(java.lang.String) throws java.io.UnsupportedEncodingException]

看到这里,大家应该领会到封装了 ValueWrapper 的益处了吧?它提供了不便的合乎直觉的办法,对于 Node 类型来说,它提供了 tagNames()properties()labels() 等等十分好用的办法:

java> node.properties("player")java.util.HashMap res11 = {name="Tim Duncan", age=42}java> node.tagNames()java.util.ArrayList res12 = [player]java> node.labels()java.util.ArrayList res13 = [player]java> node.values("player")java.util.ArrayList res14 = [42, "Tim Duncan"]

咱们这里只展现了顶点数据类型的解决、解析形式(RETURN n),像其余的数据类型比方边(edge)、门路(path)或者天文数据、工夫数据,用这种形式(看有什么办法,再交互地去试试办法怎么用)也是一样的,对吧?

间接返回 JSON 的 executeJson 办法

最初,好消息是:从 v2.6 开始,NebulaGraph 能够间接返回 JSON 的 String 了,咱们下面的纠结也都不是必要的了:

java> String resp_json = session.executeJson("USE basketballplayer;MATCH (n:player) WHERE n.name==\"Tim Duncan\" RETURN n");java.lang.String resp_json = "{   "errors":[      {         "code":0      }   ],   "results":[      {         "spaceName":"basketballplayer",         "data":[            {               "meta":[                  {                     "type":"vertex",                     "id":"player100"                  }               ],               "row":[                  {                     "player.age":42,                     "player.name":"Tim Duncan"                  }               ]            }         ],         "columns":[            "n"         ],         "errors":{            "code":0         },         "latencyInUs":4761      }   ]}"

我置信大家必定比我更善于解决 JSON 的后果了哈~~

论断

  • 如果你有条件(v2.6 及其以上版本)用 JSON,状况会很容易,甚至你都不太须要本文的办法,不过本文可能会让你的交互环境更容易;
  • 如果你不得不和 ResultSet 打交道,记得用 ValueWrapper。因为咱们能够用 asNode()asRelationship()asPath(),封装之后的值比原始的值可恶太多了!

    • 通过 REPL 工具,联合 Java 的 reflection 加上源代码自身,剖析数据的解决将变得异样顺滑。

Happy Graphing!


谢谢你读完本文 (///▽///)

NebulaGraph Desktop,Windows 和 macOS 用户装置图数据库的绿色通道,10s 拉起搞定海量数据的图服务。通道传送门:http://c.nxw.so/9ShUq

想看源码的小伙伴能够返回 GitHub 浏览、应用、(^^)-☆ star 它 -> GitHub;和其余的 NebulaGraph 用户一起交换图数据库技术和利用技能,留下「你的名片」一起游玩呢~