Spring Boot入门教程:第九章:访问Neo4j图形数据库

本章带你用Spring Data Neo4j构建一个应用,从Neo4j(一个基于图形的NoSQL数据库)中保存和取出数据。

本文目标

我们将会使用Spring Data Neo4j创建一个内置的Neo4j服务器,来存储实体和关系,并开发查询。

你需要

  • 15分钟左右
  • IntelliJ IDEA
  • JDK 1.8+
  • Maven 3.2+

用Spring Initializr生成项目代码

对于所有的Spring应用,你都可以使用Spring Initializr生成基本的项目代码。Initializr提供了一个快速的方式去引入所有你需要的依赖,并且为你做了很多设置。当前例子只需要Spring Data Neo4j依赖。具体设置如下图:
在这里插入图片描述
如上图所示,我们选择了Maven作为编译工具。你也可以选择Gradle来进行编译。然后我们分别把Group和Artifact设置为“com.hansy”和“accessing-data-neo4j”。

生成的pom.xml文件内容如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.hansy</groupId>
	<artifactId>accessing-data-neo4j</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>accessing-data-neo4j</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-neo4j</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

启动一个Neo4j服务器

在我们实现应用的其他功能之前,我们需要设置一个Neo4j服务器。

Neo4j有一个开源的服务器,我们可以免费安装。

官网下载链接中选择你的系统的对应版本,进行安装。

安装成功之后,访问http://localhost:7474,显示如下界面:
在这里插入图片描述
默认情况下,Neo4j服务器的用户名和密码是:neo4j和neo4j。登入进去之后,需要设置新的密码。为了方便测试,我们把密码设置为secret(生产环境需要更复杂的密码)。
在这里插入图片描述
完成以上设置之后,我们可以开始使用Neo4j了。

定义一个简单的实体

Neo4j包含实体和关系,他们有相同的重要性。可以想象一个这样的场景,你想要为每个人储存一条记录。但是,你还想记录每个人的同事(本例中的teammates字段)。使用Spring Data Neo4j,你可以利用几个简单的注解,实现该功能:

package com.hansy.accessingdataneo4j;

import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;

import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@NodeEntity
public class Person {
    
    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private Person() {
    
    
        //Empty constructor required of Neo4j API 2.0.5
    }

    public Person(String name) {
    
    
        this.name = name;
    }

    /**
     * Neo4j doesn't REALLY have bi-directional relationships. It just means when querying
     * * to ignore the direction of the relationship.
     * * https://dzone.com/articles/modelling-data-neo4j
     */
    @Relationship(type = "TEAMMATE", direction = Relationship.UNDIRECTED)
    public Set<Person> teammates;

    public void workWith(Person person) {
    
    
        if (teammates == null) {
    
    
            teammates = new HashSet<>();
        }

        teammates.add(person);
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    @Override
    public String toString() {
    
    
        return this.name + "'s teammates => "
                + Optional.ofNullable(this.teammates).orElse(
                Collections.emptySet()).stream()
                .map(Person::getName)
                .collect(Collectors.toList());
    }
}

这里的Person类只有一个属性:name。

Person类上面添加了@NodeEntity注解。当Neo4j存储Person对象时,会创建一个新的节点。Person类还有一个标记了@Id注解的id字段。Neo4j使用@Id在内部标识区分数据。

下一个重要的点是teammates集合。类型是Set,并添加了@Relationship注解。意味着这个集合的每个成员都是一个单独的Person节点。然后@Relationship的方向设置为了UNDIRECTED。这使得当你查询TEAMMATE的关系, Spring Data Neo4j会忽略关系的方向。

使用worksWith()方法,你可以简单得建立Person之间的关系。

最后,toString()方法可以方便的打印出Person的名字和Person的同事。

创建简单的查询

Spring Data Neo4j聚焦于用Neo4j存储数据。 但是它继承了Spring Data Commons项目的功能,包括获得查询的能力。也就是说,你不用学习Neo4j的查询语言,只需要编写少量方法就能实现查询功能。

为了看一下使用方法,我们创建一个查询Person节点的接口。代码如下:

package com.hansy.accessingdataneo4j;

import org.springframework.data.neo4j.repository.Neo4jRepository;

import java.util.List;

public interface PersonRepository extends Neo4jRepository<Person,Long> {
    
    

    Person findByName(String name);

    List<Person> findByTeammatesName(String name);
}

PersonRepository继承了Neo4jRepository接口,并且放入了将要操作的类:Person。这个接口附带了很多的操作,包括标准的CRUD(Create、Read、Update和Delete)操作。

你还可以定义其他的查询操作通过声明方法签名。在本例中,我们添加了findByName方法,来查找Person类型的节点,根据name字段是否符合传入的参数。还添加了findByTeammatesName方法,同样用来查找Person类型的节点,不过是根据teammates里面的Person对象的name字段是否有符合条件来判断的。

设置访问Neo4j的权限

Neo4j社区版本在访问时需要凭证。你可以用一组属性配置这些凭证(在src/main/resources/application.properties里面),如下:

spring.data.neo4j.username=neo4j
spring.data.neo4j.password=secret

这里面包括默认用户名(neo4j)和我们刚才新设置的密码(secret)。

以上只是演示,实际开发中不要在源代码库中保存真实的凭证。而应该在你的运行时进行配置。

修改Application类

添加了 @SpringBootApplication 的类的包和子包中,Spring Boot会自动扫描到我们定义的Repository接口。为了更好地控制这个注册过程,我们可以使用 @EnableNeo4jRepositories 注解。

默认情况下,@EnableNeo4jRepositories会扫描当前包下面的所有继承了Spring Data的Repository接口的接口。如果我们是多项目的结构的话,我们可以使用basePackageClasses=MyRepository.class属性,告诉Spring Data Neo4j去扫描一个不同包下面的接口。


Application类的代码如下:

package com.hansy.accessingdataneo4j;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;

import java.util.Arrays;
import java.util.List;

@SpringBootApplication
@EnableNeo4jRepositories
public class AccessingDataNeo4jApplication {
    
    

    private final static Logger log = LoggerFactory.getLogger(AccessingDataNeo4jApplication.class);

    public static void main(String[] args) {
    
    
        SpringApplication.run(AccessingDataNeo4jApplication.class, args);
        System.exit(0);
    }

    @Bean
    CommandLineRunner demo(PersonRepository personRepository) {
    
    
        return args -> {
    
    
            personRepository.deleteAll();

            Person greg = new Person("Greg");
            Person roy = new Person("Roy");
            Person craig = new Person("Craig");

            List<Person> team = Arrays.asList(greg, roy, craig);

            log.info("Before linking up with Neo4j...");

            team.forEach(person -> log.info("\t" + person.toString()));

            personRepository.save(greg);
            personRepository.save(roy);
            personRepository.save(craig);

            greg = personRepository.findByName(greg.getName());
            greg.workWith(roy);
            greg.workWith(craig);
            personRepository.save(greg);

            roy = personRepository.findByName(roy.getName());
            roy.workWith(craig);
            //We already know that roy works with greg
            personRepository.save(roy);

            //We already know craig works with roy and greg

            log.info("Lookup each person by name...");
            team.forEach(person -> log.info("\t" + personRepository.findByName(person.getName()).toString()));

            List<Person> teammates = personRepository.findByTeammatesName(greg.getName());
            log.info("The following have Greg as a teammate...");
            teammates.forEach(person -> log.info("\t" + person.getName()));
        };
    }
}

main方法使用Spring Boot的SpringApplication.run()方法来加载应用,并调用CommandLineRunner来构建实体之间的关系。

在本例中,我们创建了三个本地的Person对象:Greg,Roy和Craig。最初他们只存在在内存中。并且他们之间还没有伙伴关系。

首先,我们查询Greg,并指出他跟Roy和Craig工作在一起,然后保存Greg。注意,伙伴关系标识为UNDIRECTED(也就是双向的意思)。这意味着Roy和Craig也会被更新。

这也是为什么我们在更新Roy之前,需要首先从Neo4j中获取最新的数据的原因。在把Craig添加到关系列表之前,我们需要Roy关系的最新状态。

为什么没有从Neo4j中获取Craig,并添加任何关系?因为我们已经有了!Greg在之前就已经标识Craig为伙伴了,Roy也是。这意味着没有必要在更新Craig的关系。我们可以看到这些关系,通过迭代每个成员,并打印出他们的信息到控制台。

最后,我们直接查询Greg的伙伴,并显示。

运行程序

运行程序,在控制台里面,可以看到如下信息:

Before linking up with Neo4j...
 	Greg's teammates => []
 	Roy's teammates => []
 	Craig's teammates => []
Lookup each person by name...
 	Greg's teammates => [Roy, Craig]
 	Roy's teammates => [Craig, Greg]
 	Craig's teammates => [Roy, Greg]
The following have Greg as a teammate...
 	Roy
 	Craig

我们可以看到,最开始所有人之间都没有关系。然后,在我们添加关系之后,他们绑定到了一起。最后,我们可以看到可以很方便得通过关系查找伙伴。

小结

我们配置好了一个内置的Neo4j服务器,储存了一些简单的关系实体,开发了一些快速查询。

源码下载

accessing-data-neo4j

参考资料

https://spring.io/guides/gs/accessing-data-neo4j/

猜你喜欢

转载自blog.csdn.net/hanshiying007/article/details/107875460
今日推荐