Позвольте мне объяснить, как это работает, предоставив очень простые примеры. Как и в предыдущих статьях, мы будем создавать приложение Spring с использованием недавно выпущенной версии 1.0 проекта Spring Data MongoDB . Наш файл POM содержит очень простые зависимости, не более того.
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
4.0.0 mongodb com.example.spring 0.0.1-SNAPSHOT jar UTF-8 3.0.7.RELEASE org.springframework.data spring-data-mongodb 1.0.0.RELEASE org.springframework spring-beans org.springframework spring-expression cglib cglib-nodep 2.2 log4j log4j 1.2.16 org.mongodb mongo-java-driver 2.7.2 org.springframework spring-core ${spring.version} org.springframework spring-context ${spring.version} org.springframework spring-context-support ${spring.version} org.apache.maven.plugins maven-compiler-plugin 2.3.2 1.6 1.6 |
Чтобы правильно настроить контекст Spring , я буду использовать конфигурационный подход с использованием классов Java. Я все больше и больше выступаю за использование этого стиля, поскольку он обеспечивает строго типизированную конфигурацию, и большинство ошибок могут быть обнаружены во время компиляции, больше нет необходимости проверять ваши 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
28
29
30
31
32
|
package com.example.mongodb.hierarchical; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoFactoryBean; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.SimpleMongoDbFactory; @Configuration public class AppConfig { @Bean public MongoFactoryBean mongo() { final MongoFactoryBean factory = new MongoFactoryBean(); factory.setHost( "localhost" ); return factory; } @Bean public SimpleMongoDbFactory mongoDbFactory() throws Exception{ return new SimpleMongoDbFactory( mongo().getObject(), "hierarchical" ); } @Bean public MongoTemplate mongoTemplate() throws Exception { return new MongoTemplate( mongoDbFactory() ); } @Bean public IDocumentHierarchyService documentHierarchyService() throws Exception { return new DocumentHierarchyService( mongoTemplate() ); } } |
Это довольно мило и понятно. Спасибо, весна, ребята! Теперь все стандартные вещи готовы. Давайте перейдем к интересной части: документы. Наша база данных будет содержать коллекцию документов, в которой хранятся документы типа SimpleDocument. Мы описываем это, используя аннотации Spring Data MongoDB для SimpleDocument POJO.
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
57
58
59
60
61
62
63
|
package com.example.mongodb.hierarchical; import java.util.Collection; import java.util.HashSet; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; @Document ( collection = "documents" ) public class SimpleDocument { public static final String PATH_SEPARATOR = "." ; @Id private String id; @Field private String name; @Field private String path; // We won't store this collection as part of document but will build it on demand @Transient private Collection< SimpleDocument > documents = new HashSet< SimpleDocument >(); public SimpleDocument() { } public SimpleDocument( final String id, final String name ) { this .id = id; this .name = name; this .path = id; } public SimpleDocument( final String id, final String name, final SimpleDocument parent ) { this ( id, name ); this .path = parent.getPath() + PATH_SEPARATOR + id; } public String getId() { return id; } public void setId(String id) { this .id = id; } public String getName() { return name; } public void setName(String name) { this .name = name; } public String getPath() { return path; } public void setPath(String path) { this .path = path; } public Collection< SimpleDocument > getDocuments() { return documents; } } |
Позвольте мне объяснить несколько вещей здесь. Во-первых, путь магического свойства: это ключ для построения и запроса через нашу иерархию. Путь содержит идентификаторы всех родителей документа, обычно разделенные каким-то разделителем, в нашем случае просто . (точка) Хранение иерархических отношений документа таким способом позволяет быстро построить иерархию, искать и перемещаться. Во-вторых, обратите внимание на временную коллекцию документов : эта непостоянная коллекция создается постоянным провайдером и содержит все документы-потомки (которые, в случае, также содержат собственных потомков). Давайте посмотрим на это в действии, изучив реализацию метода find :
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
57
58
59
60
61
62
63
64
65
|
package com.example.mongodb.hierarchical; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; public class DocumentHierarchyService { private MongoOperations template; public DocumentHierarchyService( final MongoOperations template ) { this .template = template; } @Override public SimpleDocument find( final String id ) { final SimpleDocument document = template.findOne( Query.query( new Criteria( "id" ).is( id ) ), SimpleDocument. class ); if ( document == null ) { return document; } return build( document, template.find( Query.query( new Criteria( "path" ).regex( "^" + id + "[.]" ) ), SimpleDocument. class ) ); } private SimpleDocument build( final SimpleDocument root, final Collection< SimpleDocument > documents ) { final Map< String, SimpleDocument > map = new HashMap< String, SimpleDocument >(); for ( final SimpleDocument document: documents ) { map.put( document.getPath(), document ); } for ( final SimpleDocument document: documents ) { map.put( document.getPath(), document ); final String path = document .getPath() .substring( 0 , document.getPath().lastIndexOf( SimpleDocument.PATH_SEPARATOR ) ); if ( path.equals( root.getPath() ) ) { root.getDocuments().add( document ); } else { final SimpleDocument parent = map.get( path ); if ( parent != null ) { parent.getDocuments().add( document ); } } } return root; } } |
Как видите, чтобы получить один документ с целой иерархией, нам нужно выполнить всего два запроса (но более оптимальный алгоритм может сократить его до одного запроса). Вот пример иерархии и результат чтения корневого документа из MongoDB
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
|
template.dropCollection( SimpleDocument. class ); final SimpleDocument parent = new SimpleDocument( "1" , "Parent 1" ); final SimpleDocument child1 = new SimpleDocument( "2" , "Child 1.1" , parent ); final SimpleDocument child11 = new SimpleDocument( "3" , "Child 1.1.1" , child1 ); final SimpleDocument child12 = new SimpleDocument( "4" , "Child 1.1.2" , child1 ); final SimpleDocument child121 = new SimpleDocument( "5" , "Child 1.1.2.1" , child12 ); final SimpleDocument child13 = new SimpleDocument( "6" , "Child 1.1.3" , child1 ); final SimpleDocument child2 = new SimpleDocument( "7" , "Child 1.2" , parent ); template.insertAll( Arrays.asList( parent, child1, child11, child12, child121, child13, child2 ) ); ... final ApplicationContext context = new AnnotationConfigApplicationContext( AppConfig. class ); final IDocumentHierarchyService service = context.getBean( IDocumentHierarchyService. class ); final SimpleDocument document = service.find( "1" ); // Printing document show following hierarchy: // // Parent 1 // |-- Child 1.1 // |-- Child 1.1.1 // |-- Child 1.1.3 // |-- Child 1.1.2 // |-- Child 1.1.2.1 // |-- Child 1.2 |
Вот и все. Простая мощная концепция. Конечно, добавление индекса к свойству пути значительно ускорит запрос. Есть много улучшений и оптимизаций, но основная идея должна быть ясна сейчас.
Справка: Хранение иерархических данных в MongoDB от нашего партнера по JCG Андрея Редько в блоге Андрея Редько .