当前位置:网站首页>使用TinkerPop框架对GDB增删改查
使用TinkerPop框架对GDB增删改查
2022-08-02 05:08:00 【大淘宝技术】

本文介绍了使用GDB作为存储,进行服务端开发中需要注意的点。并以TinkerPop框架实现了几个常用的例子,展示GDB操作增删查改时需要注意的地方,以及使用两种方式提交GDB操作的差异。
org.apache.tinkerpop.gremlin.driver.exception.ResponseException: Invalid OpProcessor requested
由于业务中需要处理、存储类图数据,因此选用了GDB作为存储库。GDB官方例子中大多数为直接提交字符串型的脚本(Script)来进行库的CRUD操作的。但:
不少业务逻辑的初始筛选条件类似,后续的操作分散,由于Gremlin语法类似链式操作,直接使用String拼接的话无法在编码中对上一步返回的数据类型进行预判,导致编写逻辑的缺失或错误;
节点写入和边增加的操作依赖复杂的业务判重逻辑,需要通过事务包裹,而官方文档并没有给出事务操作的例子;
TinkerPop提供的部分API在GDB上无法使用,产生奇妙的报错(如SessionedClient、tx、Transaction等)。
另,本人的后端代码能力有待提高,例子的分层仅供参考,欢迎斧正和讨论[手动狗头]。
在综合了各种资料后,目前GDB的最佳maven依赖是:
https://github.com/aliyun/alibabacloud-gdb-java-sdk
使用git clone后maven install一下(maven公共repo没有可用发布包)。
因为:
TinkerPop的官方maven包没有实现对GDB的事务支持
GDB-Java-SDK的1.0.1正式版本没有source和doc
在alibabacloud-gdb-java-driver依赖中,引用了tinkerpop的gremlin-driver和gremlin-core这两个包,版本号是3.4.2;同时有junit.jupiter和slf4j的引用。请注意排包。
以下逻辑就按照GDB-JAVA-SDK内提供的API来进行介绍。
由于GDB支持Script和Bytecode两种提交模式,因此DAO层有以下几种方式生成:
实现Mybatis的接口和模板,实现Script的模板拼接,有人把这个能力实现写了专利(地址:http://www.xjishu.com/zhuanli/55/202210251927.html);--这部分实现非常繁琐,后续补例子
通过StringBuffer拼接脚本,自己通过GDBClient的submitAsync方法提交脚本和参数——注意参数是通过HashMap键来获取的;
public String insert(DataDo dataDO) throws CoreSystemException {//Param Checker...StringBuilder stringBuffer = new StringBuilder();//填充实体LabelstringBuffer.append(String.format("g.addV('%s')", dataDO.getLabel()));//填充实体属性,注意For循环内填充的是根据property为Key的参数预占符for (String property : dataDO.getPropertyCreationParamList()) {stringBuffer.append(String.format(".property('%s', %s)", property, property));}stringBuffer.append(String.format(".property('propId', '%s')", id));stringBuffer.append(String.format(".property('creatorId', %d)", dataDO.getCreatorId()));stringBuffer.append(String.format(".property('area', %d)", dataDO.getArea()));//生成实体idString id = UUID.randomUUID().toString();//设置idstringBuffer.append(String.format(".property(id, '%s')", id));//提交结果,注意带一个HashMap<String, Object>参数作为填充参数。用于最终替换数值List<Result> results = gdbClient.queryResultSync(stringBuffer.toString(),dataDO.getPropertyCreationParamMap());//结果处理dataDO.setId(id);if (results.size() != 1) {return null;}return id;}通过GraphTraversal拼接出操作流,再通过Client编译成Bytecode进行提交。操作流拼接
public GraphTraversal insertTraversal(DataDO dataDO, GraphTraversal traversal) {//Param Checker...//填充实体Labeltraversal = traversal.addV(knowledgePropNodeDO.getLabel());//填充实体属性,注意For循环内填充的是具体的值for (String property : knowledgePropNodeDO.getPropertyCreationParamList()) {traversal = traversal.property(property, knowledgePropNodeDO.getPropertyCreationParamMap().get(property));}traversal = traversal.property("propId", id).property("creatorId", knowledgePropNodeDO.getCreatorId()).property("area", knowledgePropNodeDO.getArea());//生成idString id = UUID.randomUUID().toString();knowledgePropNodeDO.setId(id);traversal = traversal.property(T.id, id);//Do event log append...//返回拼接的操作流,不执行真正的插入return traversal;}
操作流程提交就用GdbClient.submit方法(不带超时,内部调用submitAsync方法直接get)或exec方法(带超时参数,内部调用submitAsync方法死循环查询完成情况)。
▐ 注意

TinkerPop(3.6.0版本或以上)支持自定义DSL,通过注解可以把Gremlin操作二次打包成领域内的语义操作,这时可以通过直接调用DO的一些方法来实现操作流编排,如:
加引用:
<dependency><groupId>org.apache.tinkerpop</groupId><artifactId>gremlin-annotations</artifactId><version>3.6.0</version><!--这里的版本号最好和GdbClient的依赖对齐--></dependency>
加接口:
public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> {public default GraphTraversal<S, Vertex> knows(String personName) {return out("knows").hasLabel("person").has("name", personName);}public default <E2 extends Number> GraphTraversal<S, E2> youngestFriendsAge() {return out("knows").hasLabel("person").values("age").min();}public default GraphTraversal<S, Long> createdAtLeast(int number) {return outE("created").count().is(P.gte(number));}}
引入接口并使用(注意,这里可以使用knows方法了)
SocialTraversalSource social = traversal(SocialTraversalSource.class).withEmbedded(graph);social.V().has("name","marko").knows("josh");
在这里通过事务/非事务的业务操作把一次需要处理的DAO串起来。
在TinkerPop框架中,对client的一次no Session提交,就相当于一次事务处理,在计算中发生错误则整条语句都不生效。
而通过g.tx()方法,可以获取一个Transaction进行多条语句执行的手工管理,通过transaction的begin、commit、close、rollback操作,实现事务的开启、提交、关闭和回滚。
因此,在Service层就按照是否需要事务分为NoSession操作和Session操作。
一般情况下增改删操作以Session操作归类(因为需要根据上一步查询的条件判断操作是否成功,来确定后续是否需要执行),特别是插入操作,需要插入点以及对应的关系边,而在点插入成功以前,并不能确知点的ID,从而不能直接连续操作完成插边——只能通过两段操作来完成。
对于No Session的操作这里不进行赘述,只需要按完整的Script或Bytecode进行提交,获取执行结果即可。
▐ TinkerPop官方食用方法
根据官方实例,在数据库支持事务的情况下,我们可以使用g.tx()获取到一个Transaction对象。然后通过Transaction对象的begin方法得到一个新的GraphTraversalSource对象进行连贯操作。可以多次使用这个对象进行CRUD,并在其中获取返回结果进行逻辑判断。并在最后通过commit或者rollback选择确认或回滚操作。
如下例子:
GraphTraversalSource g = traversal().withRemote(conn);Transaction tx = g.tx();// spawn a GraphTraversalSource from the Transaction. Traversals spawned// from gtx will be essentially be bound to tx//开启事务GraphTraversalSource gtx = tx.begin();try {//添加点1List<Vertex> personResult = gtx.addV("person").property("name", "John").toList();Assert.isTrue(!CollectionUtils.isEmpty(personResult), "Failed in create PersonNode");//添加点2List<Vertex> softwareResult = gtx.addV("software").property("name", "YYDS").toList();Assert.isTrue(!CollectionUtils.isEmpty(personResult), "Failed in create SoftwareNode");//添加点1到点2的关系List<Edge> edgeCreateResult = gtx.addE("create").from(gtx.V(personResult.get(0).id())).to(gtx.V(softwareResult.get(0).id())).toList();//提交结果tx.commit();log.info("Success create Record [{} -create-> {}]", "John", "YYDS");return edgeCreateResult.get(0);} catch (Exception ex) {//回滚事务tx.rollback();throw ex;} finally {tx.close();}
在这个例子中,完整创建一个记录”John create YYDS“,需要对数据库执行两点一边的插入操作,最后的边关系依赖两个点的id,所以前步操作成功后才可执行后步,通过tx生成的gtx实现了一次提交,失败可回滚。
▐ GDB实际用法
但在GDB中,直接按照TinkerPop的官方例子会出现Invalid OpProcessor异常。原因在于GDB不支持g.tx()的API操作[见Gremlin(地址:https://help.aliyun.com/document_detail/102883.html)兼容性,其实提交Script脚本串是可以开启事务的,在SDK的GdbClient中也是这样来实现open、commit、rollback方法的],因此无法直接获取到Transaction对象。
事实上,官方框架中,Transaction对象包裹了一个SessionedClient对象。在这个Client进行操作提交时会加上SessionId以及额外的SessionSettings参数来实现同一事务的操作确认/回滚。
而在GDB的SDK中,实现了一个特别的GdbClient类,用于把Bytecode的事务等提交逻辑改换成部分Script的方式来提交。因此,在GDB中,既可以通过提交”g.tx().open()“这样的script来实现官网ScriptDemo一致的事务开关;也可以用GdbClient自己提供的方法提交事务Bytecode。
这里只举提交Bytecode的方法(Script模式操作逻辑与官方例子一致,只是把每次的操作流程改成用client.submit提交的String就可以)。
//创建Cluster连接池,获取SessionedClientGdbClient.SessionedClient client = GdbCluster.build(gdbAHost).port(gdbAPort).credentials(gdbAUsername, gdbAPassword).serializer(Serializers.GRAPHBINARY_V1D0).create().connect(UUID.randomUUID().toString()).init();//用于存放执行结果StringBuffer result = new StringBuffer();try {//重点:调用batchTransaction方法实现事务提交client.batchTransaction((tx, gtx) -> {try {//重点:调用tx(其实是GdbClient)的exec方法提交Bytecode操作流,并获取执行结果//不能直接使用gtx的toList方法(会出现Invalid OpProcessor异常)List<Result> personResult = tx.exec(gtx.addV("person").property("name", "John"), GQL_TIMEOUNT);Assert.isTrue(!CollectionUtils.isEmpty(personResult), "Failed in create PersonNode");List<Result> softwareResult = tx.exec(gtx.addV("software").property("name", "YYDS"), GQL_TIMEOUNT);Assert.isTrue(!CollectionUtils.isEmpty(personResult), "Failed in create SoftwareNode");//重点:返回的结果与直接执行bytecode不同Vertex person = personResult.get(0).getVertex();Vertex software = softwareResult.get(0).getVertex();List<Result> edgeCreateResult = tx.exec(gtx.addE("create").from(gtx.V(person.id())).to(gtx.V(software.id())), GQL_TIMEOUNT);log.info("Success create Record [{} -create-> {}]", "John", "YYDS");result.append(edgeCreateResult.get(0).getEdge().id());} catch (Exception ex) {//log itthrow ex;}});}finally {//重点:一定要手工关闭clientclient.close();}return result.toString();
团队介绍
我们是大淘宝技术部新品平台技术团队, 依托于淘宝大数据正在建立一套完整的涵盖消费者洞察、宏观及细分市场分析、竞争分析、市场策略研究、产品创新机制等的新品研发和创新孵化平台, 为品牌、商家及行业提供规模化的新品孵化和运营能力, 沉淀新品孵化机制和运营策略, 建立起一套基于大数据驱动的从市场研究、新品研发到新品投放营销的全链路新品运营平台。

本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
边栏推荐
- golang的time包:时间间隔格式化和秒、毫秒、纳秒等时间戳格式输出的方法
- ATM系统
- PSQL function, predicate, CASE expression and set operations
- kubernetes affinity, anti-affinity, taint, tolerance
- Shuttle + Alluxio 加速内存Shuffle起飞
- Detailed explanation of mysql stored procedure
- 100 latest software testing interview questions in 2022, summary of common interview questions and answers
- c语言:查漏补缺(三)
- 网安学习-内网渗透4
- 公司不重视软件测试,新来的阿里P8给我们撰写了测试用例编写规范
猜你喜欢

ERROR 1045 (28000) Access denied for user 'root'@'localhost'Solution

mysql实现按照自定义(指定顺序)排序

How much does a test environment cost? Start with cost and efficiency

在腾讯做外包测试的那些日子.....

Go语学习笔记 - 处理超时问题 - Context使用 从零开始Go语言

为什么4个字节的float要比8个字节的long大呢?

Matlab paper illustration drawing template No. 41 - bubble chart (bubblechart)

12 reasons for MySQL slow query

利用浏览器本地存储 实现记住用户名的功能

What do interview test engineers usually ask?The test supervisor tells you
随机推荐
ERROR 1045 (28000) Access denied for user 'root'@'localhost'Solution
Redis数据库
H5如何实现唤起APP
浏览器的onload事件
MySQL implements sorting according to custom (specified order)
[C language] LeetCode26. Delete duplicates in an ordered array && LeetCode88. Merge two ordered arrays
【C语言】LeetCode26.删除有序数组中的重复项&&LeetCode88.合并两个有序数组
Matlab paper illustration drawing template No. 41 - bubble chart (bubblechart)
51 MCU Peripherals: Infrared Communication
12 reasons for MySQL slow query
去字节跳动自动化测试二面原题(根据录音整理)真实有效 26
提高软件测试能力的方法有哪些?看完这篇文章让你提升一个档次
21天学习挑战赛安排
golang环境详细安装、配置
JUC(二)原子类:CAS、乐观锁、Unsafe和原子类
Redis-----非关系数据库
LeetCode刷题系列 -- 787. K 站中转内最便宜的航班
leetcode每天5题-Day04
Brush LeetCode topic series - 10. Regular expression match
配合蓝牙打印的encoding-indexes.js文件内容:

