Spring Data Neo4j - Neo4j 데이터 추가 및 조회

spring Data Neo4j는 그래프 데이터베이스 중에 하나인 neo4j에 다루는 프레임워크 이다.

개요

Neo4j는 그래프 데이터베이스 중에 하나이며, Spring Data에서는 Neo4j를 다를 수 있는 인테페이스를 제공하는 프레임워크를 제공한다.

Kotlin 언어를 이용하여 간단한 Spring Data Neo4j 활용한 프로젝트를 만들어 보겠다.

Neo4j 서버 준비

Neo4j는 오픈 소스 서버이기에 무료로 설치하거나, Docker로 실행할 수 있다.

Neo4j macOS 환경에서는 3가지 설치 방법이 있다.

본인에 원하는 방식으로 설치를 해도 상관 없다.

Neo4j 프로젝트 생성

아래와 같이 curl 명령어를 사용하여 Spring Boot 초기 프로젝트를 생성한다.

curl https://start.spring.io/starter.tgz \
-d bootVersion=3.0.6 \
-d dependencies=data-neo4j \
-d baseDir=spring-data-neo4j \
-d groupId=com.devkuma \
-d artifactId=spring-data-neo4j \
-d packageName=com.devkuma.neo4j \
-d applicationName=Neo4jApplication \
-d packaging=jar \
-d language=kotlin \
-d javaVersion=17 \
-d type=gradle-project-kotlin | tar -xzvf -

위 명령어를 실행하게 되면 Java 17, Spring Boot 버전은 3.0.6으로 프로젝트가 생성된다.

빌드 스크립트

빌드 스크립트에 Neo4j을 동작시키기 위한 라이브러리를 아래와 같이 추가한다.

/build.gradle.kts

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-data-neo4j")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

의존성 라이브러리에 Spring Data Neo4j 라이브러리(spring-boot-starter-data-neo4j)가 포함된 것을 볼 수 있다.

Entity 정의

Neo4j는 엔터티와 엔터티의 관계를 연결되며, 두 방향 모두 똑같이 중요한다. 각 사람에 대한 레코드를 저장하는 시스템을 모델링한다고 생각해 보자. 여기서 특정 사람의 동료도 추적하려고 한다. 예를 들어, 아래 엔티티에서 teammates에 해당된다.

src/main/java/com/devkuma/neo4j/entity/Person.java

package com.devkuma.neo4j.entity

import org.springframework.data.neo4j.core.schema.GeneratedValue
import org.springframework.data.neo4j.core.schema.Id
import org.springframework.data.neo4j.core.schema.Node
import org.springframework.data.neo4j.core.schema.Relationship
import java.util.*
import java.util.stream.Collectors

@Node
class Person {

    @Id
    @GeneratedValue
    var id: Long = 0
    var name: String = ""

    @Relationship(type = "TEAMMATE")
    var teammates: MutableSet<Person>? = null

    fun worksWith(person: Person) {
        if (teammates == null) {
            teammates = HashSet()
        }
        teammates!!.add(person)
    }

    override fun toString(): String {
        return ("$name's teammates => " +
                Optional
                    .ofNullable(teammates)
                    .orElse(mutableSetOf()).stream()
                    .map { obj: Person -> obj.name }
                    .collect(Collectors.toList()))
    }
}

쿼리 Repository 생성

Spring Data Neo4j는 Neo4j에 데이터를 저장하는데 중점을 둔다. 그러나 조회 쿼리 파생 기능을 포함하여 Spring Data Commons 프로젝트의 기능을 상속받는다. 기본적으로 Neo4j의 쿼리 언어를 배울 필요가 없다. 대신 몇 가지 메서드를 작성해야 쿼리가 자동으로 작성되도록 할 수 있다.

src/main/java/com/devkuma/neo4j/repository/PersonRepository.java

package com.devkuma.neo4j.repository

import com.devkuma.neo4j.entity.Person

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

interface PersonRepository : Neo4jRepository<Person, Long> {
    fun findByName(name: String): Person?
    fun findByTeammatesName(name: String): List<Person>
}

PersonRepository 인터페이스는 Neo4jRepository 확장하고, 작동하려는 유형(Person)을 연결한다. 이 인터페이스는 표준 CRUD(만들기, 읽기, 업데이트 및 삭제) 작업을 비롯한 많은 작업과 함께 제공된다.

Neo4j 접근 권한

Neo4j Community Edition에 접근하려면 자격 증명 설정이 필요하다. spring 기본 설정 파일인 application.propertiesapplication.yml로 변경하고 아래와 같이 설정을 넣는다.

/src/main/resources/application.yml

spring:
  neo4j:
    uri: bolt://localhost:7687
    authentication:
      username: neo4j
      password: secret123

로그 설정

로그를 표시하기 위해 의존성으로 kotlin-logging 라이브러리를 추가한다.

dependencies {
	// .. 생략 ..

	implementation("io.github.microutils:kotlin-logging:3.0.5")
}

그리고, Spring 설정 파일에 로그 Level를 설정한다.

logging:
  level:
    org.springframework.data.neo4j.cypher: ERROR

이 설정이 없으면 cypher 관련 WARN이 발생한다. (아무래도, Spring Data Neo4j 업데이트가 필요해 보인다.)

애플리케이션 클래스 생성

Spring Initializr는 애플리케이션을 위한 간단한 클래스를 생성해준다.

src/main/java/com/devkuma/neo4j/entity/Person.java

package com.devkuma.neo4j

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class Neo4Ajpplication

fun main(args: Array<String>) {
	runApplication<Neo4Ajpplication>(*args)
}

이 생성된 애플리케이션 클래스를 아래와 같이 변경한다.

package com.devkuma.neo4j

import com.devkuma.neo4j.entity.Person
import com.devkuma.neo4j.repository.PersonRepository
import mu.KotlinLogging
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories
import kotlin.system.exitProcess

private val log = KotlinLogging.logger {}

@SpringBootApplication
@EnableNeo4jRepositories
class Neo4jApplication {

    @Bean
    fun demo(@Autowired personRepository: PersonRepository): CommandLineRunner {
        return CommandLineRunner {

            personRepository.deleteAll()

            var greg = Person()
            greg.name = "Greg"
            var roy = Person()
            roy.name = "Roy"
            val craig = Person()
            craig.name = "Craig"
            val team: List<Person> = listOf(greg, roy, craig)

            log.info("Before linking up with Neo4j...")
            team.stream().forEach { person: Person ->
                log.info("\t${person}")
            }

            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)

            log.info("Lookup each person by name...")
            team.stream().forEach { person: Person ->
                log.info("\t${personRepository.findByName(person.name)}")
            }
            val teammates = personRepository.findByTeammatesName(craig.name)
            log.info("The following have ${craig.name} as a teammate...")
            teammates.stream()
                .forEach { person: Person -> log.info("\t${person.name}") }
        }
    }
}

fun main(args: Array<String>) {
    runApplication<Neo4jApplication>(*args)
    exitProcess(0)
}
  • @EnableNeo4jRepositories
    • 이 어노테이션이 있으므써, Neo4j 설정이 활성화 된다.
  • 초기에는 모두 데이터를 삭제하고, “Greg”, “Roy”, “Craing"을 넣고, TEAMMATEworkWith(..) 함수로 추가하고 있는 코드를 확인할 수 있다.
  • 결과로는 각 사람의 teammates를 표시해주고, 반대로 “Craig"를 teammate로 지정한 사람을 표시해주고 있다.

애플리케이션 실행

그럼 실행을 해보면, 결과가 로그로 표시되는 것을 볼 수 있다.

2023-05-13T01:53:34.640+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : Before linking up with Neo4j...
2023-05-13T01:53:34.640+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : 	Greg's teammates => []
2023-05-13T01:53:34.640+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : 	Roy's teammates => []
2023-05-13T01:53:34.640+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : 	Craig's teammates => []
2023-05-13T01:53:34.852+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : Lookup each person by name...
2023-05-13T01:53:34.865+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : 	Greg's teammates => [Craig, Roy]
2023-05-13T01:53:34.874+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : 	Roy's teammates => [Craig]
2023-05-13T01:53:34.881+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : 	Craig's teammates => []
2023-05-13T01:53:34.893+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : The following have Craig as a teammate...
2023-05-13T01:53:34.894+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : 	Greg
2023-05-13T01:53:34.894+09:00  INFO 36427 --- [           main] com.devkuma.neo4j.Neo4jApplication       : 	Roy

Neo4j Browser(http://localhost:7474/browser/)에 접속해 보면 Node List를 확인할 수 있다.

Accessing Data Neo4j

참조

위에 예제 코드는 GitHub에서 확인해 볼 수 있다.




최종 수정 : 2024-01-18