Closed Anyzm closed 1 year ago
@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;
}
类似这样,用注解的方式标注Entity,用基础的Mapper执行查询或者更新操作,查询封装了API的方式操作,基础的Mapper也支持自己手写ngql
@klay-ke @Nicole00
这个需求看到issues里面很多人提过,类似的:查询结果直接转换为java bean,nebula支持springboot等,都是此类需求。 总结就是:1、ORM框架,2、spring-boot-starter,目前这两者我这边粗略实现了。 风险点:1、框架集成的是2.0.1的nebula-java-client,不知道其余nebula的版本是否适用, 2、查询API没有穷尽,所以版本升级相应的查询API也需要长期维护升级,带来了新的维护成本
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,不支持索引之类的查询
@Anyzm 发布把
感谢@Anyzm 提供的ORM方案,我们目前也在working on it. 目前您这边是包括了插入和查询类的语法封装,请教两个问题:有关于数据更新、删除的封装么?对于返回的查询结果有对应的封装么? 我们可以基于您提供的方案一起打造一个更齐全的ORM。@klay-ke
感谢@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();
}
}
}
更新删除也有简单封装:
@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());
}
谢谢提供的方案,想问一下你这里是主要基于反射实现的吗?我们后续可能也会看重一下这个方案对于其他客户端的适用性,你是否觉得对于python,go客户端你的大体方案也同样适用?有时间可以一起沟通聊一聊
谢谢提供的方案,想问一下你这里是主要基于反射实现的吗?我们后续可能也会看重一下这个方案对于其他客户端的适用性,你是否觉得对于python,go客户端你的大体方案也同样适用?有时间可以一起沟通聊一聊
Go 的话社区也还没来得及做 ORM,不过 zhihu 做了一个 --> https://github.com/zhihu/norm Python 的话社区也还没来得及做哈
谢谢提供的方案,想问一下你这里是主要基于反射实现的吗?我们后续可能也会看重一下这个方案对于其他客户端的适用性,你是否觉得对于python,go客户端你的大体方案也同样适用?有时间可以一起沟通聊一聊
其实我对于python和go的了解程度很浅,据我个人的认知,我觉得go也可以按照类似的思路实现,因为go本身和java很类似,都是强类型语言,go的出现本就被预言是用来替代java的。至于python,我觉得可能不是特别适合,因为Python是弱类型语言,更像脚本,所以python可能需要另外的思路实现,可能更有利于用户方便使用。
周末我可以将代码提一个PR,或者发布到我的个人仓库
是的 主要是python 那先看一下你的PR吧 谢谢
PR提了,可能有两个问题:注释是中文,另外新加的模块没加代码chekStyle,其余基本ok,大佬们看下吧
你好 你在写这套orm框架的时候有写设计文档吗?如果有的话可以提供一下吗,方便review一些,谢谢
你好 你在写这套orm框架的时候有写设计文档吗?如果有的话可以提供一下吗,方便review一些,谢谢
没有写设计文档,只是在脑海中设计了一下。回头我再补充一下设计文档
你好 你在写这套orm框架的时候有写设计文档吗?如果有的话可以提供一下吗,方便review一些,谢谢
没有写设计文档,只是在脑海中设计了一下。回头我再补充一下设计文档
再次感谢你对nebula社区做出的贡献,前面因为一些事情耽误了你这边的review,实在不好意思
你好 你在写这套orm框架的时候有写设计文档吗?如果有的话可以提供一下吗,方便review一些,谢谢
没有写设计文档,只是在脑海中设计了一下。回头我再补充一下设计文档
再次感谢你对nebula社区做出的贡献,前面因为一些事情耽误了你这边的review,实在不好意思
没事,反正不着急,设计文档已经补充到PR中了,请查看,如依然有任何问题可随时沟通。
您好,非常感谢您的贡献。我们内部沟通了一下,java orm可以参考go orm,可以在自己的组织开源,也可以个人开源。
您好,非常感谢您的贡献。我们内部沟通了一下,java orm可以参考go orm,可以在自己的组织开源,也可以个人开源。
好的
在个人仓库已发布,过段时间上传中央仓库,开发者也可以下载源码自己改。 通过捐赠仓库的形式贡献ORM https://github.com/Anyzm/graph-ocean
感谢Anyzm老师提供的orm:https://github.com/nebula-contrib/graph-ocean
感谢您的来件,邮件已收到。
使用示例:
业务代码层引入框架中的基础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 目前已经用于生产,没有明显大问题 不知大家有什么好的建议呢?