neo4jをSpring bootで触ってみた

↓のアドベントカレンダーの7日目!(ざわ・・・)

www.adventar.org

はじめに

今日はneo4jをSpring bootで触ってみたので、その話を少々します。

neo4j

ひょんなことからグラフDBを触る機会ができたので、なにがいいかなーって考えたらやっぱ有名ドコロでしょってことでneo4jを触るようになってます。そんな私です。

neo4jはオープンソースのグラフDBで、Javaで実装されています。

neo4j.com

はい、インストール

$ brew install neo4j

はい、起動

neo4j start

ええ、それはもう。

つぎにSpring boot

導入しましょう

Spring Data Neo4jとかいう便利なやつがいるので、 今回はそれを使います。

mavenの定義はこのような感じに・・・

(Gradleで書こうかと思ったけど、まぁ今回はいいかなと・・・)

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-accessing-data-neo4j</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.2.RELEASE</version>
    </parent>

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

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

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

    <repositories>
        <repository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>

</project>

はい、OKです。

ちょちょっと何か作ろう

ひどく簡単(というか↓のチュートリアルそのもの・・・)ですが、サンプルも書いてみました。

spring.io

人がチームメイトという関係でつながっているサンプルです。

まずは人のクラス

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

import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;

@NodeEntity
public class Person {

    @GraphId private Long id;

    private String name;

    
    private Person() {
        // Empty constructor required as 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 worksWith(Person person) {
        if (teammates == null) {
            teammates = new HashSet<>();
        }
        teammates.add(person);
    }

    public String toString() {

        return this.name + "'s teammates => "
                + Optional.ofNullable(this.teammates).orElse(
                        Collections.emptySet()).stream().map(
                                person -> person.getName()).collect(Collectors.toList());
    }

    public String getName() {
        return name;
    }

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

ノードであることは@Relationshipで宣言して、主キーみたいなもんのIDは@GraphIdで宣言して、ノード間の関係は@Relationshipで宣言して、って感じですか。

よくできてますね〜。ええ、はい。

次に人を検索するためのリポジトリ

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

public interface PersonRepository extends GraphRepository<Person> {
    Person findByName(String name);
}

これで名前での検索が可能になります。

では、実際にノードを定義していきましょう。

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

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;

@SpringBootApplication
@EnableNeo4jRepositories
public class Application {
    private final static Logger log = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
    
    @Bean
    CommandLineRunner demo(PersonRepository personRepository) {
        return args -> {
            personRepository.deleteAll();

            Person yamada = new Person("山田 哲人");
            Person ogawa = new Person("小川 泰弘");
            Person kawabata = new Person("川端 慎吾");
            
            List<Person> team = Arrays.asList(yamada, ogawa, kawabata);

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

            team.stream().forEach(person -> log.info("\t" + person.toString()));
        
            personRepository.save(yamada);
            personRepository.save(ogawa);
            personRepository.save(kawabata);
            
            yamada = personRepository.findByName(yamada.getName());
            yamada.worksWith(ogawa);
            yamada.worksWith(kawabata);
            personRepository.save(yamada);
            
            ogawa = personRepository.findByName(ogawa.getName());
            ogawa.worksWith(kawabata);
            
            // We already know that roy works with greg
            personRepository.save(ogawa);

            // We already know craig works with roy and greg

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

        };
    }
}

実行してみて〜

DBを確認すると、ほれこの通り。ちゃんと山田、ライアン、川端がチームメイトとしてつながっております。メジャーとか行くなよ。

f:id:extinctddt:20161220070335p:plain

そして、ログをみてもこの通り。 ちゃんと検索されております。

2016-12-20 06:25:05.978  INFO 85511 --- [           main] o.n.o.drivers.http.request.HttpRequest   : Thread: 1, url: http://localhost:7474/db/data/transaction/commit, request: {"statements":[{"statement":"MATCH (n:`Person`) WHERE n.`name` = { `name` } WITH n MATCH p=(n)-[*0..1]-(m) RETURN p, ID(n)","parameters":{"name":"山田 哲人"},"resultDataContents":["graph","row"],"includeStats":false}]}
2016-12-20 06:25:05.989  INFO 85511 --- [           main] stretch.neo4j.Application                :    山田 哲人's teammates => [川端 慎吾, 小川 泰弘]
2016-12-20 06:25:05.990  INFO 85511 --- [           main] o.n.o.drivers.http.request.HttpRequest   : Thread: 1, url: http://localhost:7474/db/data/transaction/commit, request: {"statements":[{"statement":"MATCH (n:`Person`) WHERE n.`name` = { `name` } WITH n MATCH p=(n)-[*0..1]-(m) RETURN p, ID(n)","parameters":{"name":"小川 泰弘"},"resultDataContents":["graph","row"],"includeStats":false}]}
2016-12-20 06:25:06.001  INFO 85511 --- [           main] stretch.neo4j.Application                :    小川 泰弘's teammates => [山田 哲人, 川端 慎吾]
2016-12-20 06:25:06.002  INFO 85511 --- [           main] o.n.o.drivers.http.request.HttpRequest   : Thread: 1, url: http://localhost:7474/db/data/transaction/commit, request: {"statements":[{"statement":"MATCH (n:`Person`) WHERE n.`name` = { `name` } WITH n MATCH p=(n)-[*0..1]-(m) RETURN p, ID(n)","parameters":{"name":"川端 慎吾"},"resultDataContents":["graph","row"],"includeStats":false}]}
2016-12-20 06:25:06.014  INFO 85511 --- [           main] stretch.neo4j.Application                :    川端 慎吾's teammates => [山田 哲人, 小川 泰弘]

あら、便利ですねぇ。

最後に

neo4jを導入した。そして、javaを使ってノード作成、検索ができるようになった!

本当に簡単なことしかやってないので、次回はもっと面白いこともやってみよう!

ということで、さようなら。

投稿が遅れたこと、、、、すまないと思っている。