Создание клона IMDB с Java бэкендом на SparkJava и Neo4j
Мы решили создать курс для Java Backend и хотим обсудить некоторые аспекты и выбранные альтернативы, которые мы заметили при его создании.
Курс по разработке приложений шаг за шагом описывает внедрение конечных точек, начиная с тестовых наборов данных и заканчивая полноценным приложением, готовым к развертыванию.
Приложение представляет собой клон IMDB, основанный на наборе данных рекомендации MovieLens, дополненном данными о фильмах и ролях с themoviedb.org.
Внешний интерфейс написан на vue.js и выглядит довольно приятно.
Он вызывает несколько конечных точек REST API для вызова различных представлений и функций.

- регистрация и аутентификация пользователя и хранение его информации
- список жанров, фильмов, людей, отсортированных и отфильтрованных, и сопутствующая информация
- добавление фильмов в избранное и рейтинг, а также возврат этих списков и рекомендаций
Репозиторий содержит полный код приложения и может использоваться для сборки и запуска приложения.
У него есть ветки для каждого модуля курса, поэтому вы можете отслеживать прогресс/различия и переходить к завершенному решению, если что-то пойдет не так.
Инфраструктура курса использует Asciidoctor.js, поэтому мы можем использовать includes (используя тег region) непосредственно из нашего репозитория кода.
Таким образом, мы также получаем подсветку синтаксиса, предупреждающие маркеры и многие другие полезные вещи из коробки.
Во время интерактивного курса множество викторин проверяют понимание, позволяют запускать тесты или проверять базу данных на наличие успешных обновлений.
Курс также автоматически интегрируется с песочницей Neo4j, поэтому вы можете запускать тестовые запросы, а также приложение для своего персонального экземпляра, на котором размещен набор данных фильмов.

Настройка
Мы использовали традиционную настройку Java, установив Java 17 и Apache Maven через sdkman.
Поскольку в Java 17 появились строковые блоки и записи, мы захотели использовать эти возможности.
sdk install java 17-open sdk use java 17-open sdk install maven
Веб фреймворк — SparkJava
Возможно, вы не слышали о SparkJava, который существует уже довольно давно и представляет собой минималистичный веб фреймворк, эквивалентный Express/Sinatra для Java.
Поскольку появятся другие курсы со Spring (Data Neo4j), для этого курса мы хотели ограничиться чем-то простым. Quarkus также был вариантом, о котором мы думали, но затем выбрали SparkJava из-за его минимализма.
Также приложение курса JavaScript использовало Express.
Таким образом, перенос кода с JavaScript на Java был довольно простым, с помощью всего лишь нескольких замен у нас уже через несколько минут что-то заработало.
Минимальный пример hello world выглядит так:
import static spark.Spark.*; public class HelloWorld < public static void main(String[] args) < get("/hello", (req, res) -> "Hello World"); >>
Все наше основное приложение, которое регистрирует маршруты, добавляет обработку ошибок, проверяет авторизацию, обслуживает форматирование публичных файлов в JSON (используя GSON) и запускает сервер, занимает не более 20 строк.
Документация для SparkJava очень короткая и в то же время исчерпывающая, все, что вам нужно, можно найти быстро.
package neoflix; import static spark.Spark.*; import java.util.*; import com.google.gson.Gson; import neoflix.routes.*; import org.neo4j.driver.*; public class NeoflixApp < public static void main(String[] args) throws Exception < AppUtils.loadProperties(); int port = AppUtils.getServerPort(); port(port); Driver driver = AppUtils.initDriver(); Gson gson = GsonUtils.gson(); staticFiles.location("/public"); String jwtSecret = AppUtils.getJwtSecret(); before((req, res) -> AppUtils.handleAuthAndSetUser(req, jwtSecret)); path("/api", () -> < path("/movies", new MovieRoutes(driver, gson)); path("/genres", new GenreRoutes(driver, gson)); path("/auth", new AuthRoutes(driver, gson, jwtSecret)); path("/account", new AccountRoutes(driver, gson)); path("/people", new PeopleRoutes(driver, gson)); >); exception(ValidationException.class, (exception, request, response) -> < response.status(422); var body = Map.of("message",exception.getMessage(), "details", exception.getDetails()); response.body(gson.toJson(body)); response.type("application/json"); >); System.out.printf("Server listening on http://localhost:%d/%n", port); > >
Маршруты — AccountRoutes
Маршруты могут быть сгруппированы по корневому пути, а затем обработаны в простом DSL. Вот как получить список избранного в AccountRoutes
get("/favorites", (req, res) -> < var params = Params.parse(req, Params.MOVIE_SORT); String userId = AppUtils.getUserId(req); return favoriteService.all(userId, params); >, gson::toJson);
Сначала мы анализируем некоторые параметры из URL-адреса запроса, затем извлекаем userId из атрибутов запроса и вызываем FavoriteService для запроса базы данных.
Fixtures
По мере прохождения курса постепенно добавляется реализация для использования базы данных.
Чтобы иметь возможность тестировать, запускать и взаимодействовать с приложением с самого начала, некоторые наборы данных статических фикстур используются для возврата ответов от служб.
Исходное Javascript приложение использовало файлы JS для хранения фикстур как JS объектов, но мы хотели более переносимый вариант.
Итак, мы преобразовали фикстуры в файлы JSON, а затем прочитали их в List структуры в сервисах, которые использовали фикстуры.
public static List loadFixtureList(final String name) < var fixture = new InputStreamReader(AppUtils.class.getResourceAsStream("/fixtures/" + name + ".json")); return GsonUtils.gson().fromJson(fixture,List.class); >
Который затем можно использовать в сервисе с this.popular = AppUtils.loadFixtureList(«popular»); .
[< "actors": [ , , , ], "languages": ["English"], "plot": "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.", "year": 1994, "genres": [,], "directors": [], "imdbRating": 9.3, "tmdbId": "0111161", "favorite": false, "title": "Shawshank Redemption, The", "poster": "https://image.tmdb.org/t/p/w440_and_h660_face/5KCVkau1HEl7ZzfPsKAPM0sMiKc.jpg" >]
Чтобы сделать его не полностью статичным, мы использовали обработку Java Streams для реализации фильтрации, сортировки и разбиения на страницы данных фикстуры.
public static List process( List result, Params params) < return params == null ? result : result.stream() .sorted((m1, m2) ->(params.order() == Params.Order.ASC ? 1 : -1) * ((Comparable)m1.getOrDefault(params.sort().name(),"")).compareTo( m2.getOrDefault(params.sort().name(),"") )) .skip(params.skip()).limit(params.limit()) .toList(); >
Который затем используется в сервисах, например:
public List all(Params params, String userId) < // TODO: Open an Session // TODO: Execute a query in a new Read Transaction // TODO: Get a list of Movies from the Result // TODO: Close the session return AppUtils.process(popular, params); >
Драйвер Neo4j
Реализации реальной службы используют официальный драйвер Neo4j Java для запросов к базе данных.
Мы можем отправлять параметризованные запросы Cypher на сервер, использовать параметры и обрабатывать результаты в рамках функции повторяемой транзакции (транзакция чтения или записи).
Добавьте зависимость драйвера org.neo4j.driver:neo4j-java-driver в файл pom.xml.
Далее вы можете создать один экземпляр драйвера на весь срок службы вашего приложения и использовать сеансы драйвера по мере необходимости.
Сеансы не удерживают TCP-подключения, а используют их из пула по мере необходимости.
В рамках сеанса вы можете использовать транзакции чтения и записи для выполнения вашей единицы работы.
Мы прочитали учетные данные подключения из application.properties и для удобства установили в качестве системного свойства, и затем инициализировали драйвер.
static Driver initDriver()
Сервис FavoriteService
Затем драйвер передается каждой службе при построении и может использоваться оттуда для создания сеансов и взаимодействия с базой данных.
Здесь, в примере FavoriteService для перечисления избранного пользователя, вы можете увидеть, как мы используем блоки String для оператора Cypher и лямбда-выражения для обратного вызова readTransaction
public List all(String userId, Params params) < // Open a new session try (var session = this.driver.session()) < // Retrieve a list of movies favorited by the user var favorites = session.readTransaction(tx ->< String query = """ MATCH (u:User )-[r:HAS_FAVORITE]->(m:Movie) RETURN m < .*, favorite: true >AS movie ORDER BY m.title ASC SKIP $skip LIMIT $limit """; var res = tx.run(query, Values.parameters("userId", userId, "skip", params.skip(), "limit", params.limit())); return res.list(row -> row.get("movie").asMap()); >); return favorites; > >
При добавлении любимого фильма мы используем транзакцию записи FavoriteService.add , чтобы создать связь FAVORITE между пользователем и фильмом.
public Map add(String userId, String movieId) < // Open a new Session try (var session = this.driver.session()) < // Create HAS_FAVORITE relationship within a Write Transaction var favorite = session.writeTransaction(tx -> < String statement = """ MATCH (u:User ) MATCH (m:Movie ) MERGE (u)-[r:HAS_FAVORITE]->(m) ON CREATE SET r.createdAt = datetime() RETURN m < .*, favorite: true >AS movie """; var res = tx.run(statement, Values.parameters("userId", userId, "movieId", movieId)); return res.single().get("movie").asMap(); >); return favorite; // Throw an error if the user or movie could not be found > catch (NoSuchRecordException e) < throw new ValidationException("Could not create favorite movie for user", Map.of("movie",movieId, "user",userId)); >>
Метод result.single() завершится ошибкой, если нет ровно одного результата с NoSuchRecordException , поэтому нам не нужно проверять это в запросе.
Аутентификация
Наше приложение также обеспечивает управление пользователями для персонализации.
Вот почему мы должны:
- зарегистрировать пользователя
- аутентифицировать пользователя
- проверить токен авторизации и добавить информацию о пользователе в запрос
Для регистрации и аутентификации мы сохраняем пользователя непосредственно как узел в Neo4j и используем библиотеку bcrypt для хеширования и сравнения хешированных паролей с входными данными.
Вот пример из метода authenticate класса AuthService .
public Map authenticate(String email, String plainPassword) < // Open a new Session try (var session = this.driver.session()) < // Find the User node within a Read Transaction var user = session.readTransaction(tx -> < String statement = "MATCH (u:User ) RETURN u"; var res = tx.run(statement, Values.parameters("email", email)); return res.single().get("u").asMap(); >); // Check password if (!AuthUtils.verifyPassword(plainPassword, (String)user.get("password"))) < throw new ValidationException("Incorrect password", Map.of("password","Incorrect password")); >String sub = (String)user.get("userId"); // compute JWT token signature String token = AuthUtils.sign(sub, userToClaims(user), jwtSecret); return userWithToken(user, token); > catch(NoSuchRecordException e) < throw new ValidationException("Incorrect email", Map.of("email","Incorrect email")); >>
Для передачи информации об аутентификации в виде токенов JWT в браузер и обратно мы используем библиотеку Java Auth0 для создания токена, а затем также проверяем его.
Это делается с помощью обработчика before в SparkJava, который при успешной проверке сохраняет атрибут sub , содержащий атрибут запроса userId . К которому затем могут получить доступ маршруты, например, для персонализации или рейтингов.
static void handleAuthAndSetUser(Request req, String jwtSecret) < String token = req.headers("Authorization"); String bearer = "Bearer "; if (token != null && !token.isBlank() && token.startsWith(bearer)) < token = token.substring(bearer.length()); String userId = AuthUtils.verify(token, jwtSecret); req.attribute("user", userId); >> // usage in NeoflixApp before((req, res) -> AppUtils.handleAuthAndSetUser(req, jwtSecret));
Записи Java 17
Изначально мы планировали использовать записи Java 17 на протяжении всего курса, но потом столкнулись с двумя проблемами.
Во-первых, библиотека Google Gson еще не поддерживает (де-) сериализацию записей, поэтому вместо этого нам пришлось бы переключиться на Jackson (вероятно, следовало бы это сделать).
И результаты драйвера Java Neo4j не могли быть были просто конвертированы в экземпляр записи, как нам хотелось.
Поскольку мы не хотели добавлять много ненужных сложностей в образовательный код, особенно. если вы хотите сохранить однострочное лямбда замыкание для обратного вызова. Поэтому мы сохранили API toMap() , предлагаемый для результатов работы драйвера.
var movies = tx.run(query, params) .list(row -> row.get("movie") .computeOrDefault(v -> new Movie(v.get("title").asString(),v.get("tmbdId").asString()), v.get("published").asLocalDate())));
var movies = tx.run(query, params).list(row -> row.get("movie").toMap());
Тестирование
Мы использовали JUnit 5 для тестирования, что не составляло труда.
Поскольку мы хотели, чтобы один и тот же тест работал во всех ветвях репозитория, независимо от того, доступно соединение с базой данных или нет, мы использовали операторы Assume , чтобы пропустить несколько тестов и условие существования экземпляра драйвера для некоторого кода очистки в методы настройки @BeforeClass/Before .
В курсе для запуска тестов используется команда mvn test -Dtest=neoflix.TestName#testMethod , с помощью которой участник курса мог проверить свой прогресс и правильность выполнения.
Некоторые тесты также выводят результаты, которые пользователь должен заполнить в викторинах во время курса.
Вывод
Для реального приложения мы могли бы использовать одну из более крупных фреймворков приложений, так как нужно удовлетворить больше потребностей.
Кроме того, повторное использование простых запросов Cypher для операций, а также сопоставление и обработка ошибок должны были обрабатываться кодом инфраструктуры, который мы обычно рефакторим.
Но, в образовательных целях, мы оставили его в каждом сервисе.
Не стесняйтесь проверить курс Neo4j Java и приложение Neoflix
Если вам интересно переписать этот пример с любой другой веб фреймворк: Spring Boot, Quarkus, Micronaut, vert.x, Play и т. д., сообщите нам и поделитесь репозиторием, чтобы мы могли добавить его как ветку.
How do I export Cypher Favorites recorded in the browser
Cypher Favorites are common Cypher statements which one can save to the left panel of the Neo4j browser. A Favorite is created by entering the Cypher at the top prompt and then clicking the Favorite icon to the right as depicted:

After clicking on the Favorite Icon, the title of the Cypher statement (in this case ‘my favorite Cypher’) will be added to the left panel of the browser. For example:

These favorites are stored in local browser storage and as such are centric to the user/browser who has recorded those favorites. Additionally, if one clears their browser cache, recorded favorites will be removed.
To export favorites, use the Developer Console of Google Chrome and connect to the Neo4j browser URL.
- Launch Google Chrome and connect to the Neo4j browser on http://localhost:7474
- Access the Developer Console, https://developer.chrome.com/devtools/docs/console within Google Chrome
- Use the keyboard shortcut Command + Option + J (Mac) or Control + Shift + J (Windows/Linux).
var res = JSON.parse(localStorage.getItem('neo4j.documents')) for (x in res)

Is this page helpful?
NEO4J – графовые базы данных

В данной статье будет рассмотрена графовая система управления базами данных в Neo4j, а именно:
- Установим и настроим Neo4j;
- Создадим базы данных с использованием Python;
- Познакомимся с базовым синтаксисом языка Cypher для создания запросов;
- Рассмотрим пример использования данной СУБД.
Что такое Neo4j или немного теории
Neo4j — это графовая система управления базами данных с открытым исходным кодом, реализованная на Java. Она является ведущей графовой СУБД в мире. Аналогами Neo4j являются Oracle NoSQL Database, HypherGraphDB, GraphBase, InfiniteGraph и AllegroGraph.
Всем известно, что такое граф, и из чего он состоит. Так вот, графовые базы данных представляет собой набор объектов, где объекты связаны между собой, подобно как в графе вершины (узлы), связанные через ребра (отношения).

Почему графы и где они используются?
Графы – это удобная и наиболее понятная визуальная система описания данных. Обычно они используются в геоинформационных системах, логистике, социальных сетях и т.п.
На этом теории достаточно, переходим к практике
Итак, для того, чтобы работать с любой базой данных, необходимы данные, как бы тавтологично это не звучало. Для примера, будем использовать уже готовые таблицы (формат CSV) с данными, которые содержать информацию о работе интернет-магазина. Все необходимые данные находятся тут.

Ход работы:
- Скачиваем среду разработки Neo4j Desktop с официального сайта. Больше информации можно прочесть в гайде для разработчиков
- Создаем базу данных
Кликаем на «New» чтобы создать новый проект. После нажимаем на Add -> local DBMS и уже в самом конце называем её (больше информации тут). После всех проделанных действий нам необходимо запустить её и открыть Neo4j Browser (большая синяя кнопка с надписью «Open»).

3. Добавление администратора.
В Neo4j browser необходимо ввести команду server user add, для того, чтобы добавить пользователя и после заполнить все поля. В поле roles нужно выбрать admin.

4. Python + Neo4j. Подключение к СУБД.
Для того, чтобы подключится к базе данных, необходимо установить библиотеку neo4j.
pip install neo4j
После импортируем GraphDatabase для подключения к СУБД.
from neo4j import GraphDatabase
Далее представлен код для подключения
class Neo4jConnection: def __init__(self, uri, user, password): self.driver = GraphDatabase.driver(uri, auth=(user, password)) def close(self): if self.driver is not None: self.driver.close() # Метод, который передает запрос в БД def query(self, query, db=None): assert self.driver is not None, "Driver not initialized!" session = None response = None try: session = self.driver.session(database=db) if db is not None else self.driver.session() response = list(session.run(query)) except Exception as e: print("Query failed:", e) finally: if session is not None: session.close() return response
Запросы в Neo4j пишутся на языке Cypher. Он интуитивно понятный для освоения. Документацию по этому языку можно прочитать перейдя по ссылке.
Итак, подключаемся к СУБД. Создаем объект connection и передаем имя пользователя и пароль (который создавали при регистрации пользователя). Далее передаем запрос на создание самой базы данных «graphDb» — это название БД.
conn = Neo4jConnection(uri="bolt://localhost:7687", user="Mario", password="123123") conn.query("CREATE OR REPLACE DATABASE graphDb")
Далее необходимо загрузить все данные из таблицы в БД (загружаем таблицу Product). Для этого пишем запрос:
query_string = ''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/bd/main/Product.csv' AS line FIELDTERMINATOR ',' MERGE (product:Product ) ON CREATE SET product.productName = line.ProductName, product.UnitPrice = toFloat(line.UnitPrice); ''' conn.query(query_string, db='graphDb')
Как работает данный запрос? Он загружает CSV-файл из github, после построчно считывает данные, разделяя их по запятой (FIELDTERMINATOR ‘,’). Далее связываем узлы (объекты) и создаем свойства объекта
product.productName = line.ProductName
После запускаем код и переходим в Neo4j browser. В окне «Node Labels» кликаем по появившейся кнопке «Product».

Поздравляю! Вывели узлы Product. Если нажать на один узел, то в окне справа появятся все свойства (productName, productID, UnitPrice), которые мы прописывали в запросе.
Далее создаем запросы на загрузку остальных данных из других таблиц. Код можно посмотреть в GitHub. В итоге в левом окне должно быть 5 узлов:

Можно вывести все узлы в одно окно. Для этого необходимо прописать следующий запрос:
MATCH (n) RETURN n
Пока наши узлы (объекты) не имеют отношений с другими узлами.
5. Запросы на создание отношений.
Допустим, нам нужно посмотреть какие заказы выполнили сотрудники. Для этого нужно связать узлы (объекты) Order и Employee. Создаем следующий запрос:
query_string =''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/bd/main/Order.csv' AS line MATCH (order:Order ) MATCH (employee:Employee ) CREATE (employee)-[:SOLD]->(order); ''' conn.query(query_string, db='graphDb')
MATCH – это основной способ получения данных в текущем наборе привязок. С помощью CREATE создаем саму связь, синтаксис напоминает строение графа, в «[]» прописываем название связи. Более наглядно это можно продемонстрировать следующим образом:


Данный запрос создаст связь, которая отобразиться в левом окне. Эта связь показывает отношение между сотрудником и заказами, который выполнил сотрудник. Например, Aleekseev -> SOLD-> Grandma’s Bakery, означает, что сотрудник Алексеев продал что-то Grandma’s Bakery (это что-то мы узнаем чуть позже).
Чтобы узнать какие данные хранятся в узле, достаточно просто на него нажать и в правом окне появится информация об объекте. Важно отметить, что здесь появляется информация, а именно свойства, которые прописывали в запросе.

Теперь нам необходимо узнать, какой именно продукт приобрели в заказе. Для этого пишем следующий запрос:
query_string = ''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/bd/main/Order.csv' AS line MATCH (order:Order ) MATCH (product:Product ) MERGE (order)-[op:PURCHASED]->(product) ON CREATE SET op.quantity = toFloat(line.Quantity); ''' conn.query(query_string, db='graphDb')

И так, у нас есть связи сотрудника с заказом и с проданным в заказе продуктом. Теперь создадим связь между продуктом и поставщиком, а также свяжем его с категорией товара. Для этого выполните два запроса:
query_string = ''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/bd/main/Product.csv ' AS line MATCH (product:Product ) MATCH (supplier:Supplier ) MERGE (supplier)-[:SUPPLIES]->(product); ''' conn.query(query_string, db='graphDb'

query_string = ''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/bd/main/Product.csv' AS line MATCH (product:Product ) MATCH (category:Category ) MERGE (product)-[:IS_CATEGORY]->(category); ''' conn.query(query_string, db='graphDb')

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

Давайте проанализируем сотрудника Timofeeva. Из графа видно, какие именно товары она продала «Fresh product», и кто был поставщиком данных товаров.

Также помним, что это все-таки база данных, и здесь можно узнать имя, фамилию и должность сотрудника — кликнув по объекту.

6. Запросы для поиска конкретной информации.
До этого все запросы писались через Python, но их можно создавать в самой Neo4j browser (в строке сверху).

Допустим, нужно найти конкретного поставщика (например, Peter ‘s Lands), и какие продукты он поставляет. Пишем запрос:
MATCH (s:Supplier)-[r1:SUPPLIES]->(p:Product) RETURN s, r1, p;

Необязательно всегда выводить нужную информацию в графах, можно и в таблице. Например, выведем информацию о том какие продукты продал сотрудник Nekrasov.
MATCH (p:Product)<-[:PURCHASED]-(:Order)<-[:SOLD]-(em:Employee) RETURN em.lastName + ' '+em.firstName as Full_name, p.productName as sold_products
Результатом этого запроса будет:

Разница в том, какие данные возвращаем. Если это свойства объекта, то результатом выполнения запроса будет таблица.
Также мы можем выгрузить данные из БД. Для этого импортируем pandas.
query_string = ''' MATCH (em:Employee) RETURN em.firstName, em.lastName, em.post ''' dtf_data = DataFrame([dict(_) for _ in conn.query(query_string, db='graphDb')]) print(dtf_data)

Вы можете создавать любые запросы, и для этого в документации есть очень хорошая карта со всем синтаксисом Cypher Refcard.
После того, как рассмотрели базовые вопросы, касаемые Neo4j, нужно разобраться с вопросом, где же все-таки применяются графовые базы данных в реальных проектах? Один из таких примеров — это рекомендации по предложению продукта (товара). Мы же рассмотрим пример «Рекомендация статей для читателей» с использование Neo4j.
Пример «Рекомендация статей для читателей»
Суть нашей системы рекомендаций статей следующая: необходимо предлагать нашим читателям альтернативную статью. Например, если читатель проявляет интерес к определенной категории статей, можно создать рекомендацию, содержащую альтернативные статьи.
Чтобы показать, как это работает, давайте создадим модель данных:
Наша система рекомендаций будет содержать следующие объекты с атрибутами:
- articles – title,
- category_articles – title,
- reader – name, email, nickname,
- RecommendationArticleToReader – nickname
Отношения будут следующие:

На этом у нас закончилось моделирование сущностей.
Также стоит обратить внимание, что в Neo4j нет необходимости моделировать двунаправленные отношения, например, статья находится в категории, а категория имеет множество статей. Графические базы данных позволяют отслеживать ребра в обоих направлениях.
Кстати одним из преимуществ графических баз данных от реляционных в том, что мы можем добавлять, изменять и удалять сущности (узлы) и связи (ребра), не беспокоясь о внешних ключах.
2. Работа в Neo4J:
Заполняем нашу БД в соответствии с моделью, которую мы определили выше. Запросы будем писать в Neo4j Browser.
Загружаем данные в формате csv c github:
# LOAD CATEGORY query_string = ''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/ArticleBD/main/Category.csv' AS line FIELDTERMINATOR ',' MERGE (category:Category ) ON CREATE SET category.title = line.title; ''' conn.query(query_string, db='graphDb') # LOAD ARTICLES query_string = ''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/ArticleBD/main/Articles.csv' AS line FIELDTERMINATOR ',' MERGE (article:Article ) ''' conn.query(query_string, db='graphDb') # LOAD READER query_string = ''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/ArticleBD/main/Reader.csv' AS line FIELDTERMINATOR ',' MERGE (reader:Reader ) ON CREATE SET reader.nickname = line.nickname,reader.email = line.email; ''' conn.query(query_string, db='graphDb')
Далее создаем отношения article-[:IS_IN]-> category и reader-[:READ]->article:
# LOAD CATEGORY_ARTICLES query_string =''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/ArticleBD/main/Category_articles.csv' AS line MATCH (category:Category ) MATCH (article:Article ) CREATE (article)-[:IS_IN]->(category); ''' conn.query(query_string, db='graphDb') # LOAD READ_ARTICLES query_string =''' LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/Mario-cartoon/ArticleBD/main/read_articles.csv' AS line MATCH (reader:Reader ) MATCH (article:Article ) CREATE (reader)-[:READ]->(article); ''' conn.query(query_string, db='graphDb')

3. Создадим рекомендации статей для читателей основываясь на прочитанных статьях. Для этого пишем запрос:
MATCH (reader:Reader) MATCH (unread_article:Article) WHERE NOT ((reader)-->(unread_article)) MATCH (article:Article) WHERE ((reader)-->(article)) MATCH (unread_article)-[:IS_IN]->()) WITH rec, unread_article, reader MERGE(rec)-[rel:USED_TO_PROMOTE ]->(unread_article) RETURN rec, unread_article, rel;
В запросе отбираем прочитанные статьи от непрочитанных, создавая новую сущность RecommendationArticleToReader, которая содержит nickname читателей. После создаем новую связь: RECOMMEND_READING, где связываем не прочитанную статью, той же категории, что и статьи, которые уже прочитал читатель. В итоге получим следующий граф:

Обращаю внимание, что в связи: RECOMMEND_READING передается email читателя.
Итак, теперь выведем все связи и сущности, а также проанализируем получившийся граф, а именно, возьмем одного читателя и посмотрим все связи. Для примера возьмем Ivan Ivanov. Из графа видно, что он прочёл, и какие статьи ему предлагается прочитать.

Давайте напишем запрос чтобы вывести только рекомендации по читателю Ivan Ivanov:
MATCH (rec: RecommendationArticleToReader )-[:RECOMMEND_READING]->(article:Article) RETURN article,rec;

4. Также можно попробовать рекомендовать статьи, основываясь на прочтенных статьях других читателей, которые имеют хотя бы одну прочтённую статью с другим читателем. Для этого пишем запрос, который выведет всех читателей и прочтенные статьи:
MATCH (reader:Reader)-->(article:Article) RETURN reader, article;

Из графа видно, что у многих читателей есть общие прочитанные статьи, и мы можем рекомендовать им те статьи, которые прочёл читатель (с которым есть общая прочтённая статья), но не прочел другой. Для примера возьмем читателя Ivan Ivanov. Пишем запрос:
MATCH (reader_1:Reader)-->(article_1:Article)(article_2:Article) WHERE (article_2 <> article_1) RETURN reader_1, reader_2, article_2,article_1;
С помощью этого запроса мы возвращаем статью, не прочитанную Ivan’ом, но прочитанную читателем, с которым у Ivan’a есть общая прочитанная статья. Т.е. в переменной article_2 хранится статья, не прочитанная Ivan’ом. Для примера возвратили все переменные чтобы увидеть связь между сущностями.

Но, чтобы вывести только нужные статьи, достаточно вернуть только переменную article_2.
В данной статье был рассмотрен базовый функционал Neo4j и примеры его использования. Это очень удобный и понятный инструмент графовых баз данных. Но использовать его стоит только для решения задач, которые решаются с использованием графов. Важно также отметить, что Neo4j это не фреймворк и сравнивать его с gephi или networkx нет смысла. Потому что Neo4j не просто визуализирует данные, но и содержит их, так как является базой данных.
- Python
- Программирование
- Визуализация данных
Neo4j Browser User Interface Guide
This page is no longer being maintained and its content may be out of date. For the latest guidance, please visit the Getting Started Manual .
This article demonstrates how to use the Neo4j Browser for querying, visualization, and data interaction.
Prerequisites
Please launch an AuraDB Instance or have Neo4j downloaded and installed. It also helps if you have read the section on graph databases.
Neo4j Browser is an interactive cypher command shell that allows you to interact with your graph and visualize the information in it. Neo4j Browser is bundled with Neo4j and is available in all editions and versions of Neo4j.

Launch Neo4j Browser
If you using AuraDB, go to the Aura Console, and find Neo4j Browser in the «Open» button.

If you are using Neo4j Desktop, you can use the graph apps tab on the left hand side (which looks like four squares) to find Neo4j Browser, which will connect to any running database that you have.

If you are running Neo4j in another environment, Neo4j Browser is available via HTTP at http://localhost:7474 and HTTPS at https://localhost:7473 , substituting the right address for your environment.
Using Built-In Guides
Neo4j Browser contains built-in guides which introduce different concepts. Now that we can access the graph, we can use these guides to start working with data using Cypher.
To use one of these guides, just type the command (such as :play intro ) into the command line on the right of Neo4j Browser and press Enter or click the play button ( ).
Title
Description
Command
A guided tour of Neo4j Browser
Graph database basics
Neo4j’s graph query language introduction
The Movie Graph
A mini graph model of connections between actors and movies
:play movie graph
The Northwind Database
A classic use case of RDBMS to graph with import instructions and queries
:play northwind graph
The full list of available browser guides can be found here. You can also create your own custom browser guides to share learning with colleagues, students, and others in the community.
If you’d like to pin guides to the top so they do not get pushed down as you run additional queries, just use the pushpin button ( ) in Neo4j Browser.
Graph Metadata
In the left menu, the top icon is the database section ( ), where you can find the currently used node labels, relationship types, and property keys. Clicking on any one of those options runs a quick query to show you a sample of the graph with those elements.
Change Graph Appearance
Any query you run in Neo4j Browser will populate your results below the command line. You can switch between visual graph, table format, and ascii-table results with the icons on the left side of the result frame. You can also adjust by moving the view or dragging nodes to re-arrange them. To see more of the graph, click any empty spot and drag it.
Nodes are assigned captions by the browser, which selects a property to use. These properties appear below the visualization when a node is selected. Larger property sets may be collapsed into a subset, but there is an option to expand them.

For example, if you click on one of the Movie nodes in the MovieGraph ( :play movie graph ), then you can see its properties below the graph visual. The same applies for Actor nodes or the ACTED_IN relationships. If you click on any label or relationship above the graph visualization, you can then choose its styling in the area below the graph. Colors, sizes, and captions are selectable from there.
To see this for yourself, you can click on the Person label above the graph and change the color, size, and captions of all nodes labeled with Person . The first image below shows changes to nodes labeled Person . The second image shows changes to relationships labeled ACTED_IN .

Commands and Keyboard Shortcuts
Styling Popup & Reset
Ctrl + Enter or Cmd + Enter
Ctrl + Up or Cmd + Up
Enter Multiline Mode
Move Focus to Editor
Toggle Editor to Full Screen
Query and Command-Line Tips
Query Tips
You can remove all accumulated output frames with :clear . The ‘X’ button at the top right of each pane removes that frame and aborts a (long-)running statement. The maximum number of frames that are kept is configurable in the Browser Settings from the left-side menu.
If you want to review a past query, you can find the result pane and click the query above the graph visualisation to pull it back into the editor. The keyboard shortcuts listed above will help you work efficiently within the editor area.
You can also write and edit multi-line queries by switching to multi-line editing mode with Shift + Enter , then Enter will create newlines. You then need to run Ctrl + Enter or Cmd + Enter to run multi-line queries.
Command Tips
Ctrl + Up and Ctrl + Down (Mac users, use the Cmd key) allows you to navigate command history, and you can access all command history with :history . The command history is persisted across Browser restarts.
Output, Export, & Visualization Tips
You can switch between Graph , Table , Text , and Code views to see the results in various formats by clicking the icons on the left of each pane.
Don’t worry if you don’t see any output. You might just be in Graph mode, but had your query return tabular/scalar data. To see the results, just switch the mode to the Table view.
Query time is reported in the Table or Code views (don’t rely on that exact timing though), and it includes the latency and deserialization costs, not just the actual query execution time.
You can also export the results of queries as a CSV or JSON, and for the graph view, you may also export a PNG or SVG image, as shown below.

If you enter fullscreen mode of a graph visualization, you can zoom in and out. After a node is clicked, it gets a halo, where you can expand and remove nodes from the visualization. You can also turn previously dragged nodes loose again.
Setting Favorites
If you currently have an empty frame, you can display some nodes and relationships by using the Favorites ( ) in the left-side menu. Neo4j stores a few default favorites to get you started. Just click on the Basic Queries, then choose Get Some Data and run the query. This executes the statement MATCH (n) RETURN n limit 100 , which fetches some nodes.
You can save your own queries as favorites by «starring» them. Just populate the Browser command line with the query you want to favorite, then click the Favorites ( ) icon to the right of the command line. This will add the query to your Favorites list in the left-side menu. To run one of your Favorites, click on the left-side menu Favorites, choose the query, and run it.
To provide a title or helpful info, you can use a comment // comment above your query to provide a title. The Favorites menu uses your comment as the query name.
Creating folders can help organize your favorites, and you can rearrange them by dragging or delete them if they are no longer useful.
Advanced Styling
For more advanced styling, you can bring up the style-viewer with :style and copy/paste the graph-style-sheet (GRASS) that is returned. You can edit this stylesheet offline, save the file as a .grass file, and drag it back onto the drag-area of the viewer.
| You can reset to the default styles with :style reset . |
Within the GRASS file, you can change colors, fonts, sizes, outlines, and titles per node label and relationship type. It is also possible to combine multiple properties into a caption with caption: ‘, born in ‘;
Configuration
The defaults for all the settings can adjusted at any time by going to the configuration option on the left-side menu. Some possible config changes and views are listed below.
- You can retrieve the current configuration with :config .
- Individual settings are configured with the following defaults:
- :config maxNeighbours:100 — maxiumum number of neighbours for a node
- :config maxRows:100 — maximum number of rows for the tabular result
You can also see current stats on your database, such as store sizes, ID allocation, page cache, and transaction info. To do this, just type the command :sysinfo on the command line.

Executing REST requests
You can also execute REST requests with Neo4j Browser. The command syntax is :COMMAND /a/path . The available commands are :GET , :POST , :PUT and :DELETE .
A simple query like :GET /db/data/ inspects the available endpoints of the database, with the returned results formatted in JSON. Then, you can retrieve all labels in the database with :GET /db/data/labels .
To execute a Cypher statement, you post to the transaction Cypher endpoint like this:
:POST /db/data/transaction/commit <"statements":[ <"statement":"MATCH (m:Movie) WHERE m.title=RETURN m.title, m.released, labels(m)", "parameters":>]>
There are endless possibilities to send and retrieve data using REST. In a later guide, you can create an application to interact with Neo4j and use REST endpoints for interaction between you and the database. See the Language Guides section for more information.
Further Information
- Neo4j Browser Manual
- Send Neo4j Browser feature requests
- Neo4j Browser change-log