Учебные материалы

Доступ к данным Neo4j

Этот урок освещает процесс создания приложения c Neo4J, которое использует Spring Data.

Что вы создадите

Вы будете использовать Neo4j NoSQL графовое хранилище для сборки встроенного Neo4j сервера, сохранения сущностей и разработки запросов.

Что вам потребуется

  • Примерно 15 минут свободного времени
  • Любимый текстовый редактор или IDE
  • JDK 6 и выше
  • Gradle 1.11+ или Maven 3.0+
  • Вы также можете импортировать код этого урока, а также просматривать web-страницы прямо из Spring Tool Suite (STS), собственно как и работать дальше из него.

Как проходить этот урок

Как и большинство уроков по Spring, вы можете начать с нуля и выполнять каждый шаг, либо пропустить базовые шаги, которые вам уже знакомы. В любом случае, вы в конечном итоге получите рабочий код.

Чтобы начать с нуля, перейдите в Настройка проекта.

Чтобы пропустить базовые шаги, выполните следующее:

Когда вы закончите, можете сравнить получившийся результат с образцом в gs-accessing-data-neo4j/complete.

Настройка проекта

Для начала вам необходимо настроить базовый скрипт сборки. Вы можете использовать любую систему сборки, которая вам нравится для сборки проетов Spring, но в этом уроке рассмотрим код для работы с Gradle и Maven. Если вы не знакомы ни с одним из них, ознакомьтесь с соответсвующими уроками Сборка Java-проекта с использованием Gradle или Сборка Java-проекта с использованием Maven.

Создание структуры каталогов

В выбранном вами каталоге проекта создайте следующую структуру каталогов; к примеру, командой mkdir -p src/main/java/hello для *nix систем:

└── src
    └── main
        └── java
            └── hello

Создание файла сборки Gradle

Ниже представлен начальный файл сборки Gradle. Файл pom.xml находится здесь. Если вы используете Spring Tool Suite (STS), то можете импортировать урок прямо из него.

Если вы посмотрите на pom.xml, вы найдете, что указана версия для maven-compiler-plugin. В общем, это не рекомендуется делать. В данном случае он предназначен для решения проблем с нашей CI системы, которая по умолчанию имеет старую(до Java 5) версию этого плагина.

build.gradle

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-release" }
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.8.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'gs-accessing-data-neo4j'
    version =  '0.1.0'
}

repositories {
    mavenLocal()
    mavenCentral()
    maven { url "http://repo.spring.io/libs-release" }
    maven { url "http://m2.neo4j.org" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    compile("org.springframework:spring-context")
    compile("org.springframework:spring-tx")
    compile("org.springframework.data:spring-data-neo4j")
    compile("org.hibernate:hibernate-validator")
    testCompile("junit:junit")
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

Spring Boot gradle plugin предоставляет множество удобных возможностей:

  • Он собирает все jar'ы в classpath и собирает единое, исполняемое "über-jar", что делает более удобным выполнение и доставку вашего сервиса
  • Он ищет public static void main() метод, как признак исполняемого класса
  • Он предоставляет встроенное разрешение зависимостей, с определенными номерами версий для соответсвующих Spring Boot зависимостей. Вы можете переопределить на любые версии, какие захотите, но он будет по умолчанию для Boot выбранным набором версий

Описание простой сущности

Neo4j описывает сущности и их взаимосвязи, оба из этих сторон одинаково важны. Представьте себе, что вы моделируете систему, где вы сохраняете запись о каждом человеке. Но вы также хотите отслеживать и тех, с кем он работает (teammates в данном примере). С Neo4j вы можете описать все это несколькими простыми аннотациями.

src/main/java/hello/Person.java

package hello;

import java.util.HashSet;
import java.util.Set;

import org.neo4j.graphdb.Direction;
import org.springframework.data.neo4j.annotation.Fetch;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelatedTo;

@NodeEntity
public class Person {

    @GraphId Long id;
    public String name;

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

    @RelatedTo(type="TEAMMATE", direction=Direction.BOTH)
    public @Fetch Set<Person> teammates;

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

    public String toString() {
        String results = name + "'s teammates include\n";
        if (teammates != null) {
            for (Person person : teammates) {
                results += "\t- " + person.name + "\n";
            }
        }
        return results;
    }

}

Здесь у вас есть класс Person с одним лишь атрибутом name. У вас есть два конструктора, один без, а другой с параметром name. Для использования Neo4j далее, вам необходим конструктор без параметров.

В этом уроке типичные методы получения и установки опущены для краткости.

Следующей важной частью является установка teammates. Это простой Set<Person>, но помеченный как @RelatedTo. Это означает, что каждый элемент этого набора сответствует отдельному узлу Person. Обратите внимание, что направление установлено в BOTH. Это означает, что когда вы генерируете TEAMMATE взаимосвязь в одном направлении, оно существует и в другом направлении. Есть ещё @Fetch аннотация в этом поле. Это означает, что элементы будут возвращены сразу. Иначе, вам пришлось бы использовать neo4jTemplate.fetch().

В методе worksWith() вы можете легко связать людей вместе.

И наконец, у вас есть метод toString() для печати имени человека и его коллег.

Создание простых запросов

Spring Data Neo4j ориентирована на хранение данных в Neo4j. Но он наследует функциональность от проекта Spring Data Commons, включая умение составлять запросы. Фактически, вам не нужно изучать язык запросов Neo4j, а можно просто написать несколько методов и запросы будут написаны за вас.

Чтобы увидеть, как это работает, создайте интерфейс запросов узлов Person.

src/main/java/hello/PersonRepository.java

package hello;

import org.springframework.data.repository.GraphRepository;

public interface PersonRepository extends GraphRepository<Person, String> {

    Person findByName(String name);

    Iterable<Person> findByTeammatesName(String name);

}

PersonRepository расширяет GraphRepository класс и указывает тип, с которым он работает: Person. Из коробки этот интерфейс идет с множеством операций, включая стандартные CRUD (create-read-update-delete) операции.

Но вы можете определить и другие запросы, которые вам необходимы, просто описав сигнатуры метода. В данном случае, вы добавили findByName, он ищет узлы типа Person и находит те, которые соответствуют по name. У вас также есть findByTeammatesName, который ищет Person узел, проверяя в каждом из элементов поля teammates на соответствие значения name.

Приступим к работе и посмотрим, что он найдет!

Создание класса Application

Создайте класс Application со всеми компонентами.

src/main/java/hello/Application.java

package hello;

import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.kernel.impl.util.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.core.GraphDatabase;

import java.io.File;

@Configuration
@EnableNeo4jRepositories(basePackages = "hello")
public class Application extends Neo4jConfiguration implements CommandLineRunner {

    public Application() {
        setBasePackage("hello");
    }

    @Bean
    GraphDatabaseService graphDatabaseService() {
        return new GraphDatabaseFactory().newEmbeddedDatabase("accessingdataneo4j.db");
    }

    @Autowired
    PersonRepository personRepository;

    @Autowired
    GraphDatabase graphDatabase;

    public void run(String... args) throws Exception {
        Person greg = new Person("Greg");
        Person roy = new Person("Roy");
        Person craig = new Person("Craig");

        System.out.println("Before linking up with Neo4j...");
        for (Person person : new Person[]{greg, roy, craig}) {
            System.out.println(person);
        }

        Transaction tx = graphDatabase.beginTx();
        try {
            personRepository.save(greg);
            personRepository.save(roy);
            personRepository.save(craig);

            greg = personRepository.findByName(greg.name);
            greg.worksWith(roy);
            greg.worksWith(craig);
            personRepository.save(greg);

            roy = personRepository.findByName(roy.name);
            roy.worksWith(craig);
            // Мы уже знаем, что Рой работает с Грегом
            personRepository.save(roy);

            // Мы уже знаем, что Крейг работает с Роем и Грегом

            System.out.println("Lookup each person by name...");
            for (String name: new String[]{greg.name, roy.name, craig.name}) {
                System.out.println(personRepository.findByName(name));
            }

            System.out.println("Looking up who works with Greg...");
            for (Person person : personRepository.findByTeammatesName("Greg")) {
                System.out.println(person.name + " works with Greg.");
            }

            tx.success();
        } finally {
            tx.close();
        }

    }

    public static void main(String[] args) throws Exception {
        FileUtils.deleteRecursively(new File("accessingdataneo4j.db"));

        SpringApplication.run(Application.class, args);
    }

}

В конфигурацию вам необходимо добавить аннотацию @EnableNeo4jRepositories, а также наследоваться от класса Neo4jConfiguration для удобства работы с нужными компонентами.

Единственное, чего не хватает, это бина службы графовой БД. В данном случае вы используете EmbeddedGraphDatabase, который создает и переиспользует хранилище в виде файла accessingdataneo4j.db.

В рабочей среде вы скорее всего подключитесь к отдельнозапущенному Neo4j серверу.

Вы вызываете экземпляр PersonRepository, описанный ранее. Spring Data Neo4j будет динамически создавать нужный класс, реализующий этот интерфейс и при необходимости использовать нужный код запроса для удовлетворения потребностей интерфейса.

public static void main использует Spring Boot SpringApplication.run() для запуска приложения и CommandLineRunner для сборки связей.

В данном случае вы создаете три экземпляра Person, Greg, Roy и Craig. Первоначально они существуют только в памяти. Важно отметить, что на данный момент пока ни один из них не является коллегой друг другу.

Чтобы что-то сохранить в Neo4j, вы должны начать транзакцию, используя graphDatabase. здесь вы будете сохранять каждого человека. Затем вы выбираете каждого из них и связываете вместе.

Вначале вы находите Грега и указываете, что он работает с Роем и Крейгом, затем сохраняете его снова. Помните, что связь между ними была определена как BOTH, а значит двунаправлена. Это означает, что Рой и Крейг будут также обновлены.

Поэтому, когда вам необходимо обновить Роя, важно извлечь запись о нем из Neo4j первой. Вам нужен последние данные о коллегах Роя до добавления Крейга в его список.

Почему нет кода для получения Крейга и добавления у него связей? Потому что вы уже сделали это! Грег ранее был связан с Крейгом как коллега, так же как и с Роем. Это означает, что вам не нужно снова обновлять связи Крейга. Вы можете убедиться в этом, когда печатается информация о каждом из них и их связях.

В заключение, проверяется другой запрос, где наоборот, отвечается на вопрос "кто работает с кем?".

Сборка исполняемого JAR

Вы можете собрать единый исполняемый JAR-файл, который содержит все необходимые зависимости, классы и ресурсы. Это делает его легким в загрузке, версионировании и развертывании сервиса как приложения на протяжении всего периода разработки, на различных средах и так далее.

./gradlew build

Затем вы можете запустить JAR-файл:

java -jar build/libs/gs-accessing-data-neo4j-0.1.0.jar

Если вы используете Maven, вы можете запустить приложение, используя mvn spring-boot:run, либо вы можете собрать приложение с mvn clean package и запустить JAR примерно так:

java -jar target/gs-accessing-data-neo4j-0.1.0.jar
Процедура, описанная выше, создает исполняемый JAR. Вы также можете вместо него собрать классический WAR-файл.

Запуск сервиса

Если вы используете Gradle, вы можете запустить ваш сервис из командной строки:

./gradlew clean build && java -jar build/libs/gs-accessing-data-neo4j-0.1.0.jar
Если вы используете Maven, то можете запустить ваш сервис таким образом: mvn clean package && java -jar target/gs-accessing-data-neo4j-0.1.0.jar.

Как вариант, вы можете запустить ваш сервис напрямую из Gradle примерно так:

./gradlew bootRun
С mvn - mvn spring-boot:run.

Вы должны увидеть следующее:

Before linking up with Neo4j...
Greg's teammates include

Roy's teammates include

Craig's teammates include

Lookup each person by name...
Greg's teammates include
	- Craig
	- Roy

Roy's teammates include
	- Craig
	- Greg

Craig's teammates include
	- Roy
	- Greg

Looking up who works with Greg...
Roy works with Greg.
Craig works with Greg.

Итог

Поздравляем! Вы только что настроили встроенный Neo4j сервер, сохранили несколько простых, но связанных сущностей и разработали несколько простых запросов.

С оригинальным текстом урока вы можете ознакомиться на spring.io.

comments powered by Disqus