在项目里使用dgraph构建了关联图谱,简单写个不是用例的用例记录一下:
dgraph的docker-compose.yml:
version: "3.2"
services:
zero:
image: dgraph/dgraph:latest
volumes:
- /tmp/data:/dgraph
ports:
- 5080:5080
- 6080:6080
restart: on-failure
command: dgraph zero --my=zero:5080
alpha:
image: dgraph/dgraph:latest
volumes:
- /tmp/data:/dgraph
ports:
- 8080:8080
- 9080:9080
restart: on-failure
command: dgraph alpha --my=alpha:7080 --zero=zero:5080 --whitelist 172.17.0.0:172.20.0.0,192.168.1.1 --query_edge_limit 10000000 --lru_mb 4096
ratel:
image: dgraph/dgraph:latest
ports:
- 8000:8000
command: dgraph-ratel
启动:docker-compose up -d
启动之后访问ratel:http://127.0.0.1:8000进入客户端,注意ratel是通过8080端口连接的alpha
控制台可以看出来对dgraph中数据的操作主要是两类,一类是query,一类是mutate,增删改都属于突变操作
添加dgraph中的架构:
name,age,sex表示人的属性(谓词,边),friend表示人与人之间的关系,dgraph节点间的关系是有方向性的 A --> B 表示A有B这样一个朋友,但是B不是A的朋友,~ 表示关系是双向的 A --> B同时存在B --> A
类别,增加Person这个类别,把name,age,sex归属给Person这个类别
<age>: int .
<friend>: uid @reverse .
<name>: string @index(exact) .
<sex>: string .
type <Person> {
age
name
sex
}
存入一条数据:
用作查询的字段需要添加为索引:
查询:
再添加几个数据节点
创建关系:p1和p2是朋友,p2和p3是朋友,p3和p4是朋友。这里是直接看着uid构建的,代码中的话需要记录下返回的id或者查询出满足条件的id再构建关系
创建节点之间的关系,都是边
//rdf方式同时创建多个关系
{
set {
<0x2> <friend> <0x3> .
<0x3> <friend> <0x4> .
}
}
查找p1的朋友
查询p1的朋友,递归查询,深度为4,p1本身是第一层,往下再3个深度
再增加一个节点p5,p5对p1只算半个朋友,关系上可以添加标签
边的方向性:
这时候查询应该还有p1–>p2–>p3–>p4的边,但是加上这个节点后没有了,有点不对。出现这个情况是因为在设置类型的时候friend关系没有设置成list类型,此时从p1节点只能存在一条friend类型的边,与p5建立关联后便与p1断了联系
Springboot中集成dgraph,用的dgraph4j,这个客户端对java而言并不友好,就只是把ratel里面写的语句拼起来通过rpc协议发送给客户端而已,用例和资料也不多,这方面neo4j成熟太多了,不过语句并不是很复杂,其实也可以通过xml封装,类似mysql的mapper文件。
引入包增加配置类
<dependency>
<groupId>io.dgraph</groupId>
<artifactId>dgraph4j</artifactId>
<version>21.03.1</version>
</dependency>
@Configuration
public class DgraphConfig {
@Value("${dgraph.ip}")
private String ip;
@Value("${dgraph.port}")
private String port;
@Bean
public DgraphClient dgraphClient() {
ManagedChannel channel1 = ManagedChannelBuilder
.forAddress(ip, Integer.valueOf(post)).usePlaintext()
.build();
DgraphGrpc.DgraphStub stub1 = DgraphGrpc.newStub(channel1);
//多个集群节点
/* ManagedChannel channel2 = ManagedChannelBuilder
.forAddress("localhost", 9082)
.usePlaintext().build();
DgraphGrpc.DgraphStub stub2 = DgraphGrpc.newStub(channel2);
ManagedChannel channel3 = ManagedChannelBuilder
.forAddress("localhost", 9083)
.usePlaintext().build();
DgraphGrpc.DgraphStub stub3 = DgraphGrpc.newStub(channel3);*/
// DgraphClient dgraphClient = new DgraphClient(stub1, stub2, stub3);
return new DgraphClient(stub1);
}
}
创建Person类
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class Person {
private Integer age;
private String name;
private String sex;
}
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.ByteString;
import io.dgraph.DgraphClient;
import io.dgraph.DgraphProto;
import io.dgraph.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
//代码并没有做测试,但是项目里写过一些,应该不会错太多的吧
@Component
public class Mutate {
@Autowired
DgraphClient dgraphClient;
@Autowired
ObjectMapper objectMapper;
//添加一个person节点
public void addPersion() {
Transaction transaction = null;
try {
Person person = new Person().setAge(18).setName("张三").setSex("M");
transaction = dgraphClient.newTransaction();
DgraphProto.Mutation mutation = DgraphProto.Mutation.newBuilder().setSetJson(ByteString.copyFromUtf8(objectMapper.writeValueAsString(person))).build();
DgraphProto.Response mutate = transaction.mutate(mutation);
//成功时候可以取到存入节点的uid
List<String> uidList = new ArrayList<String>( mutate.getUidsMap().values());
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != transaction) {
transaction.discard();
}
}
}
//查询一个节点
static String queryPersonByName = "query person($name:string){\n" +
" person(func: eq(name,$name)) {\n" +
" name\n" +
" age\n" +
" sex\n" +
"\t}\n" +
"}";
public Person queryPersonByName(String name) {
HashMap vars = new HashMap<String,String>() {{
put("$personName", name);
}};
try {
DgraphProto.Response res = dgraphClient.newTransaction().queryWithVars(queryPersonByName, vars);
return objectMapper.readValue(res.getJson().toStringUtf8(),Person.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//创建两个节点之间的关系
public void createEdgesByRdf() {
//带\n换行的多行就是批量创建
String rdf = "<0x1> <friend> <0x2> .\n<0x2> <friend> <0x3>";
Transaction transaction = null;
try {
transaction = dgraphClient.newTransaction();
DgraphProto.Mutation mutation = DgraphProto.Mutation.newBuilder()
.setSetNquads(ByteString.copyFromUtf8(rdf))
.build();
transaction.mutate(mutation);
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != transaction) {
transaction.discard();
}
}
}
//查询一个人的朋友
static String queryFriendsByName = "query data($name:string,$depth:string){\n" +
" data(func: eq(name,$name)) @recurse(loop: false, depth: $depth) {\n" +
" name\n" +
" age\n" +
" sex\n" +
" friend @facets\n" +
"\t}\n" +
"}";
public JsonNode queryFriendsByName(String name, String depth) {
HashMap param = new HashMap<String,String>() {{
put("$name", name);
put("$depth", depth);
}};
DgraphProto.Response res ;
try {
res = dgraphClient.newTransaction().queryWithVars(queryFriendsByName, param);
String resJsonStr = res.getJson().toStringUtf8();
JsonNode jsonNode = objectMapper.readTree(resJsonStr);
//data是传入的query函数的名称,queryFriendsByName中query的data,如果改成node,那么返回的数据名称就是node
if (null != jsonNode && jsonNode.has("data") && jsonNode.get("data").size() > 0) {
return jsonNode.get("data");
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}