#26 Spring Boot – Neo4j

W poprzednim wpisie pokazałam jak zainstalować Neo4j. Teraz możemy spróbować napisać prostą aplikację z użyciem Spring Boot’a, która wykorzystuje bazę grafową Neo4j.  Dzięki wykorzystaniu Spring Data otrzymujemy całą abstrakcję związaną z warstwą persystencji. Ponadto operacje CRUD’owe wywoływane są w taki sam sposób jak dla innych, znanych nam typów baz.

Maven

Zacznijmy od dodania zależności:

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

Połączenie

Aby nawiązać połączenie z bazą Neo4j należy w pliku application.properties wskazać adres, użytkownika oraz hasło:

spring.data.neo4j.uri=http://localhost:7474/
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=neo4j

Jeśli uruchamiamy bazę lokalnie na domyślnym porcie 7474 to wystarczy podać użytkownika i hasło. Domyślnie są to wartości neo4j zarówno dla użytkownika jak i hasła, ale przy pierwszym uruchomieniu bazy zostajemy poproszeni o zmianę hasła.

Mapowanie

Teraz stwórzmy klasy z węzłami i relacjami. Do mapowania obiektów na grafy wykorzystywana jest biblioteka OGM (ang. Object Graph Mapping).

Węzły

W celu oznaczenia naszej klasy POJO jako węzeł używamy adnotacji @NodeEntity. Podobnie jak w JPA domyślną nazwą węzła będzie nazwa klasy, ale możemy to zmienić ustawiając wartość label, @NodeEntity(label="Blog"). Każdy węzeł, który będzie zapisany w bazie musi posiadać unikalny identyfikator typu Long. Jeśli nazwa pola jest różna od “id” to należy użyć adnotacji @GraphId, w celu jawnego oznaczenia identyfikatora.

Relacje

Relacja może być reprezentowana przez zbiór w klasie węzła, który jest adnotowany jako @Relationship. Każda relacja musi mieć określony typ oraz kierunek. Domyślnym typem relacji jest uppercase’owana nazwa adnotowanego zbioru. Jeśli chcemy określić swój typ to umieszczamy go pod type. Z kolei domyślny kierunek relacji to OUTGOING. Inne typy ustawiamy pod direction:

  • UNDIRECTED – brak zdefiniowanego kierunku relacji
  • OUTGOING – relacja wychodząca z węzła
  • INCOMING – relacja wchodząca do węzła

Wszystkie typy relacji dostępne są w klasie org.neo4j.ogm.annotation.Relationship, ale możemy również podać tą wartość jako String (uppercase).

@Relationship(type = "LIKE", direction = Relationship.INCOMING)

Relacje z właściwościami

Jeśli relacja zawiera dodatkowe właściwości musimy utworzyć dla niej nową klasę z adnotacją @RelationshipEntity. Typ relacji określamy tak samo jak w przypadku @Relationship w type. Zamiast kierunku definiujemy początkowy oraz końcowy węzeł relacji poprzez adnotacje @StartNode i @EndNode.

@RelationshipEntity(type = "STUDY")
class Studies{
    @StartNode
    Student student;
    @EndNode
    Course course;
    Date startDate;
}

Implementacja

Zaczynamy od stworzenia klasy z węzłem Blog.

@NodeEntity
@Data
@NoArgsConstructor
public class Blog {

    @GraphId
    private Long blogId;
    private String name;

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

Drugi węzeł Subscriber posiada dodatkowo zbiór oznaczający relacje z węzłem Blog. Metoda subscribe(), pozwala nam utworzyć (dodać do zbioru) relacje pomiędzy węzłami Blog oraz Subscriber.

@NodeEntity
@Data
@NoArgsConstructor
public class Subscriber {

    private Long id;
    private String name;

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

    @Relationship(type = "LIKE")
    public Set<Blog> blogs;

    public void subscribe(Blog blog) {
        if (blogs == null) {
            blogs = new HashSet<>();
        }
        blogs.add(blog);
    }
}

Repozytorium

Podobnie jak w innych typach baz, wykorzystujemy abstrakcję dostarczoną przez Spring Data. Nasz interfejs musi dziedziczyć po interfejsie GraphRepository. Dzięki temu otrzymujemy zbiór podstawowych metod:

interface SubscriberRepository extends GraphRepository<Subscriber> {
        Subscriber findByName(String name);
}

GitHub

Cały kod z mapowaniem oraz użyciem CRUD’owych operacji znajdziecie na GitHubie.

  • em

    Używanie adnotacji @Data jest poważnym błędem (ze względu na to, w jaki sposób tworzone jest equals/hashcode oraz udostępnianie toString, który powinien być traktowany tak samo jak API) – nie dawajcie złego przykładu i zmieńcie to na Setter/Getter (nic więcej nie jest potrzebne w tym przykładzie)

    • CodeCouple.pl

      Witaj, zgadzamy się co do tego iż Getter/Setter w tym przypadku byłby wystarczający. Czy mógłbyś podać źródła lub opisać dla czego equals/hashcode jest źle tworzony? Dzięki!

      • em

        Nie do końca wierzę, że zadajesz to pytanie serio, bo przecież znasz API, które używasz (i nawet prezentujesz je innym) ale ok…
        Wystarczy, że zobaczysz co robi domyślny Equals w Lomboku – robi deepEqualsa a to NA BANK nie jest to, co chcesz mieć pod metodą equals (przypominam: najmniejszy możliwy zbiór pól biznesowych pozwalający jednoznacznie zidentyfikować tożsamość obiektu – np. email), która powinna być szybka i nie służy do stwierdzenia czy dwa obiekty mają takie same pola (co robi właśnie deepEquals – equals na wszystkie pola i kolekcje)