Join parent child
Preface
The bottom layer of ES is Lucene. Since Lucene actually does not support nested types, all documents are stored in Lucene in a flat structure. ES's support for parent-child documents is actually implemented in an opportunistic way.
Parent-child documents are stored as separate documents, and then associations are added, and parent-child documents must be in the same shard. Since parent-child type documents do not reduce the number of documents, and increase 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 traditional relational database thinking. We can complete it by merging the fields and contents of multiple tables into one table (an index). Expected functions, avoiding the use of parent-child types as much as possible, are not only more efficient, but also more powerful.
Of course, it is reasonable to exist, and there are indeed certain scenarios where the parent-child type is inevitably used. As the world's leading ES-ORM framework, we also provide support for this. Users can do without 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, it is recommended that you choose the parent-child type. If the document is read more than written, then please choose the nested type.
# Create index for parent-child type
- Step 1: Add annotations and specify the parent-child relationship:
/**
* The Document root document has sub-documents Author (author) and Comment (comments), and Author also has a sub-document Contact (contact information)
*Join parent-child type structure is as follows
*Document
* / \
* Comment Author
*\
*Contact
* The above structure can be expressed by @Join annotation and @Node annotation. Please refer to the following case
**/
@Join(nodes = {@Node(parentClass = Document.class, childClasses = {Author.class, Comment.class}), @Node(parentClass = Author.class, childClasses = Contact.class)})
public class Document {
// Omit other irrelevant fields
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Note: Be sure to add the annotation @Join to the class of the parent document and the @Node annotation to express the parent-child structure like the example above. There is no need to add this annotation repeatedly to the class of the child document. It only needs to be added to the Root node. Just add this comment
- Step 2: Call the API to complete the index creation (no need to call if it is automatically blocked, the framework will be created automatically when the project is started)
// Manual block is created with one click through the root mapper
documentMapper.createIndex();
2
After creation, the parent-child type index structure is as shown below:
# Parent-child type CRUD
Note that since the parent and child types are independent documents and independent entity classes, each needs its own mapper.
CRUD example:
@Test
public void testInsert() throws InterruptedException {
//Add a new parent document, and then insert the child document
String parentId = "doc-1";
Document root = new Document();
root.setEsId(parentId);
root.setTitle("I am the title of the parent document");
root.setContent("father doc");
documentMapper.insert(FIXED_ROUTING, root);
Thread.sleep(2000);
//Insert subdocument 1
Comment nodeA1 = new Comment();
nodeA1.setId("comment-1");
nodeA1.setCommentContent("test1");
// Pay special attention here. The sub-document must specify that its route is the same as the parent document. Otherwise, the stupid son can't find his father. Don't blame me for not reminding him (ES syntax is like this, not frame restricted)
commentMapper.insert(FIXED_ROUTING, parentId, nodeA1);
//Insert subdocument 2
Comment nodeA2 = new Comment();
nodeA2.setId("comment-2");
nodeA2.setCommentContent("test2");
commentMapper.insert(FIXED_ROUTING, parentId, nodeA2);
//Insert subdocument 3
Author nodeB1 = new Author();
nodeB1.setAuthorId("author-1");
nodeB1.setAuthorName("tom");
authorMapper.insert(FIXED_ROUTING, parentId, nodeB1);
//Insert subdocument 4
Author nodeB2 = new Author();
nodeB2.setAuthorId("author-2");
nodeB2.setAuthorName("cat");
authorMapper.insert(FIXED_ROUTING, parentId, nodeB2);
Thread.sleep(2000);
//Insert grandson document 1 (hang grandson 1 on subdocument 3)
Contact child1 = new Contact();
child1.setContactId("contact-1");
child1.setAddress("zhejiang province");
contactMapper.insert(FIXED_ROUTING, nodeB1.getAuthorId(), child1);
//Insert grandson document 2 (hang grandson 2 on subdocument 3)
Contact child2 = new Contact();
child2.setContactId("contact-2");
child2.setAddress("hangzhou city");
contactMapper.insert(FIXED_ROUTING, nodeB1.getAuthorId(), child2);
//Insert grandson document 3 (hang grandson 3 on subdocument 4)
Contact child3 = new Contact();
child3.setContactId("contact-3");
child3.setAddress("binjiang region");
contactMapper.insert(FIXED_ROUTING, nodeB2.getAuthorId(), child3);
// There is a delay in es writing data. Sleep appropriately to ensure that subsequent query results are correct.
Thread.sleep(2000);
}
@Test
public void testSelect() {
// Warm reminder, the type in the wrapper below is actually the parent-child name specified in the index JoinField, which is consistent with the native syntax.
// case1: hasChild query returns the relevant parent document, so the query uses the parent document entity and its mapper
LambdaEsQueryWrapper<Document> documentWrapper = new LambdaEsQueryWrapper<>();
documentWrapper.hasChild("comment", w -> w.eq(FieldUtils.val(Comment::getCommentContent), "test1"));
List<Document> documents = documentMapper.selectList(documentWrapper);
System.out.println(documents);
LambdaEsQueryWrapper<Author> authorWrapper = new LambdaEsQueryWrapper<>();
authorWrapper.hasChild("contact", w -> w.match(FieldUtils.val(Contact::getAddress), "city"));
List<Author> authors = authorMapper.selectList(authorWrapper);
System.out.println(authors);
// case2: hasParent query returns the relevant subdocument, so the query uses the subdocument entity and its mapper
LambdaEsQueryWrapper<Comment> commentWrapper = new LambdaEsQueryWrapper<>();
commentWrapper.like(Comment::getCommentContent, "test");
// You don’t need to use FieldUtils.val for the field name, you can just pass it in the string directly.
commentWrapper.hasParent("document", w -> w.match("content", "father"));
List<Comment> comments = commentMapper.selectList(commentWrapper);
System.out.println(comments);
// case2.1: The grandson checks his father's situation
LambdaEsQueryWrapper<Contact> contactWrapper = new LambdaEsQueryWrapper<>();
contactWrapper.hasParent("author", w -> w.eq(FieldUtils.val(Author::getAuthorName), "cat"));
List<Contact> contacts = contactMapper.selectList(contactWrapper);
System.out.println(contacts);
//case2.2: abbreviation of 2.1
LambdaEsQueryWrapper<Contact> contactWrapper1 = new LambdaEsQueryWrapper<>();
// The reason why hasParent can not specify the parentType abbreviation is because the framework can automatically infer its parent type through the parent-child relationship specified in the @Join annotation. Therefore, users can directly query without specifying the parent type, but hasChild cannot be abbreviated because a father may have Multiple children, but a child can only have one biological father
contactWrapper1.hasParent(w -> w.eq(FieldUtils.val(Author::getAuthorName), "cat"));
List<Contact> contacts1 = contactMapper.selectList(contactWrapper1);
System.out.println(contacts1);
// case3: parentId query returns related subdocuments, similar to case2, so the query uses the subdocument entity and its mapper
commentWrapper = new LambdaEsQueryWrapper<>();
commentWrapper.parentId("doc-1", "comment");
List<Comment> commentList = commentMapper.selectList(commentWrapper);
System.out.println(commentList);
contactWrapper = new LambdaEsQueryWrapper<>();
contactWrapper.parentId("author-2", "contact");
List<Contact> contactList = contactMapper.selectList(contactWrapper);
System.out.println(contactList);
}
@Test
public void testUpdate() {
// case1: parent document/child document updated according to their respective ids
Document document = new Document();
document.setEsId("doc-1");
document.setTitle("I am the title of Lao Wang next door");
documentMapper.updateById(FIXED_ROUTING, document);
Contact contact = new Contact();
contact.setContactId("contact-2");
contact.setAddress("update address");
contactMapper.updateById(FIXED_ROUTING, contact);
// case2: parent document/child document updated according to respective conditions
Comment comment = new Comment();
comment.setCommentContent("update comment content");
LambdaEsUpdateWrapper<Comment> wrapper = new LambdaEsUpdateWrapper<>();
wrapper.eq(Comment::getCommentContent, "test1");
wrapper.routing(FIXED_ROUTING);
commentMapper.update(comment, wrapper);
}
@Test
public void testDelete() {
// case1: parent document/child document are deleted according to their respective ids
documentMapper.deleteById(FIXED_ROUTING, "doc-1");
//case2: parent document/child document deleted according to respective conditions
LambdaEsQueryWrapper<Comment> wrapper = new LambdaEsQueryWrapper<>();
wrapper.like(Comment::getCommentContent, "test")
.routing(FIXED_ROUTING);
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
For related demos, please refer to the test module of the source code->test directory->join package