实践Neo4j之四API

Introduction

Installation

JDK

Neo4j

设置NEO4J_HOME环境变量

1
2
$NEO4J_HOME/bin/neo4j --help
Usage: neo4j { console | start | stop | restart | status | version }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//console模式启动
$NEO4J_HOME/bin/neo4j console
Active database: graph.db
Directories in use:
  home:         /home/angi/software/neo4j
  config:       /home/angi/software/neo4j/conf
  logs:         /home/angi/software/neo4j/logs
  plugins:      /home/angi/software/neo4j/plugins
  import:       /home/angi/software/neo4j/import
  data:         /home/angi/software/neo4j/data
  certificates: /home/angi/software/neo4j/certificates
  run:          /home/angi/software/neo4j/run
Starting Neo4j.
WARNING: Max 10240 open files allowed, minimum of 40000 recommended. See the Neo4j manual.
2017-07-07 08:20:02.907+0000 INFO  ======== Neo4j 3.2.1 ========
2017-07-07 08:20:03.161+0000 INFO  Starting...
2017-07-07 08:20:04.691+0000 INFO  Bolt enabled on 127.0.0.1:7687.
2017-07-07 08:20:10.441+0000 INFO  Started.
2017-07-07 08:20:11.252+0000 INFO  Remote interface available at http://localhost:7474/

Neo4j Web Admin Console

Neo4j Web Admin Console (localhost)

neo4j web admin console

Neo4j Shell

neo4j shell是neo4j自带的客户端,可以连接访问本地或远程neo4j数据库

  • 连接本地
1
2
3
4
5
6
7
8
9
10
11
12
13
$NEO4J_HOME/bin/neo4j-shell -path /home/angi/software/neo4j/data
NOTE: Local Neo4j graph database service at '/home/angi/software/neo4j/data'
Welcome to the Neo4j Shell! Enter 'help' for a list of commands. Please note that neo4j-shell is deprecated and to be replaced by cypher-shell.


neo4j-sh (?)$ start keanu = node(1) return keanu;
+-------+
| keanu |
+-------+
+-------+
0 row
584 ms
neo4j-sh (?)$ 
  • 连接远程

默认没有开启远程shell服务,通过修改neo4j.conf中如下配置开启

1
2
3
4
5
6
## Enable a remote shell server which Neo4j Shell clients can log in to.
dbms.shell.enabled=true
## The network interface IP the shell will listen on (use 0.0.0.0 for all interfaces).
dbms.shell.host=127.0.0.1
## The port the shell will listen on, default is 1337.
dbms.shell.port=1337
1
2
3
4
5
6
7
8
9
10
11
12
13
$NEO4J_HOME/bin/neo4j-shell -host localhost -port 1337
Welcome to the Neo4j Shell! Enter 'help' for a list of commands. Please note that neo4j-shell is deprecated and to be replaced by cypher-shell.
NOTE: Remote Neo4j graph database service 'shell' at port 1337

neo4j-sh (?)$ start keanu = node(1) return keanu;
+----------------------------------------+
| keanu                                  |
+----------------------------------------+
| Node[1]{name:"Keanu Reeves",born:1964} |
+----------------------------------------+
1 row
20 ms
neo4j-sh (?)$ 

Cypher Shell

GraphDatabaseService

schema-less,无模式

1
2
//嵌入式
GraphDatabaseService graphDb = new GraphDatabaseFactory().newEmbeddedDatabase("/tmp/neo4j/")

Node

Vertice,顶点,节点

1
2
3
Node user1 = graphDb.createNode();
Node user2 = graphDb.createNode();
Node movie1 = graphDb.createNode();

Node

id是neo4j自动生成的

Label

可以理解为Node Type

1
2
3
public interface Label {
	java.lang.String name();
}
1
2
3
public enum MyLabels implements Label{
	MOVIES, USERS
}
1
movie1.addLabel(MyLabels.MOVIE);

DynamicLabel

动态创建Label

1
Label label1 = DynamicLabel.label("MOVIES");

RelationShip

Edge,关系,边

RelationShip

id是neo4j自动生成的

RelationShipType

1
2
3
public interface RelationshipType{
public String name();
}
1
2
3
4
public enum MyRelationshipTypes implements RelationshipType {
    IS_FRIEND_OF,
    HAS_SEEN;
}
1
Relationship rel1 = user1.createRelationshipTo(movie1, MyRelationshipTypes.HAS_SEEN);

DynamicRelationshipType

动态创建RelationShipType

1
RelationshipType rt1 = DynamicRelationshipType.withName("IS_FRIEND_OF");

Properties

1
2
user1.setProperty("name", "John Johnson");
rel1.setProperty("stars", 4);

Transaction

1
2
3
4
5
try (Transaction tx = graphDb.beginTx()) {
	user1 = this.graphDb.createNode();
    logger.info("created user:" + user1.getId());
    tx.success();
}

or

1
2
3
4
5
6
7
8
9
10
11
Transaction tx = graphDb.beginTx();
    try {
        user1 = this.graphDb.createNode();
        logger.info("created user:" + user1.getId());
      	//成功
        tx.success();
      	//如果失败则
      	//tx.failure();
    } finally {
      	tx.finish();
}

GlobalGraphOperations

1
2
3
4
5
6
7
8
9
10
try (Transaction tx = graphDb.beginTx()) {
    GlobalGraphOperations ggo = GlobalGraphOperations.at(graphDb);
    for (Relationship r : ggo.getAllRelationships()) {
    	r.delete();
	}
    for (Node n : ggo.getAllNodes()) {
    	n.delete();
    }
	tx.success();
}

Traversal

Using Neoj4 Core Java API

1
2
Node node1 = graphDb.getNodeById(0)
RelationShip rs1 = graphDb.getRelationShipById(1);

Node和RelationShip也自带一些一级检索方法,具体参见前文。

Using Neo4j Traversal API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TraversalDescription traversalMoviesFriendsLike = graphDb.traversalDescription()
				.relationships(MyRelationships.IS_FRIEND_OF).relationships(MyRelationships.HAS_SEEN, Direction.OUTGOING)
				.depthFirst().uniqueness(Uniqueness.NODE_GLOBAL).evaluator(Evaluators.atDepth(2))
				.evaluator(new Evaluator() {
					public Evaluation evaluate(Path path) {
						Node endNode = path.endNode();
						if (!endNode.hasProperty("type") || endNode.getProperty("type").equals("Movie")) {
							return Evaluation.EXCLUDE_AND_PRUNE;
						}
						for (Relationship r : endNode.getRelationships(Direction.INCOMING, MyRelationships.HAS_SEEN)) {
							if (r.getStartNode().equals(userJohn)) {
								return Evaluation.EXCLUDE_AND_CONTINUE;
							}
						}
						return Evaluation.INCLUDE_AND_CONTINUE;
					}
				});

Iterable<Node> moviesFriendsLike = traversalMoviesFriendsLike.traverse(userJohn).nodes();

Index

index

manual index

1
2
3
4
5
6
7
8
9
10
String johnSmithName = "John Smith";
String johnSmithEmail = "jsmith@example.org";
Node personOne = graphDB.createNode();
personOne.setProperty("name", johnSmithName);
personOne.setProperty("email", johnSmithEmail);
IndexManager indexManager = graphDB.index();
//没有则创建索引,有则返回已存在的
Index<Node> userIndex = indexManager.forNodes("users");
//向索引添加数据
userIndex.add(personOne, "email", johnSmithEmail);
1
2
3
4
5
6
7
8
String userEmail = "jsmith@example.org";
//从索引中获取数据
IndexHits<Node> indexHits = userIndex.get("email", userEmail);
//单个
Node loggedOnUserNode = indexHits.getSingle();
if (loggedOnUserNode == null) {
	throw new NoSuchUserException("No user with email " + userEmail + " found");
}
1
2
3
4
5
IndexHits<Node> indexHits = userIndex.get("age", 34);
//多个
for (Node user : indexHits) {
	System.out.println(user.getProperty("name"));
}
1
2
//从索引中删除数据
userIndex.remove(loggedOnUserNode,"email", userEmail);

schema indexing

1
2
Label movieLabel = DynamicLabel.label("MOVIE");
graphDb.schema().indexFor(movieLabel).on("name").create();
1
ResourceIterable<Node> result = graphDb.findNodesByLabelAndProperty(movieLabel, "name", "Michael Collins");

auto-indexing

server mode

1
2
3
4
5
node_auto_indexing=true
relationship_auto_indexing=true

node_keys_indexable=name, dateOfBirth
relationship_keys_indexable=type,name

embedded

1
2
3
4
5
6
7
8
9
Map<String, String> config = new HashMap<String, String>();

config.put( Config.NODE_AUTO_INDEXING, "true" );
config.put( Config.RELATIONSHIP_AUTO_INDEXING, "true" );

config.put( Config.NODE_KEYS_INDEXABLE, "name, dateOfBirth" );
config.put( Config.RELATIONSHIP_KEYS_INDEXABLE, "type, name" );

EmbeddedGraphDatabase graphDb = new EmbeddedGraphDatabase("/var/neo4j/data", config );
1
2
3
AutoIndexer<Node> nodeAutoIndexer = graphDb.index().getNodeAutoIndexer();
IndexHits<Node> nodesWithMatchingName = nodeAutoIndexer.getAutoIndex().get("name", "John");
Node userNode = nodesWithMatchingName.getSingle();

Cypher

1
2
3
4
5
6
7
8
9
10
基本格式:
()-[]-()
([identifier][:label][{map-construct}])-[[identifier]:relationShipType[|relationShipType][*n..m]]-([identifier][:label])
(): 代表node(s)
[]: 代表relationShip
-,->,<-: direction
identifier:标识符,可选,如果不指定,则表示anonymous,当指定时,()可省略
label: nodelable,可选,支持多个,例如:(a:User:Admin)
*n..m: 可选,默认*1,表示一层关系,例如:[:KNOWS*2..5][:KNOWS*0..1][:KNOWS*..5][:KNOWS*]
{map-construct}: 属性过滤,例如:(a {name: 'Andres', sport: 'Brazilian Ju-Jitsu'})

node(s) lookup by ID

1
2
start user=node(1) return user;
start user=node(1,3) return user;

node(s) lookup using an index

1
2
3
4
5
6
7
8
9
10
11
12
//users is index name
start john=node:users(name = "John Johnson") return john
//native Luence query
start john=node:users("name:John Johnson") return john
//native Luence query
start john=node:users("name:John* AND yearOfBirth<1980") return john

//automatice indexing
start john=node:node_auto_index(name = "John Johnson") return john

//schema indexing, USER is a label
match (john:USER) where john.name='John Johnson' return john

getting the results

return nodes, properties, relationship, path and paging.

1
2
3
MATCH (s1:Station)-[r:connected]-(s2:Station)
WHERE s1.name="Euston"
RETURN DISTINCT r.line
1
2
3
4
5
6
ExecutionEngine engine = new ExecutionEngine(graphDb);
String cql = "start user=node(1)" + " match (user)-[:HAS_SEEN]->(movie)" + " return movie;";
ExecutionResult result = engine.execute(cql);
for(Map<String,Object> row : result){
	System.out.println("Row:" + row);
}

create

1
2
3
4
5
6
7
//create node
create newuser{
	name: 'Grace Spencer',
	yearOfBirth: 1982,
	email: 'grace@mycompany.com'
}
return newuser;
1
2
3
4
start john = node:users(name = "John Johnson"), grace = node(10)
//create relationship
create john-[r:IS_FRIEND_OF]->grace
return r;

delete

1
2
start grace = node(10)
delete grace

update

1
2
start john=node:users(name = "John Johnson")
set john.yearOfBirth = 1973

aggregation

聚合

function

函数

piping using the with clause

管道

cypher compatibility

cypher兼容性