bboyjing's blog

Neo4j学习笔记四【索引】

与传统关系型数据库一样,为了更快地遍历出需要的节点,索引是必不可少的。

显示地创建索引

还是使用上几章创建的数据为例,假定现在给USERS节点添加email属性,并且期望电子邮件地址是唯一的,然后以用户的邮箱作为索引。在命令行输入index --indexes可以列出索引项,该索引为legacy index,如果可能请只使用下面的schema index同时避免legacy index。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args){
File file = new File("/opt/neo4j-community-3.0.3/data/databases/graph.db");
GraphDatabaseService graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(file);
try(Transaction tx = graphDB.beginTx()){
String johnEmail = "john@example.org";
String kateEmail = "kage@example.org";
String jackEmail = "jack@example.org";
//获取用户节点,并且设置email属性
Node userJohn = graphDB.getNodeById(0l);
userJohn.setProperty("email", johnEmail);
Node userKate = graphDB.getNodeById(1l);
userKate.setProperty("email", kateEmail);
Node userJack = graphDB.getNodeById(2l);
userJack.setProperty("email", jackEmail);
//获取索引管理器
IndexManager indexManager = graphDB.index();
//查找名称为users的索引,若不存在则创建一个
Index<Node> userIndex =indexManager.forNodes("users");
//以email为key,为users索引添加具体的索引项目
userIndex.add(userJohn, "email", johnEmail);
userIndex.add(userKate, "email", kateEmail);
userIndex.add(userJack, "email", jackEmail);
tx.success();
}
graphDB.shutdown();
}

通过邮件地址索引查找用户

用户的邮箱已经被索引,现在我们就通过索引来查找USERS节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try(Transaction tx = graphDB.beginTx()){
//获取索引管理器
IndexManager indexManager = graphDB.index();
//查找名称为users的索引
Index<Node> userIndex =indexManager.forNodes("users");
//获取索引命中的结果集
IndexHits<Node> indexHits = userIndex.get("email", "john@example.org");
/**
* 获取命中的节点,且要求命中节点只有一个,如果有多个则抛出NoSuchElementException("More than one element in...")
* 若索引命中的结果集中不只一条是,秩序遍历indexHits即可
* for(Node user : indexHits){
* System.out.println(user.getProperty("name"));
* }
*/
Node loggedOnUserNode = indexHits.getSingle();
if(loggedOnUserNode != null){
System.out.println(loggedOnUserNode.getProperty("name"));
}
tx.success();
}

手动创建索引的修改

上面我们以email为值手动为用户创建了索引,如果此时用户需要修改email,原索引就失效了。同时Neo4j并不会自动地修改索引,而且Index<Node>接口也没有提供修改索引的方法,所以解决的方法就是将原索引先删除,然后再添加新的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try(Transaction tx = graphDB.beginTx()){
String johnEmail = "john@example.org";
String updateJohnEmail = "john@new.example.org";
//获取索引管理器
IndexManager indexManager = graphDB.index();
//查找名称为users的索引
Index<Node> userIndex =indexManager.forNodes("users");
//获取索引命中的结果集
IndexHits<Node> indexHits = userIndex.get("email", johnEmail);
Node loggedOnUserNode = indexHits.getSingle();
if(loggedOnUserNode != null){
//删除索引
userIndex.remove(loggedOnUserNode, "email", johnEmail);
//更新
loggedOnUserNode.setProperty("email",updateJohnEmail);
//新增索引
userIndex.add(loggedOnUserNode, "email", updateJohnEmail);
}
tx.success();
}

自动索引

Neo4j有两种自动维护索引的方法–模式索引和自动索引。

模式索引

该索引为schema index,下面演示单个节点对应单个索引的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static void main(String[] args){
File file = new File("/opt/neo4j-community-3.0.3/data/databases/graph.db");
GraphDatabaseService graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(file);
//声明将要使用的标签
Label movieLabel = MyLabels.MOVIES;
Label userLabel = MyLabels.USERS;
Node movie, user;
try(Transaction tx = graphDB.beginTx()){
//创建电影名字属性索引
graphDB.schema().indexFor(movieLabel).on("name").create();
//创建用户名字属性索引
graphDB.schema().indexFor(userLabel).on("name").create();
tx.success();
}
try(Transaction tx = graphDB.beginTx()){
//创建新的MOVIES节点,并设置name属性值
movie = graphDB.createNode(movieLabel);
movie.setProperty("name", "Michael Collins");
//创建新的USERS节点,并设置name属性值
user = graphDB.createNode(userLabel);
user.setProperty("name", "Michael Collins");
tx.success();
}
//验证结果
try(Transaction tx = graphDB.beginTx()){
//通过名字索引查找电影
ResourceIterator<Node> result = graphDB.findNodes(movieLabel, "name", "Michael Collins");
result.forEachRemaining(node -> System.out.println(node.getId()+ " -> " + node.getLabels()));
tx.success();
}
graphDB.shutdown();
}
//输出结果
6 -> [MOVIES]

每个节点可以附加多个标签,每个标签可以有一个索引,如果节点具有多个标签,Neo4j将会确保相关的索引按要求更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static void main(String[] args){
File file = new File("/opt/neo4j-community-3.0.3/data/databases/graph.db");
GraphDatabaseService graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(file);
Label userLabel= MyLabels.USERS;
Label adminLabel= MyLabels.ADMIN;
try(Transaction tx = graphDB.beginTx()){
//创建用户名字属性索引,该索引在上个例子中已经创建,如果再次创建会报错
//graphDB.schema().indexFor(userLabel).on("name").create();
//创建管理员字属性索引
graphDB.schema().indexFor(adminLabel).on("name").create();
tx.success();
}
try(Transaction tx = graphDB.beginTx()){
//创建同时为USERS和ADMIN类型的节点
Node user = graphDB.createNode(userLabel, adminLabel);
user.setProperty("name", "Peter Smith");
tx.success();
}
//验证结果
try(Transaction tx = graphDB.beginTx()){
ResourceIterator<Node> adminSearch = graphDB.findNodes(adminLabel, "name", "Peter Smith");
adminSearch.forEachRemaining(node -> System.out.println(node.getId()+ " -> " + node.getLabels()));
ResourceIterator<Node> userSearch = graphDB.findNodes(userLabel, "name", "Peter Smith");
userSearch.forEachRemaining(node -> System.out.println(node.getId()+ " -> " + node.getLabels()));
tx.success();
}
graphDB.shutdown();
}
//输出结果
8 -> [USERS, ADMIN]
8 -> [USERS, ADMIN]

需要注意的是graphDB.schema().indexFor(label).on(property).create()创建的只是索引,不带唯一约束功能。可以通过graphDatabaseService.schema().constraintFor(label).assertPropertyIsUnique(property).create()创建带唯一约束的索引,此时如果创建已经存在的节点会在transaction提交时报异常。
在neo4j-shell中可以看出创建索引的详细信息:

1
2
3
4
5
6
neo4j-sh (?)$ schema
Indexes
ON :USER(identifyId) ONLINE (for uniqueness constraint)
Constraints
ON (user:USER) ASSERT user.identifyId IS UNIQUE

自动索引

自动索引的开启需要修改配置文件,或者声明newEmbeddedDatabase的时候给定特殊的参数。对于数据库有点认知的同学都会知道,索引是门学问,并不是越多越好,该内容暂时就不做展开了。