Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An ORM framework for nebula-java #347

Closed
Anyzm opened this issue Sep 9, 2021 · 24 comments
Closed

An ORM framework for nebula-java #347

Anyzm opened this issue Sep 9, 2021 · 24 comments
Labels
community Source: who proposed the issue type/enhancement Type: make the code neat or more efficient

Comments

@Anyzm
Copy link
Contributor

Anyzm commented Sep 9, 2021

使用示例:

@GraphEdge(value = "e_call", srcVertex = VertexEntity.class, dstVertex = VertexEntity.class)
public class ECallEntity  {
    @GraphProperty(value = "user_no1", required = true, propertyTypeEnum = GraphPropertyTypeEnum.GRAPH_EDGE_SRC_ID)
    private String userNo1;
    @GraphProperty(value = "user_no2", required = true, propertyTypeEnum = GraphPropertyTypeEnum.GRAPH_EDGE_DST_ID)
    private String userNo2;
    @GraphProperty(value = "call_out_cnt", dataType = GraphDataTypeEnum.INT)
    private int callOutCnt;
    @GraphProperty(value = "call_in_cnt", dataType = GraphDataTypeEnum.INT)
    private int callInCnt;
    @GraphProperty(value = "call_out_len", dataType = GraphDataTypeEnum.INT)
    private int callOutLen;
    @GraphProperty(value = "call_in_len", dataType = GraphDataTypeEnum.INT)
    private int callInLen;
    @GraphProperty(value = "create_time", dataType = GraphDataTypeEnum.TIMESTAMP, formatter = DateGraphValueFormatter.class)
    private Date createTime;

    @GraphProperty(value = "update_time", dataType = GraphDataTypeEnum.TIMESTAMP, formatter = DateGraphValueFormatter.class)
    private Date updateTime;
}

业务代码层引入框架中的基础Mapper,可以直接操作实体类,
比如保存边:public <S, T, E> int saveEdgeEntities(List entities) throws NebulaException;
比如查询顶点:public List fetchVertexTag(Class vertexClazz, String... vertexIds);
另外查询支持API: public QueryResult executeQuery(GraphQuery query) throws NebulaException;

等等
当然也自己集成了spring-boot-starter
目前已经用于生产,没有明显大问题
不知大家有什么好的建议呢?

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 9, 2021

@GraphVertex(value = "user", keyPolicy = GraphKeyPolicy.string_key)
public class VertexEntity  {
    @GraphProperty(value = "user_no", required = true, propertyTypeEnum = GraphPropertyTypeEnum.GRAPH_VERTEX_ID)
    private String userNo;
    @SensitiveField
    private transient String mobileNo;
    @GraphProperty(value = "mobile_no_encryptx")
    private String mobileNoEncryptx;
    @GraphProperty(value = "mobile_no_md5x")
    private String mobileNoMd5x;
    @GraphProperty(value = "birth_date")
    private String birthDate;
    @GraphProperty(value = "gender")
    private String gender;
    @GraphProperty(value = "marital")
    private String marital;
}

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 9, 2021

类似这样,用注解的方式标注Entity,用基础的Mapper执行查询或者更新操作,查询封装了API的方式操作,基础的Mapper也支持自己手写ngql

@jamieliu1023
Copy link

@klay-ke @Nicole00

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 9, 2021

这个需求看到issues里面很多人提过,类似的:查询结果直接转换为java bean,nebula支持springboot等,都是此类需求。
总结就是:1、ORM框架,2、spring-boot-starter,目前这两者我这边粗略实现了。
风险点:1、框架集成的是2.0.1的nebula-java-client,不知道其余nebula的版本是否适用,
2、查询API没有穷尽,所以版本升级相应的查询API也需要长期维护升级,带来了新的维护成本

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 9, 2021

    public GraphQuery getQueryForEdgeCount(Class labelClazz, LocalDate backtraceDate, int steps, int layer, String... userNos) {
        EdgeQuery edgeQuery = NebulaEdgeQuery.build().goFromSteps(labelClazz, EdgeDirectionEnum.BIDIRECT, steps, userNos);
        NebulaUtils.addBacktraceDate(labelClazz, edgeQuery, backtraceDate, null);
        return edgeQuery.yield(labelClazz, "userNo1", "userNo2").limit(getLayerLimit(layer));
    }

封装查询API的好处:1、面向对象,更加直观立体
2、代码可读性高,动态可复用性强
等等

 EdgeQuery edgeQuery = NebulaEdgeQuery.build().goFromSteps(labelClazz, EdgeDirectionEnum.BIDIRECT, 1, steps, userNos);
        GraphCondition baseCondition = getBaseCondition(userNo, userNos);
        NebulaUtils.addBacktraceDate(labelClazz, edgeQuery, backtraceDate, baseCondition);
        return edgeQuery.yieldDistinct("$$.", VertexEntity.class, fieldArray()).limit(limitSize).pipe().yield().connectAdd(getYieldShortQuery());

目前查询API存在的问题:1、支持的查询语法不是很全面(够我们的业务用了)
2、仅支持fetch prop 和go,不支持索引之类的查询

@sydowma
Copy link

sydowma commented Sep 10, 2021

@Anyzm 发布把

@Nicole00
Copy link
Contributor

感谢@Anyzm 提供的ORM方案,我们目前也在working on it. 目前您这边是包括了插入和查询类的语法封装,请教两个问题:有关于数据更新、删除的封装么?对于返回的查询结果有对应的封装么? 我们可以基于您提供的方案一起打造一个更齐全的ORM。@klay-ke

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 13, 2021

感谢@Anyzm 提供的ORM方案,我们目前也在working on it. 目前您这边是包括了插入和查询类的语法封装,请教两个问题:有关于数据更新、删除的封装么?对于返回的查询结果有对应的封装么? 我们可以基于您提供的方案一起打造一个更齐全的ORM。@klay-ke

查询返回结果有封装:

@ToString
public class QueryResult implements Iterable<QueryResult.Row>, Cloneable, Serializable {

    @Getter
    private List<Row> data = Collections.emptyList();

    public QueryResult() {
    }

    public QueryResult(List<Row> data) {
        if (!CollectionUtils.isEmpty(data)) {
            this.data = data;
        }
    }

    @Override
    public QueryResult clone() {
        List<Row> newList = Lists.newArrayListWithExpectedSize(data.size());
        for (Row datum : data) {
            Row clone = datum.clone();
            newList.add(clone);
        }
        return new QueryResult(newList);
    }

    /**
     * 将查询结果合并
     *
     * @param queryResult
     * @return
     */
    public QueryResult mergeQueryResult(QueryResult queryResult) {
        if (queryResult == null || queryResult.isEmpty()) {
            return this;
        }
        if (this.isEmpty()) {
            this.data = queryResult.getData();
        } else {
            this.data.addAll(queryResult.getData());
        }
        return this;
    }

    public <T> List<T> getEntities(Class<T> clazz) {
        List<T> list = new ArrayList<>();
        for (Row row : this.data) {
            list.add(row.getEntity(clazz));
        }
        return list;
    }

    public int size() {
        return this.data.size();
    }

    public boolean isEmpty() {
        return this.size() == 0;
    }

    public boolean isNotEmpty() {
        return this.size() != 0;
    }

    @Override
    public Iterator<Row> iterator() {
        return this.data.iterator();
    }

    public Stream<Row> stream() {
        Iterable<Row> iterable = this::iterator;
        return StreamSupport.stream(iterable.spliterator(), false);
    }


    @Data
    public static class Row implements Iterable<Map.Entry<String, Object>>, Cloneable, Serializable {

        private Map<String, Object> detail = Collections.emptyMap();

        public Row() {
        }

        public Row(Map<String, Object> detail) {
            if (MapUtils.isNotEmpty(detail)) {
                this.detail = detail;
            }
        }

        @Override
        public Row clone() {
            Map<String, Object> newMap = Maps.newHashMapWithExpectedSize(detail.size());
            newMap.putAll(detail);
            return new Row(newMap);
        }

        public int size() {
            return this.detail.size();
        }

        public Row setProp(String key, Object value) {
            this.detail.put(key, value);
            return this;
        }

        public Map<String, Object> getRowData() {
            return this.detail;
        }


        public Object get(String key) {
            return MapUtils.isEmpty(this.detail) ? null : this.detail.get(key);
        }


        public Date getDate(String key) {
            Long value = this.getLong(key);
            return new Timestamp(value * 1000);
        }

        public String getString(String columnLabel) {
            return getString(columnLabel, null);
        }

        public String getString(String columnLabel, String defaultValue) {
            return (String) this.detail.getOrDefault(columnLabel, defaultValue);
        }

        public Boolean getBoolean(String columnLabel) {
            return getBoolean(columnLabel, null);
        }

        public Boolean getBoolean(String columnLabel, Boolean defaultValue) {
            return (boolean) this.detail.getOrDefault(columnLabel, defaultValue);
        }

        public Short getShort(String columnLabel) {
            return getShort(columnLabel, null);
        }

        public Short getShort(String columnLabel, Short defaultValue) {
            return (short) this.detail.getOrDefault(columnLabel, defaultValue);
        }

        public Integer getInt(String columnLabel) {
            return getInt(columnLabel, null);
        }

        public Integer getInt(String columnLabel, Integer defaultValue) {
            Object obj = this.detail.get(columnLabel);
            if (obj instanceof Long) {
                long l = (Long) obj;
                if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
                    throw new IllegalArgumentException("Bad value for type int: " + l);
                }
                return (int) l;
            } else {
                return (int) this.detail.getOrDefault(columnLabel, defaultValue);
            }
        }

        public Long getLong(String columnLabel) {
            return getLong(columnLabel, null);
        }

        public Long getLong(String columnLabel, Long defaultValue) {
            return (long) this.detail.getOrDefault(columnLabel, defaultValue);
        }

        public Float getFloat(String columnLabel) {
            return getFloat(columnLabel, null);
        }

        public Float getFloat(String columnLabel, Float defaultValue) {
            return (float) this.detail.getOrDefault(columnLabel, defaultValue);
        }

        public Double getDouble(String columnLabel) {
            return getDouble(columnLabel, null);
        }

        public Double getDouble(String columnLabel, Double defaultValue) {
            Object value = this.detail.get(columnLabel);
            if (value instanceof Long) {
                long lValue = (Long) value;
                return (double) lValue;
            } else if (value instanceof Integer) {
                Integer lValue = (Integer) value;
                return (double) lValue;
            } else {
                return (double) this.detail.getOrDefault(columnLabel, 0.0);
            }
        }

        private static <T> T copyMapToBean(Map<String, ?> map, Class<T> clazz) {
            String json = JSONObject.toJSONString(map);
            return JSONObject.parseObject(json, clazz);
        }

        public <T> T getEntity(Class<T> clazz) {
            return copyMapToBean(this.detail, clazz);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || this.getClass() != o.getClass()) {
                return false;
            }
            Row row = (Row) o;
            return Objects.equals(this.detail, row.detail);
        }

        @Override
        public int hashCode() {
            return Objects.hash(this.detail);
        }

        @Override
        public Iterator<Map.Entry<String, Object>> iterator() {
            return this.detail.entrySet().iterator();
        }
    }
}

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 13, 2021

更新删除也有简单封装:

    @Override
    public int execute(String statement) throws NebulaExecuteException {
        ResultSet resultSet = null;
        try {
            log.debug("execute执行nebula,ngql={}", statement);
            resultSet = this.session.execute(statement);
        } catch (Exception e) {
            log.error("更新nebula异常 Thrift rpc call failed: {}", e.getMessage());
            throw new NebulaExecuteException(ErrorCode.E_RPC_FAILURE, e.getMessage(), e);
        }
        if (resultSet.getErrorCode() == ErrorCode.SUCCEEDED) {
            return ErrorCode.SUCCEEDED;
        }
        if (resultSet.getErrorCode() == ErrorCode.E_EXECUTION_ERROR
                && resultSet.getErrorMessage().contains(E_DATA_CONFLICT_ERROR)) {
            //版本冲突,session内部不再打印错误日志,直接抛出自定义的版本异常
            throw new NebulaVersionConflictException(resultSet.getErrorCode(), resultSet.getErrorMessage());
        }
        log.error("更新nebula异常 code:{}, msg:{}, nGql:{} ",
                resultSet.getErrorCode(), resultSet.getErrorMessage(), statement);
        throw new NebulaExecuteException(resultSet.getErrorCode(), resultSet.getErrorMessage());
    }

@klay-ke
Copy link

klay-ke commented Sep 13, 2021

谢谢提供的方案,想问一下你这里是主要基于反射实现的吗?我们后续可能也会看重一下这个方案对于其他客户端的适用性,你是否觉得对于python,go客户端你的大体方案也同样适用?有时间可以一起沟通聊一聊

@wey-gu
Copy link
Contributor

wey-gu commented Sep 13, 2021

谢谢提供的方案,想问一下你这里是主要基于反射实现的吗?我们后续可能也会看重一下这个方案对于其他客户端的适用性,你是否觉得对于python,go客户端你的大体方案也同样适用?有时间可以一起沟通聊一聊

Go 的话社区也还没来得及做 ORM,不过 zhihu 做了一个 --> https://github.com/zhihu/norm
Python 的话社区也还没来得及做哈

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 13, 2021

谢谢提供的方案,想问一下你这里是主要基于反射实现的吗?我们后续可能也会看重一下这个方案对于其他客户端的适用性,你是否觉得对于python,go客户端你的大体方案也同样适用?有时间可以一起沟通聊一聊

其实我对于python和go的了解程度很浅,据我个人的认知,我觉得go也可以按照类似的思路实现,因为go本身和java很类似,都是强类型语言,go的出现本就被预言是用来替代java的。至于python,我觉得可能不是特别适合,因为Python是弱类型语言,更像脚本,所以python可能需要另外的思路实现,可能更有利于用户方便使用。

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 13, 2021

周末我可以将代码提一个PR,或者发布到我的个人仓库

@klay-ke
Copy link

klay-ke commented Sep 13, 2021

是的 主要是python 那先看一下你的PR吧 谢谢

@Anyzm
Copy link
Contributor Author

Anyzm commented Sep 16, 2021

PR提了,可能有两个问题:注释是中文,另外新加的模块没加代码chekStyle,其余基本ok,大佬们看下吧

#349

@klay-ke klay-ke changed the title 目前开发了一款自定义的nebula-java的ORM框架 An ORM framework for nebula-java Sep 28, 2021
@klay-ke klay-ke added the type/enhancement Type: make the code neat or more efficient label Sep 28, 2021
@Sophie-Xie Sophie-Xie added the community Source: who proposed the issue label Sep 28, 2021
@klay-ke
Copy link

klay-ke commented Oct 11, 2021

你好 你在写这套orm框架的时候有写设计文档吗?如果有的话可以提供一下吗,方便review一些,谢谢

@Anyzm
Copy link
Contributor Author

Anyzm commented Oct 11, 2021

你好 你在写这套orm框架的时候有写设计文档吗?如果有的话可以提供一下吗,方便review一些,谢谢

没有写设计文档,只是在脑海中设计了一下。回头我再补充一下设计文档

@klay-ke
Copy link

klay-ke commented Oct 13, 2021

你好 你在写这套orm框架的时候有写设计文档吗?如果有的话可以提供一下吗,方便review一些,谢谢

没有写设计文档,只是在脑海中设计了一下。回头我再补充一下设计文档

再次感谢你对nebula社区做出的贡献,前面因为一些事情耽误了你这边的review,实在不好意思

@Anyzm
Copy link
Contributor Author

Anyzm commented Oct 18, 2021

你好 你在写这套orm框架的时候有写设计文档吗?如果有的话可以提供一下吗,方便review一些,谢谢

没有写设计文档,只是在脑海中设计了一下。回头我再补充一下设计文档

再次感谢你对nebula社区做出的贡献,前面因为一些事情耽误了你这边的review,实在不好意思

没事,反正不着急,设计文档已经补充到PR中了,请查看,如依然有任何问题可随时沟通。

@CPWstatic
Copy link

您好,非常感谢您的贡献。我们内部沟通了一下,java orm可以参考go orm,可以在自己的组织开源,也可以个人开源。

@Anyzm
Copy link
Contributor Author

Anyzm commented Mar 16, 2022

您好,非常感谢您的贡献。我们内部沟通了一下,java orm可以参考go orm,可以在自己的组织开源,也可以个人开源。

好的

@Anyzm
Copy link
Contributor Author

Anyzm commented Apr 11, 2022

在个人仓库已发布,过段时间上传中央仓库,开发者也可以下载源码自己改。
通过捐赠仓库的形式贡献ORM
https://github.com/Anyzm/graph-ocean

@Nicole00
Copy link
Contributor

感谢Anyzm老师提供的orm:https://github.com/nebula-contrib/graph-ocean

@Anyzm
Copy link
Contributor Author

Anyzm commented Dec 29, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community Source: who proposed the issue type/enhancement Type: make the code neat or more efficient
Projects
None yet
Development

No branches or pull requests

8 participants