«1. Введение
Эта статья посвящена Neo4j — одной из самых зрелых и полнофункциональных графовых баз данных на современном рынке. Графовые базы данных подходят к задаче моделирования данных с точки зрения, что многие вещи в жизни поддаются представлению в виде набора узлов (V) и связей между ними, называемых ребрами (E).
2. Встроенный Neo4j
Самый простой способ начать работу с Neo4j — использовать встроенную версию, в которой Neo4j работает на той же JVM, что и ваше приложение.
Во-первых, нам нужно добавить зависимость Maven:
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>3.4.6</version>
</dependency>
Вы можете проверить эту ссылку, чтобы загрузить последнюю версию.
Теперь давайте создадим фабрику:
GraphDatabaseFactory graphDbFactory = new GraphDatabaseFactory();
Наконец, мы создадим встроенную базу данных:
GraphDatabaseService graphDb = graphDbFactory.newEmbeddedDatabase(
new File("data/cars"));
Теперь можно начинать настоящее действие! Во-первых, нам нужно создать несколько узлов в нашем графе, и для этого нам нужно запустить транзакцию, поскольку Neo4j отклонит любую деструктивную операцию, если транзакция не была запущена:
graphDb.beginTx();
Как только у нас есть транзакция, мы можно начать добавлять узлы:
Node car = graphDb.createNode(Label.label("Car"));
car.setProperty("make", "tesla");
car.setProperty("model", "model3");
Node owner = graphDb.createNode(Label.label("Person"));
owner.setProperty("firstName", "baeldung");
owner.setProperty("lastName", "baeldung");
Здесь мы добавили узел Car со свойствами make и model, а также узел Person со свойствами firstName и lastName
Теперь мы можем добавить отношение:
owner.createRelationshipTo(car, RelationshipType.withName("owner"));
Оператор выше добавлено ребро, соединяющее два узла с меткой владельца. Мы можем проверить эту связь, выполнив запрос, написанный на мощном языке Cypher Neo4j:
Result result = graphDb.execute(
"MATCH (c:Car) <-[owner]- (p:Person) " +
"WHERE c.make = 'tesla'" +
"RETURN p.firstName, p.lastName");
Здесь мы просим найти владельца любого автомобиля марки tesla и вернуть нам его/ее имя и фамилию. Неудивительно, что это возвращает: {p.firstName=baeldung, p.lastName=baeldung}
3. Язык запросов Cypher
Neo4j предоставляет очень мощный и довольно интуитивно понятный язык запросов, который поддерживает полный спектр функций, которые можно ожидать от база данных. Давайте рассмотрим, как мы можем выполнить эти стандартные задачи создания, извлечения, обновления и удаления.
3.1. Create Node
Ключевое слово Create может использоваться для создания как узлов, так и отношений.
CREATE (self:Company {name:"Baeldung"})
RETURN self
Здесь мы создали компанию с одним именем свойства. Определение узла отмечается круглыми скобками, а его свойства заключаются в фигурные скобки. В этом случае self — это псевдоним узла, а Company — метка узла.
3.2. Создать отношение
Можно создать узел и отношение к этому узлу в одном запросе:
Result result = graphDb.execute(
"CREATE (baeldung:Company {name:\"Baeldung\"}) " +
"-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" +
"RETURN baeldung, tesla");
Здесь мы создали узлы baeldung и tesla и установили отношения владения между ними. Создание связей с уже существующими узлами, конечно же, также возможно.
3.3. Получить данные
Ключевое слово MATCH используется для поиска данных в сочетании с RETURN, чтобы контролировать, какие точки данных возвращаются. Предложение WHERE можно использовать для фильтрации только тех узлов, которые обладают нужными нам свойствами.
Выясним название компании, которой принадлежит tesla modelX:
Result result = graphDb.execute(
"MATCH (company:Company)-[:owns]-> (car:Car)" +
"WHERE car.make='tesla' and car.model='modelX'" +
"RETURN company.name");
3.4. Обновление узлов
Ключевое слово SET может использоваться для обновления свойств узла или меток. Давайте добавим пробег к нашей tesla:
Result result = graphDb.execute("MATCH (car:Car)" +
"WHERE car.make='tesla'" +
" SET car.milage=120" +
" SET car :Car:Electro" +
" SET car.model=NULL" +
" RETURN car");
Здесь мы добавляем новое свойство под названием milage, изменяем метки, чтобы они были как Car, так и Electro, и, наконец, мы полностью удаляем свойство модели.
3.5. Удалить узлы
Ключевое слово DELETE можно использовать для постоянного удаления узлов или связей с графа:
graphDb.execute("MATCH (company:Company)" +
" WHERE company.name='Baeldung'" +
" DELETE company");
Здесь мы удалили компанию Baeldung.
3.6. Привязка параметров
В приведенных выше примерах у нас есть жестко закодированные значения параметров, что не является лучшей практикой. К счастью, Neo4j предоставляет средство для привязки переменных к запросу:
Map<String, Object> params = new HashMap<>();
params.put("name", "baeldung");
params.put("make", "tesla");
params.put("model", "modelS");
Result result = graphDb.execute("CREATE (baeldung:Company {name:$name}) " +
"-[:owns]-> (tesla:Car {make: $make, model: $model})" +
"RETURN baeldung, tesla", params);
4. Драйвер Java
До сих пор мы рассматривали взаимодействие со встроенным экземпляром Neo4j, однако, по всей вероятности, для производства мы хотел бы запустить автономный сервер и подключиться к нему через предоставленный драйвер. Во-первых, нам нужно добавить еще одну зависимость в наш maven pom.xml:
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>1.6.2</version>
</dependency>
Вы можете перейти по этой ссылке, чтобы проверить наличие последней версии этого драйвера.
Теперь мы можем установить соединение:
Driver driver = GraphDatabase.driver(
"bolt://localhost:7687", AuthTokens.basic("neo4j", "12345"));
Затем создадим сеанс:
Session session = driver.session();
Наконец, мы можем выполнить несколько запросов:
session.run("CREATE (baeldung:Company {name:\"Baeldung\"}) " +
"-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" +
"RETURN baeldung, tesla");
Когда мы закончим всю нашу работу нам нужно закрыть и сеанс, и драйвер:
session.close();
driver.close();
5. Драйвер JDBC
«Также возможно взаимодействовать с Neo4j через драйвер JDBC. Еще одна зависимость для нашего pom.xml:
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-jdbc-driver</artifactId>
<version>3.4.0</version>
</dependency>
Вы можете перейти по этой ссылке, чтобы загрузить последнюю версию этого драйвера.
Далее установим соединение JDBC:
Connection con = DriverManager.getConnection(
"jdbc:neo4j:bolt://localhost/?user=neo4j,password=12345,scheme=basic");
Здесь con — это обычное соединение JDBC, которое можно использовать для создания и выполнения операторов или подготовленных операторов:
try (Statement stmt = con.
stmt.execute("CREATE (baeldung:Company {name:\"Baeldung\"}) "
+ "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})"
+ "RETURN baeldung, tesla")
ResultSet rs = stmt.executeQuery(
"MATCH (company:Company)-[:owns]-> (car:Car)" +
"WHERE car.make='tesla' and car.model='modelX'" +
"RETURN company.name");
while (rs.next()) {
rs.getString("company.name");
}
}
6. Отображение графов объектов
Object-Graph-Mapping или OGM — это метод, который позволяет нам использовать POJO нашего домена как сущности в базе данных Neo4j. Давайте рассмотрим, как это работает. Первым шагом, как обычно, мы добавляем новые зависимости в наш pom.xml:
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-core</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-embedded-driver</artifactId>
<version>3.1.2</version>
</dependency>
Вы можете проверить OGM Core Link и OGM Embedded Driver Link, чтобы проверить наличие последних версий этих библиотек.
Во-вторых, мы аннотируем наши POJO аннотациями OGM:
@NodeEntity
public class Company {
private Long id;
private String name;
@Relationship(type="owns")
private Car car;
}
@NodeEntity
public class Car {
private Long id;
private String make;
@Relationship(direction = "INCOMING")
private Company company;
}
@NodeEntity сообщает Neo4j, что этот объект должен быть представлен узлом в результирующем графе. @Relationship сообщает о необходимости создания отношения с узлом, представляющим связанный тип. В этом случае компания владеет автомобилем.
Обратите внимание, что Neo4j требует, чтобы у каждой сущности был первичный ключ, а поле с именем id выбиралось по умолчанию. Поле с другим именем можно использовать, аннотировав его @Id @GeneratedValue.
Затем нам нужно создать конфигурацию, которая будет использоваться для начальной загрузки OGM Neo4j. Для простоты давайте воспользуемся встроенной базой данных только в памяти:
Configuration conf = new Configuration.Builder().build();
После этого мы инициализируем SessionFactory с созданной нами конфигурацией и именем пакета, в котором находятся наши аннотированные POJO:
SessionFactory factory = new SessionFactory(conf, "com.baeldung.graph");
Наконец, мы можем создать сеанс и начать его использовать:
Session session = factory.openSession();
Car tesla = new Car("tesla", "modelS");
Company baeldung = new Company("baeldung");
baeldung.setCar(tesla);
session.save(baeldung);
Здесь мы инициировали сеанс, создали наши POJO и попросили сеанс OGM сохранить их. Среда выполнения Neo4j OGM прозрачно преобразовывала объекты в набор запросов Cypher, которые создавали соответствующие узлы и ребра в базе данных.
Если этот процесс кажется вам знакомым, значит, так оно и есть! Именно так работает JPA, с той лишь разницей, преобразуется ли объект в строки, которые сохраняются в СУБД, или ряд узлов и ребер сохраняется в графовой базе данных.
7. Заключение
В этой статье были рассмотрены некоторые основы граф-ориентированной базы данных Neo4j.
Как всегда, весь код в этой статье доступен на Github.