bboyjing's blog

Neo4j学习笔记九【Spring Data Neo4j】

到目前为止,我们一直在直接使用Neo4j提供的核心Api爱来访问数据库,尽管这种方法的功能强大且极其灵活,但是底层Neo4j Api的操作有时非常繁琐。下面我们学习下Spring Data Neo4j(SDN),这是一个以更简单、更熟悉为目标的基于Spring开发模型的Spring Data项目中的子项目。

SDN适合做什么以及不适合做什么

SDN从本质上给需要或期待操作基于POJO的域实体的开发者提供了一种方便的使用代码或库函数的方法。如果是早已使用Spring,或早已经在使用丰富领域模型并想映射到一个图形数据库,SDN正适合做这样的工作。
SDN不适合一次处理任意类型的大量数据场景。要加载或存储的任何逻辑在一次操作中超过10000个单元对SDN来说不是一个好的选择。另外,通过提供一个间接层,SDN会比仅仅使用核心Api慢,因此,如果速度和性能时考虑的最重要因素的话,最好还是使用其本身的Api。SDN提供了访问底层GraphDatabaseService实例的代码,可以使用底层核心Api来获得最佳的性能和最大的灵活性。

搭建环境

查看Spring Boot官网,目前最新版1.4(SNAPSHOP)已经包含了Neo4j,但是我们还是使用最新的RELEASE版1.3.6。SDN使用最新版的4.1.2.RELEASE。

1、新建maven项目,pom文件中引入Spring Boot 1.3.6.RELEASE,Spring Boot这里就不多说了,基本上Java码农标配。

2、添加spring-data-neo4j 4.1.2.RELEASE的依赖

下面列出pom文件内容

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.didadu</groupId>
<artifactId>springboot_sdn</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.6.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<lombok.version>1.16.2</lombok.version>
<spring-data-neo4j.version>4.1.2.RELEASE</spring-data-neo4j.version>
<guava.version>19.0</guava.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId></dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring-data-commons版本有冲突,要升级 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>${spring-data-neo4j.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-data-commons</artifactId>
<groupId>org.springframework.data</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.12.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

3、配置spring-data-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
/**
* 新建类Neo4jConfig,用于初始化Neo4j连接
*/
package cn.didadu.config;
@Configuration
//启动类的@SpringBootApplication会自动扫描同级包以及子包,所以下面的@ComponentScan不加应该没关系
//@ComponentScan("cn.didadu.sdn")
@EnableNeo4jRepositories("cn.didadu.sdn.repository")
@EnableTransactionManagement
public class Neo4jConfig extends Neo4jConfiguration {
@Bean
public org.neo4j.ogm.config.Configuration getConfiguration() {
org.neo4j.ogm.config.Configuration config = new org.neo4j.ogm.config.Configuration();
config.driverConfiguration()
.setDriverClassName("org.neo4j.ogm.drivers.http.driver.HttpDriver")
.setURI("http://neo4j:zhangjing@localhost:7474");
return config;
}
@Override
public SessionFactory getSessionFactory() {
/**
* 如果不指定节点映射的java bean路径,保存时会报如下警告,导致无法将节点插入Neo4j中
* ... is not an instance of a persistable class
*/
return new SessionFactory(getConfiguration(), "cn.didadu.sdn.entity");
}
}

SDN建模

下面我们使用SDN来构造和第二章一模一样的数据

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#### 1、新建Java Bean与节点映射
```java
package cn.didadu.sdn.entity;
@Data
@NodeEntity(label="USERS")
public class User {
public User(){}
public User(String name){
this.name = name;
}
@GraphId
private Long nodeId;
@Property(name="name")
private String name;
//关系直接定义在节点中
@Relationship(type = "IS_FRIEND_OF", direction=Relationship.OUTGOING)
private List<User> friends;
//使用外部定义的关系
@Relationship(type = "HAS_SEEN")
private List<Seen> hasSeenMovies;
}
@Data
@NodeEntity(label = "MOVIES")
public class Movie {
public Movie(String name){
this.name = name;
}
@GraphId
private Long nodeId;
@Property(name="name")
private String name;
}
@Data
@RelationshipEntity(type="HAS_SEEN")
public class Seen {
public Seen(Integer stars, User startNode, Movie endNode){
this.stars = stars;
this.startNode = startNode;
this.endNode = endNode;
}
@GraphId
private Long id;
@Property
private Integer stars;
@StartNode
private User startNode;
@EndNode
private Movie endNode;
}

2、新建Java Bean对应的Repository类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.didadu.sdn.repository;
@Repository
public interface UserRepository extends GraphRepository<User>{
@Query("MATCH (user:USERS {name:{name}}) RETURN user")
User getUserByName(@Param("name") String name);
}
@Repository
public interface MovieRepository extends GraphRepository<Movie> {
}
@Repository
public interface SeenRepository extends GraphRepository<Seen> {
}

访问和持久化实体

1、编写业务Service类

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package cn.didadu.service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private MovieRepository movieRepository;
@Autowired
private SeenRepository seenRepository;
@Transactional
public void initData(){
/**
* 初始化用户
*/
User user1 = new User("John Johnson");
User user2 = new User("Kate Smith");
User user3 = new User("Jack Jeffries");
/**
* 为用户John添加朋友关系
*/
user1.setFriends(Lists.newArrayList(user2, user3));
/**
* 初始化电影
*/
Movie movie1 = new Movie("Fargo");
Movie movie2 = new Movie("Alien");
Movie movie3 = new Movie("Heat");
/**
* 初始化HAS_SEEN关系
*/
Seen hasSeen1 = new Seen(5, user1, movie1);
Seen hasSeen2 = new Seen(3, user2, movie3);
Seen hasSeen3 = new Seen(6, user2, movie2);
Seen hasSeen4 = new Seen(4, user3, movie1);
Seen hasSeen5 = new Seen(5, user3, movie2);
/**
* 如果不加@Transactional,下面每个save都会单独开启事物
*/
userRepository.save(Lists.newArrayList(user1, user2, user3));
movieRepository.save(Lists.newArrayList(movie1, movie2, movie3));
seenRepository.save(Lists.newArrayList(hasSeen1, hasSeen2, hasSeen3, hasSeen4, hasSeen5));
}
@Transactional
public User getUserByName(String name){
return userRepository.getUserByName(name);
}
}

1、编写Unit Test

1
2
3
//测试前先删除数据
match (user:USERS),(movie:MOVIES)
detach delete user,movie;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringbootSdnApplication.class)
public class UserServiceTest {
@Autowired
private UserService userService;
/**
* 因为是通过http连接到Neo4j数据库的,所以要预先启动Neo4j:neo4j console
*/
@Test
public void testInitData(){
userService.initData();
}
@Test
public void testGetUserByName(){
User user = userService.getUserByName("John Johnson");
System.out.println(user);
}
}

至此我们已经可以通过SDN来操作Neo4j了,在本章节之前都是通过嵌入式的方式访问Neo4j的,只有这一章是通过HTTP访问Neo4j。在本节开头说过SDN提供了访问底层GraphDatabaseService实例的代码,但事实上当前版本的SDN中已经没有GraphDatabaseService相关类了,所以目前看来只能通过HTTP的方式远程访问Neo4j数据库了。什么。。。那么强大的核心api用不了了?这怎么能忍!