Join Parent child
Preface
The bottom layer of ES is Lucene. Since Lucene does not actually support nested types, all documents are stored in Lucene in a flat structure. ES's support for parent and child documents is actually implemented in an opportunistic way.
The parent and child documents are stored as independent documents, and then the association relationship is added, and the parent and child documents must be in the same shard. Because the parent-child type document does not reduce the number of documents, and increases the parent-child binding relationship, it will lead to low query efficiency, so we It is not recommended that you use parent-child types in actual development.
ES itself is more suitable for the "large wide table" mode. Don't use ES with the way of thinking of traditional relational databases. We can complete it by merging the fields and contents of multiple tables into one table (an index). Expect functions and avoid the use of parent-child types as much as possible, which is not only efficient, but also more powerful.
Of course, it is reasonable to exist, and there are indeed some scenarios where parent-child types are inevitably used. As the world's leading ES-ORM framework, we also provide support for this. Users can not use it, but we can't live without it!
Regarding the choice of parent-child type and nested type: If the document is written more than read, then it is recommended that you choose the parent-child type, if the document is read more than written, then please choose the nested type.
# parent-child type to create index
- Automatic transmission mode:
/**
* parent document
*/
@IndexName(childClass = Comment.class)
public class Document{
// Omit other fields...
/**
* The type of Join, and its parent name and child name must be indicated in the entity class of the parent document and child document through annotations. The JoinField class framework here is built-in, and there is no need to repeat the wheel
* The full path of the JoinField class is cn.easyes.common.params.JoinField. If you have to build your own wheels and support it, you need to specify joinFieldClass=the wheel you built in the @TableField annotation
*/
@IndexField(fieldType = FieldType.JOIN, parentName = "document", childName = "comment")
private JoinField joinField;
}
/**
* subdocument
*/
@IndexName(child = true)
public class Comment {
// Omit other fields...
/**
* The parent-child relationship field must be marked as Join in the entity class of the parent document and child document through annotations, and the parent-child relationship in the child document can be omitted
*/
@IndexField(fieldType = FieldType.JOIN)
private JoinField joinField;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Note: Be sure to add the annotation @TableName to the class of the parent document to indicate its child document class, and add the annotation @TableName to the class of the child document to indicate child=true as in the above example, and add the annotation @TableName to the class of the JoinField class. The specified type in the @TableField annotation is fieldType=JOIN and its parentName, childName, otherwise the framework will not work properly
- Manual transmission mode
- method one: According to the automatic mode, configure the annotation, and then directly call the one-click generation API to generate the index (v0.9.30+ version support)
documentMapper.createIndex();
- Method 2: Purely handmade, all fields are arranged by yourself, not recommended, very troublesome
LambdaEsIndexWrapper<Document> wrapper = new LambdaEsIndexWrapper<>();
// omit other code
wrapper.join("joinField", "document", "comment");
2
3
Note
In the manual mode, the annotations on the main class are still indispensable. The parent-child relationship needs to be used when the framework is running. In the second mode, the nested field needs to be specified through the wrapper, and then the index creation/update is completed.
# Parent-child type CRUD
Note that since the parent and child types are independent documents and independent entity classes, each needs to have its own mapper
CRUD example:
@Test
public void testInsert() {
// Test the new parent-child document, where the automatic block mode is turned on, and the parent-child type index has been automatically processed
// Newly add the parent document, and then insert the child document
Document document = new Document();
document.setId("1");
document.setTitle("Title of the parent document");
document.setContent("Content of the parent document");
JoinField joinField = new JoinField();
joinField.setName("document");
document.setJoinField(joinField);
documentMapper.insert(document);
// insert subdocument
Comment comment = new Comment();
comment.setId("2");
comment.setCommentContent("Document Comment 1");
// Special attention here, the child document must specify the id of its parent document, otherwise the parent document cannot be found, don't blame me for not reminding me
joinField.setParent("1");
joinField.setName("comment");
comment.setJoinField(joinField);
commentMapper.insert(comment);
// insert subdocument 2
Comment comment1 = new Comment();
comment1.setId("3");
comment1.setCommentContent("Document Comment 2");
comment1.setJoinField(joinField);
commentMapper.insert(comment1);
}
@Test
public void testSelect() {
// Warm reminder, the type in the wrapper below is actually the parentName and childName specified in the JoinField field annotation @TableField, which is consistent with the native syntax
// case1: hasChild query, which returns the related parent document, so the query uses the parent document entity and its mapper
LambdaEsQueryWrapper<Document> documentWrapper = new LambdaEsQueryWrapper<>();
documentWrapper.hasChild("comment", FieldUtils.val(Comment::getCommentContent), "comment");
List<Document> documents = documentMapper.selectList(documentWrapper);
System.out.println(documents);
// case2: hasParent query, which returns related subdocuments, so the query uses subdocument entities and their mappers
LambdaEsQueryWrapper<Comment> commentWrapper = new LambdaEsQueryWrapper<>();
// You can also use FieldUtils.val for the field name, or pass in a string directly
commentWrapper.hasParent("document", "content", "content");
List<Comment> comments = commentMapper.selectList(commentWrapper);
System.out.println(comments);
// case3: parentId query, which returns related subdocuments, similar to case2, so the query uses subdocument entities and their mappers
commentWrapper = new LambdaEsQueryWrapper<>();
commentWrapper.parentId("1", "comment");
List<Comment> commentList = commentMapper.selectList(commentWrapper);
System.out.println(commentList);
}
@Test
public void testUpdate() {
// case1: parent document/child document is updated according to their respective id
Document document = new Document();
document.setId("1");
document.setTitle("parent title");
documentMapper.updateById(document);
// case2: parent document/child document are updated according to their respective conditions
Comment comment = new Comment();
comment.setCommentContent("Updated Comment");
LambdaEsUpdateWrapper<Comment> wrapper = new LambdaEsUpdateWrapper<>();
wrapper.match(Comment::getCommentContent, "Comment");
commentMapper.update(comment, wrapper);
}
@Test
public void testDelete() {
// case1: parent document/child document are deleted according to their respective ids
documentMapper.deleteById("1");
//case2: The parent document/child document is deleted according to their respective conditions
LambdaEsQueryWrapper<Comment> wrapper = new LambdaEsQueryWrapper<>();
wrapper.match(Comment::getCommentContent, "Comment");
commentMapper.delete(wrapper);
}
2
3
4
5
6
7
8
9
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
80
81
For related demos, please refer to the test module of the source code -> test directory -> join package