ЧЕЛОВЕК CI ТРЕБОВАНИЯ
Мы начнем с простого создания КИ человека. Полезно иметь людей в CMDB по разным причинам: они позволяют вам определять детализированные элементы управления доступом (например, Джим может развертывать такие-то приложения в среде разработки; Эрик может развертывать все, что он хочет, где угодно и т. Д.) .); они позволяют вам определять группы, которые будут получать уведомления о критических событиях и инцидентах; и т.п.
Наш сотрудник CI будет иметь имя пользователя, имя и фамилию, некоторые номера телефонов, адрес электронной почты, менеджера, прямые отчеты и, наконец, проекты, над которыми он работает. Нам нужно иметь возможность отображать людей в виде списка, отображать конкретного человека в подробном представлении, позволять пользователям создавать, редактировать и удалять людей и так далее. Вот, например, как будет выглядеть представление списка, по крайней мере сейчас:
А вот как будет выглядеть наше представление деталей:
Отношения между человеком и проектом имеют связанную роль. Эти отношения также являются основой для списка соавторов: два человека являются соавторами, если есть хотя бы один проект, членами которого они являются.
Наших простых требований должно быть достаточно, чтобы показать, каково это — писать код Spring Data Neo4j.
СОЗДАТЬ ЧЕЛОВЕК И ЛИЦА ПРОЕКТА ЧЛЕНСТВА
Сначала мы создадим Человека. Я подавил аннотации валидации и JAXB, поскольку они не имеют отношения к нашим текущим целям:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
package org.skydingo.skybase.model; import java.util.Set; import org.neo4j.graphdb.Direction; import org.skydingo.skybase.model.relationship.ProjectMembership; import org.springframework.data.neo4j.annotation.*; import org.springframework.data.neo4j.support.index.IndexType; @NodeEntity public class Person implements Comparable<Person> { @GraphId private Long id; @Indexed (indexType = IndexType.FULLTEXT, indexName = "searchByUsername" ) private String username; private String firstName, lastName, title, workPhone, mobilePhone, email; @RelatedTo (type = "REPORTS_TO" ) private Person manager; @RelatedTo (type = "REPORTS_TO" , direction = Direction.INCOMING) private Set<Person> directReports; @RelatedToVia (type = "MEMBER_OF" ) private Set<ProjectMembership> memberships; public Long getId() { return id; } public void setId(Long id) { this .id = id; } public String getUsername() { return username; } public void setUsername(String username) { this .username = username; } ... other accessor methods ... public Person getManager() { return manager; } public void setManager(Person manager) { this .manager = manager; } public Set<Person> getDirectReports() { return directReports; } public void setDirectReports(Set<Person> directReports) { this .directReports = directReports; } public Iterable<ProjectMembership> getMemberships() { return memberships; } public ProjectMembership memberOf(Project project, String role) { ProjectMembership membership = new ProjectMembership( this , project, role); memberships.add(membership); return membership; } ... equals(), hashCode(), compareTo() ... } |
Есть много аннотаций, которые мы используем, чтобы создать структуру. Начнем с узлов и их свойств. Затем мы рассмотрим простые отношения между узлами. Затем мы рассмотрим так называемые сущности отношений, которые в основном являются причудливыми отношениями. Во-первых, вот абстрактное представление нашей модели предметной области:
Теперь давайте посмотрим на некоторые детали.
Узлы и их свойства . Когда у нас есть объект на основе узла, сначала мы аннотируем его аннотацией @NodeEntity. Большинство простых свойств узлов (т. Е. Свойств, которые не являются связями с другими узлами) подойдут для поездки. Обратите внимание, что мне не нужно было комментировать firstName, lastName, email и т. Д. Spring Data Neo4j будет обрабатывать отображение там автоматически.
Однако есть несколько исключений. Во-первых, я добавил @GraphId в свое свойство id. Это говорит Spring Data Neo4j, что это идентификатор, который мы можем использовать для поиска. Другая — аннотация @Indexed, которая (неожиданно) создает индекс для рассматриваемого свойства. Это полезно, когда вам нужна альтернатива поиску по идентификатору.
Теперь посмотрим на отношения. Говоря в широком смысле, существуют простые отношения и более сложные отношения. Начнем с простых.
Простые отношения . На низком уровне Neo4j является базой данных графа, поэтому мы можем говорить о графе в теоретических терминах графа, таких как узлы, ребра, направленные ребра, DAG и все такое. Но здесь мы используем графики для моделирования предметной области, поэтому мы интерпретируем концепции низкоуровневых графов в терминах концепций моделирования более высокого уровня. Язык, который использует Spring Data Neo4j, — это «сущность узла» для узлов и «связь» для ребер.
Наш Person CI имеет простые отношения, называемые REPORTS_TO, которые связывают людей, чтобы мы могли моделировать иерархии отчетов. Person имеет два поля для этих отношений: manager и directReports. Это противоположные сайты с одинаковыми отношениями. Мы используем @RelatedTo (type = «REPORTS_TO») для аннотирования этих полей. У аннотации также есть элемент direction, значением по умолчанию которого является Direction.OUTGOING, что означает, что этот узел является хвостовым краем. Вот почему мы явно указываем direction = Direction.INCOMING для поля directReports.
Как это выглядит в базе данных? Неоклипс раскрывает все. Вот несколько примеров отношений отчетности (щелкните изображение, чтобы увеличить его):
(Немного в стороне: есть аннотация @Fetch — мы увидим ее чуть позже — которая говорит Spring Data Neo4j о необходимости загружать связанный объект. По какой-то причине мне не нужно использовать его для отношений менеджера и прямых отчетов, и я не уверен почему. Если кто-то знает, я был бы признателен за объяснение.)
Отношения сущностей . Помимо отношений REPORTS_TO между людьми, нам важны отношения MEMBER_OF между людьми и проектами. Это более интересно, чем отношение REPORTS_TO, потому что MEMBER_OF имеет ассоциированное свойство — роль — это аналогично добавлению столбца в таблицу ссылок в СУБД, как я упоминал в своем ответе Бригу в предыдущем посте. Метод Person.memberOf () предоставляет удобный способ назначить человека проекту, используя специальную «сущность отношений» ProjectMembership. Вот код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
package org.skydingo.skybase.model.relationship; import org.skydingo.skybase.model.Person; import org.skydingo.skybase.model.Project; import org.springframework.data.neo4j.annotation.*; @RelationshipEntity (type = "MEMBER_OF" ) public class ProjectMembership { @GraphId private Long id; @Fetch @StartNode private Person person; @Fetch @EndNode private Project project; private String role; public ProjectMembership() { } public ProjectMembership(Person person, Project project, String role) { this .person = person; this .project = project; this .role = role; } public Person getPerson() { return person; } public void setPerson(Person person) { this .person = person; } public Project getProject() { return project; } public void setProject(Project project) { this .project = project; } public String getRole() { return role; } public void setRole(String role) { this .role = role; } ... equals(), hashCode(), toString() ... } |
ProjectMembership, как Person, является сущностью, но это сущность отношений. Мы используем @RelationshipEntity (type = «MEMBER_OF»), чтобы пометить это как объект отношения, и, как и в случае с Person, мы используем @GraphId для свойства id. Аннотации @StartNode и @EndNode указывают хвостовую часть и голову соответственно. @Fetch говорит Spring Data Neo4j загружать узлы с нетерпением. По умолчанию Spring Data Neo4j не очень загружает отношения, поскольку рискует загрузить весь график в память.
СОЗДАЙТЕ ЧЕЛОВЕКА
Вот наш интерфейс PersonRepository:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
package org.skydingo.skybase.repository; import java.util.Set; import org.skydingo.skybase.model.Person; import org.skydingo.skybase.model.Project; import org.springframework.data.neo4j.annotation.Query; import org.springframework.data.neo4j.repository.GraphRepository; public interface PersonRepository extends GraphRepository<Person> { Person findByUsername(String username); @Query ( "start project=node({0}) match project<--person return person" ) Set<Person> findByProject(Project project); @Query ( "start person=node({0}) " + "match person-[:MEMBER_OF]->project<-[:MEMBER_OF]-collaborator " + "return collaborator" ) Set<Person> findCollaborators(Person person); } |
В последнем посте я отметил, что все, что нам нужно сделать, это расширить интерфейс GraphRepository; Spring Data генерирует реализацию автоматически.
Для findByUsername () Spring Data может выяснить, для чего предназначен запрос. Для двух других запросов мы используем @Query и язык запросов Cypher, чтобы указать желаемый набор результатов. {0} в запросах относится к параметру метода поиска. В запросе findCollaborators () мы используем [: MEMBER_OF], чтобы указать, каким отношениям мы хотим следовать. Они возвращают наборы вместо Iterables для устранения дубликатов.
СОЗДАТЬ ВЕБ-КОНТРОЛЛЕР
Мы не будем здесь описывать весь контроллер, но рассмотрим некоторые типичные методы. Предположим, что мы ввели PersonRepository в контроллер.
Создание человека . Чтобы создать человека, мы можем использовать следующее:
1
2
3
4
5
|
@RequestMapping (value = "" , method = RequestMethod.POST) public String createPerson(Model model, @ModelAttribute Person person) { personRepo.save(person); return "redirect:/people?a=created" ; } |
Еще раз, мы игнорируем проверку. Все, что нам нужно сделать, это вызвать метод save () в хранилище. Вот так обновления тоже работают.
Нахождение всех людей . Далее, вот как мы можем получить всех людей:
1
2
3
4
5
6
7
8
9
|
@RequestMapping (value = "" , method = RequestMethod.GET) public String getPersonList(Model model) { Iterable<Person> personIt = personRepo.findAll(); List<Person> people = new ArrayList<Person>(IteratorUtil.asCollection(personIt)); Collections.sort(people); model.addAttribute(people); return "personList" ; } |
Нам нужно проделать некоторую работу, чтобы получить Iterable, который PersonRepository.findAll () возвращает в нужный нам формат. IteratorUtil, который поставляется с Neo4j (org.neo4j.helpers.collection.IteratorUtil), помогает здесь.
Нахождение одного человека . Здесь мы хотим отобразить личные данные, которые мы создали выше. Как и в случае с findAll (), мы должны сделать некоторые массажи самостоятельно:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
@RequestMapping (value = "/{username}" , method = RequestMethod.GET) public String getPersonDetails( @PathVariable String username, Model model) { Person person = personRepo.findByUsername(username); List<ProjectMembership> memberships = CollectionsUtil.asList(person.getMemberships()); List<Person> directReports = CollectionsUtil.asList(person.getDirectReports()); List<Person> collaborators = CollectionsUtil.asList(personRepo.findCollaborators(person)); Collections.sort(directReports); Collections.sort(collaborators); model.addAttribute(person); model.addAttribute( "memberships" , memberships); model.addAttribute( "directReports" , directReports); model.addAttribute( "collaborators" , collaborators); return "personDetails" ; } |
Если вы хотите увидеть JSP, зайдите на сайт Skybase GitHub .
НАСТРОЙКА ПРИЛОЖЕНИЯ
Наконец, вот мой файл beans-service.xml:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation=" < context:property-placeholder location = "classpath:/spring/environment.properties" /> < context:annotation-config /> < context:component-scan base-package = "org.skydingo.skybase.service" /> < tx:annotation-driven mode = "proxy" /> < neo4j:config storeDirectory = "${graphDb.dir}" /> < neo4j:repositories base-package = "org.skydingo.skybase.repository" /> </ beans > |
Neo4j имеет базовую модель сопоставления на основе POJO и усовершенствованную модель сопоставления на основе AspectJ. В этом сообщении мы использовали базовый подход, основанный на POJO, поэтому нам не нужно включать конфигурацию, связанную с AspectJ, например <context: spring-configure>.
Там у вас есть это — Person CI при поддержке Neo4j. Удачного кодирования!
Чтобы увидеть код более подробно или принять участие в разработке Skybase, посетите сайт Skybase GitHub .
Справка: Моделирование домена с помощью Spring Data Neo4j от нашего партнера по JCG Вилли Уилера в блоге Skydingo .
Статьи по Теме :