-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
405 lines (195 loc) · 579 KB
/
search.xml
File metadata and controls
405 lines (195 loc) · 579 KB
1
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>FastJson、JackJson 以及 Gson 的区别</title>
<link href="/2022/0632223.html"/>
<url>/2022/0632223.html</url>
<content type="html"><![CDATA[<h2 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h2><p>FastJson、JackJson 以及 Gson 是 Java 生态圈中三种常用的 Json 解析器,它们均可将 Java 对象序列化为 Json 格式的字符串,也可将 Json 字符串反序列化为 Java 对象。下面我们讨论一下三者在序列化和反序列化操作中的一些区别。 </p><blockquote><p>Spring Boot 默认使用 JackJson 作为 Json 解析器。 </p></blockquote><h2 id="序列化"><a href="#序列化" class="headerlink" title="序列化"></a>序列化</h2><p>FastJson 和 JackJson 会根据 getter 方法(如 getXXX)或 is 方法(如 isXXX)获取属性名(XXX),然后调用 getter 方法或 is 方法获取属性值,最终将属性和属性值添加到 Json 字符串中。而 Gson 则是通过遍历实体类中的所有属性并直接获取属性值(未调用 getter 方法)来完成序列化操作。</p><blockquote><p>isXXX 方法生效的前提条件是其返回值的类型为 boolean 或 Boolean。</p></blockquote><p>引入 FashJson 和 Gson 的依赖:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>dependency</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>groupId</span><span class="token punctuation">></span></span>com.alibaba<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>groupId</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>artifactId</span><span class="token punctuation">></span></span>fastjson<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>artifactId</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>version</span><span class="token punctuation">></span></span>1.2.58<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>dependency</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>groupId</span><span class="token punctuation">></span></span>com.google.code.gson<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>groupId</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>artifactId</span><span class="token punctuation">></span></span>gson<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>artifactId</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>version</span><span class="token punctuation">></span></span>2.8.9<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>创建实体类 User:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> Integer id<span class="token punctuation">;</span> <span class="token keyword">private</span> String name<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setId</span><span class="token punctuation">(</span>Integer id<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>id <span class="token operator">=</span> id<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setName</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> JsonProcessingException <span class="token punctuation">{</span> User user <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> user<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> user<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"John同学"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// FastJson 序列化</span> String json1 <span class="token operator">=</span> JSONObject<span class="token punctuation">.</span><span class="token function">toJSONString</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"FastJson 解析结果: "</span> <span class="token operator">+</span> json1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// Gson 序列化</span> Gson gson <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Gson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> String json2 <span class="token operator">=</span> gson<span class="token punctuation">.</span><span class="token function">toJson</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Gson 解析结果: "</span> <span class="token operator">+</span> json2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// JackJson 序列化</span> ObjectMapper objectMapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> String json3 <span class="token operator">=</span> objectMapper<span class="token punctuation">.</span><span class="token function">writeValueAsString</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"JackJson 解析结果: "</span> <span class="token operator">+</span> json3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试结果:</p><pre class="line-numbers language-bash"><code class="language-bash">FastJson 解析结果: <span class="token punctuation">{</span><span class="token punctuation">}</span>Gson 解析结果: <span class="token punctuation">{</span><span class="token string">"id"</span>:1,<span class="token string">"name"</span><span class="token keyword">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span>Exception <span class="token keyword">in</span> thread <span class="token string">"main"</span> com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found <span class="token keyword">for</span> class com.example.test.User and no properties discovered to create BeanSerializer <span class="token punctuation">(</span>to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>在 User 类中,我们并未添加任何的 getter 方法,因此 FastJson 返回了一个 “空串”。由于 Gson 不依赖 getter,所以它可以解析出完整的属性以及属性值。默认情况下,若 JackJson 未获取到任何属性(通过 getter 方法),那么程序将抛出异常。根据提示,我们可以通过关闭 <code>SerializationFeature.FAIL_ON_EMPTY_BEANS</code> 来忽略该异常。</p><p>关闭 <code>SerializationFeature.FAIL_ON_EMPTY_BEANS</code>:</p><pre class="line-numbers language-java"><code class="language-java">ObjectMapper objectMapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>objectMapper<span class="token punctuation">.</span><span class="token function">disable</span><span class="token punctuation">(</span>SerializationFeature<span class="token punctuation">.</span>FAIL_ON_EMPTY_BEANS<span class="token punctuation">)</span><span class="token punctuation">;</span>String json3 <span class="token operator">=</span> objectMapper<span class="token punctuation">.</span><span class="token function">writeValueAsString</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"JackJson 解析结果: "</span> <span class="token operator">+</span> json3<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>测试结果:</p><pre class="line-numbers language-bash"><code class="language-bash">FastJson 解析结果: <span class="token punctuation">{</span><span class="token punctuation">}</span>Gson 解析结果: <span class="token punctuation">{</span><span class="token string">"id"</span>:1,<span class="token string">"name"</span><span class="token keyword">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span>JackJson 解析结果: <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>下面我们为 User 类添加 getter 方法和 is 方法,再次测试序列化操作:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">isRemoved</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> Integer <span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> id<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> String <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> name<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试结果:</p><pre class="line-numbers language-bash"><code class="language-bash">FastJson 解析结果: <span class="token punctuation">{</span><span class="token string">"id"</span>:1,<span class="token string">"name"</span><span class="token keyword">:</span><span class="token string">"John同学"</span>,<span class="token string">"removed"</span>:false<span class="token punctuation">}</span>Gson 解析结果: <span class="token punctuation">{</span><span class="token string">"id"</span>:1,<span class="token string">"name"</span><span class="token keyword">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span>JackJson 解析结果: <span class="token punctuation">{</span><span class="token string">"id"</span>:1,<span class="token string">"name"</span><span class="token keyword">:</span><span class="token string">"John同学"</span>,<span class="token string">"removed"</span>:false<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>可见,FashJson 和 JackJson 均解析出了 removed 属性,虽然该属性并未在 User 类中定义,但由于我们添加了 isRemoved 方法且该方法的返回值类型为 boolean,因此 removed 会出现在 Json 串中。反观 Gson,其仅会将 User 类中定义的属性进行序列化。</p><h2 id="反序列化"><a href="#反序列化" class="headerlink" title="反序列化"></a>反序列化</h2><p>FastJson 和 JackJson 会根据 Json 字符串中的属性,在实体类中查找对应的 setter 方法来为该属性赋值。若未查找到属性对应的 setter 方法,那么 FastJson 会忽略该属性,Jackson 则会抛出异常。相比之下,Gson 会根据 Json 串中的属性以及属性值来填充实体类中对应的属性。</p><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> JsonProcessingException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// FastJson 反序列化</span> String json <span class="token operator">=</span> <span class="token string">"{\"id\":1,\"name\":\"John同学\",\"removed\":false}"</span><span class="token punctuation">;</span> User user1 <span class="token operator">=</span> JSONObject<span class="token punctuation">.</span><span class="token function">parseObject</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> User<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"FastJson 解析结果: "</span> <span class="token operator">+</span> user1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// Gson 反序列化</span> Gson gson <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Gson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> User user2 <span class="token operator">=</span> gson<span class="token punctuation">.</span><span class="token function">fromJson</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> User<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Gson 解析结果: "</span> <span class="token operator">+</span> user2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// JackJson 反序列化</span> ObjectMapper objectMapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> User user3 <span class="token operator">=</span> objectMapper<span class="token punctuation">.</span><span class="token function">readValue</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> User<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"JackJson 解析结果: "</span> <span class="token operator">+</span> user3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试结果:</p><pre class="line-numbers language-bash"><code class="language-bash">FastJson 解析结果: User<span class="token punctuation">{</span>id<span class="token operator">=</span>1, name<span class="token operator">=</span><span class="token string">'John同学'</span><span class="token punctuation">}</span>Gson 解析结果: User<span class="token punctuation">{</span>id<span class="token operator">=</span>1, name<span class="token operator">=</span><span class="token string">'John同学'</span><span class="token punctuation">}</span>Exception <span class="token keyword">in</span> thread <span class="token string">"main"</span> com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field <span class="token string">"removed"</span> <span class="token punctuation">(</span>class com.example.test.User<span class="token punctuation">)</span>, not marked as ignorable <span class="token punctuation">(</span>2 known properties: <span class="token string">"id"</span>, <span class="token string">"name"</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>上述代码中,我们在 Json 串中添加了一个 User 类中没有的属性,即 removed。FastJson 和 Gson 均会将该属性忽略,而 JackJson 则抛出了异常,该异常提示 “removed 为不能识别的属性”。当然,我们也可以通过配置 ObjectMapper 来忽略该异常:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// JackJson 反序列化</span>ObjectMapper objectMapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>objectMapper<span class="token punctuation">.</span><span class="token function">configure</span><span class="token punctuation">(</span>DeserializationFeature<span class="token punctuation">.</span>FAIL_ON_UNKNOWN_PROPERTIES<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>User user3 <span class="token operator">=</span> objectMapper<span class="token punctuation">.</span><span class="token function">readValue</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> User<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"JackJson 解析结果: "</span> <span class="token operator">+</span> user3<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试结果:</p><pre class="line-numbers language-bash"><code class="language-bash">FastJson 解析结果: User<span class="token punctuation">{</span>id<span class="token operator">=</span>1, name<span class="token operator">=</span><span class="token string">'John同学'</span><span class="token punctuation">}</span>Gson 解析结果: User<span class="token punctuation">{</span>id<span class="token operator">=</span>1, name<span class="token operator">=</span><span class="token string">'John同学'</span><span class="token punctuation">}</span>JackJson 解析结果: User<span class="token punctuation">{</span>id<span class="token operator">=</span>1, name<span class="token operator">=</span><span class="token string">'John同学'</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>虽然 FastJson 和 JackJson 在反序列化时均会依赖 setter 方法,但二者之间也存在一些区别。如果我们在实体类中未添加某个属性的 setter 方法,那么 FastJson 将不会为该属性赋值,而 JackJson 则会进一步查找同名的属性,接着完成赋值操作。下面我们注释掉 User 类中的 setName 方法:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// public void setName(String name) {</span><span class="token comment" spellcheck="true">// this.name = name;</span><span class="token comment" spellcheck="true">// }</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>再次测试反序列化操作:</p><pre class="line-numbers language-bash"><code class="language-bash">FastJson 解析结果: User<span class="token punctuation">{</span>id<span class="token operator">=</span>1, name<span class="token operator">=</span><span class="token string">'null'</span><span class="token punctuation">}</span>Gson 解析结果: User<span class="token punctuation">{</span>id<span class="token operator">=</span>1, name<span class="token operator">=</span><span class="token string">'John同学'</span><span class="token punctuation">}</span>JackJson 解析结果: User<span class="token punctuation">{</span>id<span class="token operator">=</span>1, name<span class="token operator">=</span><span class="token string">'John同学'</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>]]></content>
<categories>
<category> 随笔 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> Java </tag>
<tag> Json </tag>
</tags>
</entry>
<entry>
<title>聊一聊过滤器与拦截器</title>
<link href="/2022/054508.html"/>
<url>/2022/054508.html</url>
<content type="html"><![CDATA[<h2 id="过滤器-Filter"><a href="#过滤器-Filter" class="headerlink" title="过滤器 Filter"></a>过滤器 Filter</h2><blockquote><p>面试官:用过过滤器吧,介绍一下过滤器。<br>John同学(心中窃喜):用过,我经常用它来净化水 😁…<br>面试官:今天的面试到此结束,回去等通知吧。<br>John同学:🙃…</p></blockquote><h3 id="Filter-基本介绍"><a href="#Filter-基本介绍" class="headerlink" title="Filter 基本介绍"></a>Filter 基本介绍</h3><p>过滤器 Filter 是 Sun 公司在 Servlet 2.3 规范中添加的新功能,其作用是对客户端发送给 Servlet 的请求以及对 Servlet 返回给客户端的响应做一些定制化的处理,例如校验请求的参数、设置请求/响应的 Header、修改请求/响应的内容等。</p><p>Filter 引入了过滤链(Filter Chain)的概念,一个 Web 应用可以部署多个 Filter,这些 Filter 会组成一种链式结构,客户端的请求在到达 Servlet 之前会一直在这个链上传递,不同的 Filter 负责对请求/响应做不同的处理。 Filter 的处理流程如下图所示:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/Filter%E5%A4%84%E7%90%86%E8%AF%B7%E6%B1%82.jpg" alt></p><p>Filter 的作用可认为是对 Servlet 功能的增强,因为 Filter 可以对用户的请求做预处理,也可以对返回的响应做后处理,且这些处理逻辑与 Servlet 的处理逻辑是分隔开的,这使得程序中各部分业务逻辑之间的耦合度降低,从而提高了程序的可维护性和可扩展性。</p><h3 id="创建-Filter"><a href="#创建-Filter" class="headerlink" title="创建 Filter"></a>创建 Filter</h3><p>创建 Filter 需要实现 javax.servlet.Filter 接口,或者继承实现了 Filter 接口的父类。Filter 接口中定义了三个方法:</p><ul><li><p>init:在 Web 程序启动时被调用,用于初始化 Filter。</p></li><li><p>doFilter:在客户端的请求到达时被调用,doFilter 方法中定义了 Filter 的主要处理逻辑,同时该方法还负责将请求传递给下一个 Filter 或 Servlet。</p></li><li><p>destroy:在 Web 程序关闭时被调用,用于销毁一些资源。</p></li></ul><p>下面我们通过实现 Filter 接口来创建一个自定义的 Filter:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestFilter</span> <span class="token keyword">implements</span> <span class="token class-name">Filter</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span>FilterConfig filterConfig<span class="token punctuation">)</span> <span class="token keyword">throws</span> ServletException <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>filterConfig<span class="token punctuation">.</span><span class="token function">getFilterName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" 被初始化"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">doFilter</span><span class="token punctuation">(</span>ServletRequest servletRequest<span class="token punctuation">,</span> ServletResponse servletResponse<span class="token punctuation">,</span> FilterChain filterChain<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException<span class="token punctuation">,</span> ServletException <span class="token punctuation">{</span> HttpServletRequest request <span class="token operator">=</span> <span class="token punctuation">(</span>HttpServletRequest<span class="token punctuation">)</span> servletRequest<span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Filter 拦截到了请求: "</span> <span class="token operator">+</span> request<span class="token punctuation">.</span><span class="token function">getRequestURL</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Filter 对请求做预处理..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> filterChain<span class="token punctuation">.</span><span class="token function">doFilter</span><span class="token punctuation">(</span>servletRequest<span class="token punctuation">,</span> servletResponse<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Filter 修改响应的内容..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">destroy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Filter 被回收"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>init 方法的 filterConfig 参数封装了当前 Filter 的配置信息,在 Filter 初始化时,我们将 Filter 的名称打印在控制台。</p></li><li><p>doFilter 方法定义了 Filter 拦截到用户请求后的处理逻辑,<code>filterChain.doFilter(servletRequest, servletResponse);</code> 指的是将请求传递给一下个 Filter 或 Servlet,如果不添加该语句,那么请求就不会向后传递,自然也不会被处理。在该语句之后,可以添加对响应的处理逻辑(如果要修改响应的 Header,可直接在该语句之前修改;如果要修改响应的内容,则需要在该语句之后,且需要自定义一个 response)。</p></li><li><p>destroy 方法中,我们输出 “Filter 被回收” 的提示信息。</p></li></ul><h3 id="配置-Filter"><a href="#配置-Filter" class="headerlink" title="配置 Filter"></a>配置 Filter</h3><p>Spring 项目中,我们可以使用 <code>@Configuration + @Bean + FilterRegistrationBean</code> 对 Filter 进行配置:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FilterConfig</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Bean</span> <span class="token keyword">public</span> FilterRegistrationBean<span class="token operator"><</span>TestFilter<span class="token operator">></span> <span class="token function">registryFilter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> FilterRegistrationBean<span class="token operator"><</span>TestFilter<span class="token operator">></span> registration <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FilterRegistrationBean</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setFilter</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TestFilter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">addUrlPatterns</span><span class="token punctuation">(</span><span class="token string">"/*"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"TestFilter"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setOrder</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> registration<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述代码中,setFilter 方法用于设置 Filter 的类型;addUrlPatterns 方法用于设置拦截的规则;setName 方法用于设置 Filter 的名称;setOrder 方法用于设置 Filter 的优先级,数字越小优先级越高。</p><h3 id="测试-Filter"><a href="#测试-Filter" class="headerlink" title="测试 Filter"></a>测试 Filter</h3><p>接下来,我们定义一个简单的 Web 服务,测试 Filter 是否生效:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>path <span class="token operator">=</span> <span class="token string">"/hello"</span><span class="token punctuation">,</span> method <span class="token operator">=</span> RequestMethod<span class="token punctuation">.</span>GET<span class="token punctuation">)</span> <span class="token keyword">public</span> String <span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"正在处理请求..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"请求处理完成~"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token string">"I'm fine, thank you."</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>启动项目,在浏览器中访问 <code>localhost:8080/hello</code>,等待请求处理完成,然后关闭项目。整个过程中,控制台依次打印了如下信息:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/TestFilter.jpg" alt></p><p>可以看到,自定义的 TestFilter 实现了拦截请求、处理响应的目标。</p><h3 id="创建-Filter-的其它方式"><a href="#创建-Filter-的其它方式" class="headerlink" title="创建 Filter 的其它方式"></a>创建 Filter 的其它方式</h3><p><strong>1. @WebFilter 注解 + 包扫描</strong></p><p>除了 FilterRegistrationBean 外,Servlet 3.0 引入的注解 @WebFilter 也可用于配置 Filter。我们只需要在自定义的 Filter 类上添加该注解,就可以设置 Filter 的名称和拦截规则:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@WebFilter</span><span class="token punctuation">(</span>urlPatterns <span class="token operator">=</span> <span class="token string">"/*"</span><span class="token punctuation">,</span> filterName <span class="token operator">=</span> <span class="token string">"TestFilter"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestFilter</span> <span class="token keyword">implements</span> <span class="token class-name">Filter</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 省略部分代码</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>由于@WebFilter 并非 Spring 提供,因此若要使自定义的 Filter 生效,还需在配置类上添加 @ServletComponetScan 注解,并指定扫描的包:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token annotation punctuation">@ServletComponentScan</span><span class="token punctuation">(</span><span class="token string">"com.example.filter"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DemoApplication</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>DemoApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>需要注意的是,<strong>@WebFilter 注解并不允许我们设置 Filter 的执行顺序,且在 Filter 类上添加 @Order 注解也是无效的。如果项目中有多个被 @WebFilter 修饰的 Filter,那么这些 Filter 的执行顺序由其 “类名的字典序” 决定</strong>,例如类名为 “Axx” 的 Filter 的执行顺序要先于类名为 “Bxx” 的 Filter。</p><blockquote><p>添加了 @WebFilter 注解后就不要再添加 @Component 注解了,如果都添加,那么系统会创建两个 Filter。</p></blockquote><p><strong>2. @Component 注解</strong></p><p>Spring 项目中,我们可以通过添加 @Component 注解将自定义的 Bean 交给 Spring 容器管理。同样的,对于自定义的 Filter,我们也可以直接添加 @Component 注解使其生效,而且还可以添加 @Order 注解来设置不同 Filter 的执行顺序。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Order</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestFilter</span> <span class="token keyword">implements</span> <span class="token class-name">Filter</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 省略部分代码</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>此种配置方式一般不常使用,因为其无法设置 Filter 的拦截规则,默认的拦截路径为 <code>/*</code>。虽然不能配置拦截规则,但我们可以在 doFilter 方法中定义请求的放行规则,例如当请求的 URL 匹配我们设置的规则时,直接将该请求放行,也就是立即执行 <code>filterChain.doFilter(servletRequest, servletResponse);</code>。</p><p><strong>3. 继承 OncePerRequestFilter</strong></p><p>OncePerRequestFilter 是一个由 Spring 提供的抽象类,在项目中,我们可以采用继承 OncePerRequestFilter 的方式创建 Filter,然后重写 doFilterInternal 方法定义 Filter 的处理逻辑,重写 shouldNotFilter 方法设置 Filter 的放行规则。对于多个 Filter 的执行顺序,我们也可以通过添加 @Order 注解进行设置。当然,若要使 Filter 生效,还需添加 @Component 注解将其注册到 Spring 容器。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Order</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CSpringFilter</span> <span class="token keyword">extends</span> <span class="token class-name">OncePerRequestFilter</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">doFilterInternal</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> FilterChain filterChain<span class="token punctuation">)</span> <span class="token keyword">throws</span> ServletException<span class="token punctuation">,</span> IOException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 处理逻辑</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">protected</span> <span class="token keyword">boolean</span> <span class="token function">shouldNotFilter</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">)</span> <span class="token keyword">throws</span> ServletException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 放行规则</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>实际上,方式 2 和方式 3 本质上并没有什么区别,因为 OncePerRequestFilter 底层也是通过实现 Filter 接口来达到过滤请求/响应的目的,只不过 Spring 在 OncePerRequestFilter 中帮我们封装了许多功能,因此更推荐采用此种方式创建 Filter。</p><h3 id="Filter-的优先级"><a href="#Filter-的优先级" class="headerlink" title="Filter 的优先级"></a>Filter 的优先级</h3><p>上文中提到,使用配置类或添加 @Order 注解可以显式的设置 Filter 的执行顺序,修改类名可以隐式的设置 Filter 的执行顺序。如果项目中存在多个 Filter,且这些 Filter 由不同的方式创建,那么它们的执行顺序是怎样的呢?</p><p>能够确定的是,Spring 根据 Filter 的 order 决定其优先级,如果我们通过配置类或者通过 @Order 注解设置了 Filter 的 order,那么 <strong>order 值越小的 Filter 的优先级越高,无论 Filter 由何种方式创建</strong>。如果多个 Filter 的优先级相同,那么执行顺序为:</p><ol><li><p>配置类中配置的 Filter 优先执行,如果配置类中存在多个 Filter,那么 Spring 按照其在配置类中配置的顺序依次执行。</p></li><li><p>@WebFilter 注解修饰的 Filter 之后执行,如果存在多个 Filter,那么 Spring 按照其类名的字典序依次执行。</p></li><li><p>@Component 注解修饰的 Filter 最后执行,如果存在多个 Filter,那么 Spring 按照其类名的字典序依次执行。</p></li></ol><p>注意,以上优先级顺序仅适用于 order 相同的特殊情况。如果我们不配置 Filter 的 order,那么 Spring 默认将其 order 设置为 <code>LOWEST_PRECEDENCE = Integer.MAX_VALUE</code>,也就是最低优先级。由于被 @WebFilter 注解修饰的 Filter 无法显式配置优先级,因此其 order 为 Integer.MAX_VALUE。本文所说的 Filter 的优先级指的是 Filter 对请求做预处理的优先级,对响应做后处理的优先级与之相反。</p><blockquote><p>以上结论由笔者经过测试以及阅读源码得出,如有理解错误,欢迎批评指正 😁。关于源码部分,有兴趣的小伙伴可以看看 ServletContextInitializerBeans 类和 AnnotationAwareOrderComparator 类的源码,笔者在这里就不具体分析了 😈。</p></blockquote><!-- 这些 Filter 的初始化顺序由 filterName(默认为类名的首字母小写)的 hash 值决定 --><h3 id="Filter-的应用场景"><a href="#Filter-的应用场景" class="headerlink" title="Filter 的应用场景"></a>Filter 的应用场景</h3><p>Filter 的常见应用场景包括:</p><ul><li><p>解决跨域访问:前后端分离的项目往往存在跨域访问的问题,Filter 允许我们在 response 的 Header 中设置 “Access-Control-Allow-Origin”、”Access-Control-Allow-Methods” 等头域,以此解决跨域失败问题。</p></li><li><p>设置字符编码:字符编码 Filter 可以在 request 提交到 Servlet 之前或者在 response 返回给客户端之前为请求/响应设置特定的编码格式,以解决请求/响应内容乱码的问题。</p></li><li><p>记录日志:日志记录 Filter 可以在拦截到请求后,记录请求的 IP、访问的 URL,拦截到响应后记录请求的处理时间。当不需要记录日志时,也可以直接将 Filter 的配置注释掉。</p></li><li><p>校验权限:Web 服务中,客户端在发送请求时会携带 cookie 或者 token 进行身份认证,权限校验 Filter 可以在 request 提交到 Servlet 之前对 cookie 或 token 进行校验,如果用户未登录或者权限不够,那么 Filter 可以对请求做重定向或返回错误信息。</p></li><li><p>替换内容:内容替换 Filter 可以对网站的内容进行控制,防止输入/输出非法内容和敏感信息。例如在请求到达 Servlet 之前对请求的内容进行转义,防止 XSS 攻击;在 Servlet 将内容输出到 response 时,使用 response 将内容缓存起来,然后在 Filter 中进行替换,最后再输出到客户浏览器(由于默认的 response 并不能严格的缓存输出内容,因此需要自定义一个具备缓存功能的 response)。</p></li></ul><blockquote><p>Filter 应用场景的相关内容参考自《Java Web 整合开发之王者归来》,好中二的书名 🤣,关于自定义具备缓存功能的 response 可参考该书的 P175。</p></blockquote><h3 id="Filter-的实现原理"><a href="#Filter-的实现原理" class="headerlink" title="Filter 的实现原理"></a>Filter 的实现原理</h3><p>了解了 Filter 的使用方法之后,我们来分析一下 Filter 的实现原理。上文中提到,客户端的请求会在过滤链上依次传递,链上的每个 Filter 都会调用 doFilter 方法对请求进行处理。这个过程能够有条不紊的进行,底层依靠的是函数的回调机制。</p><p><strong>1. 回调函数</strong></p><p>在介绍 Filter 的回调机制之前,我们先了解一下回调函数的概念。如果将函数(C++ 中的函数指针,Java 中的匿名函数、方法引用等)作为参数传递给主方法,那么这个函数就称为回调函数,主方法会在某一时刻调用回调函数。</p><blockquote><p>为了便于区分,我们使用 “主方法” 和 “函数” 来分辨主函数和回调函数。</p></blockquote><p>使用回调函数的好处是能够实现函数逻辑的解耦,主方法内可以定义通用的处理逻辑,部分特定的操作则交给回调函数来完成。例如 Java 中 Arrays 类的 <code>sort(T[] a, Comparator<? super T> c)</code> 方法允许我们传入一个比较器来自定义排序规则,这个比较器的 compare 方法就属于回调函数,sort 方法会在排序时调用 compare 方法。</p><p><strong>2. Filter 的回调机制</strong></p><p>接下来介绍 Filter 的回调机制,上文中提到,我们自定义的 xxFilter 类需要实现 Filter 接口,且需要重写 doFilter 方法:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestFilter</span> <span class="token keyword">implements</span> <span class="token class-name">Filter</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">doFilter</span><span class="token punctuation">(</span>ServletRequest servletRequest<span class="token punctuation">,</span> ServletResponse servletResponse<span class="token punctuation">,</span> FilterChain filterChain<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException<span class="token punctuation">,</span> ServletException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// ...</span> filterChain<span class="token punctuation">.</span><span class="token function">doFilter</span><span class="token punctuation">(</span>servletRequest<span class="token punctuation">,</span> servletResponse<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Filter 接口的 doFilter 方法接收一个 FilterChain 类型的参数,这个 FilterChain 对象可认为是传递给 doFilter 方法的回调函数,严格来说应该是这个 FilterChain 对象的 doFilter 方法,注意这里提到了两个 doFilter 方法。Filter 接口的 doFilter 方法在执行结束或执行完某些步骤后会调用 FilterChain 对象的 doFilter 方法,即调用回调函数。</p><p>FilterChain 对象的实际类型为 ApplicationFilterChain,其 doFilter() 方法的处理逻辑如下(省略部分代码):</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">ApplicationFilterChain</span> <span class="token keyword">implements</span> <span class="token class-name">FilterChain</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">doFilter</span><span class="token punctuation">(</span>ServletRequest request<span class="token punctuation">,</span> ServletResponse response<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException<span class="token punctuation">,</span> ServletException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// ...</span> <span class="token function">internalDoFilter</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span>response<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">internalDoFilter</span><span class="token punctuation">(</span>ServletRequest request<span class="token punctuation">,</span> ServletResponse response<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException<span class="token punctuation">,</span> ServletException <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>pos <span class="token operator"><</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 获取第 pos 个 filter, 即 xxFilter </span> ApplicationFilterConfig filterConfig <span class="token operator">=</span> filters<span class="token punctuation">[</span>pos<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span> Filter filter <span class="token operator">=</span> filterConfig<span class="token punctuation">.</span><span class="token function">getFilter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// ...</span> <span class="token comment" spellcheck="true">// 调用 xxFilter 的 doFilter 方法</span> filter<span class="token punctuation">.</span><span class="token function">doFilter</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> response<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可见,ApplicationFilterChain 的 doFilter 方法首先根据索引查询到我们定义的 xxFilter,然后调用 xxFilter 的 doFilter 方法,在调用时,ApplicationFilterChain 会将自己作为参数传递进去。xxFilter 的 doFilter 方法执行完某些步骤后,会调用回调函数,即 ApplicationFilterChain 的 doFilter 方法,这样 ApplicationFilterChain 就可以获取到下一个 xxFilter,并调用下一个 xxFilter 的 doFilter 方法,如此循环下去,直到所有的 xxFilter 全部被调用。整个流程如下图所示:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/%E5%9B%9E%E8%B0%83%E6%9C%BA%E5%88%B6.jpg" alt></p><blockquote><p>xxFilter 执行回调函数的过程就像是给了 ApplicationFilterChain 一个通知,即通知 ApplicationFilterChain 可以执行下一个 xxFilter 的处理逻辑了。</p></blockquote><p>综上,如果我们在重写自定义 Filter 的 doFilter 方法时没有添加 <code>filterChain.doFilter(servletRequest, servletResponse);</code>,那么该 Filter 之后的其它 Filter 和 Servlet 就不会被调用。</p><h2 id="拦截器-Interceptor"><a href="#拦截器-Interceptor" class="headerlink" title="拦截器 Interceptor"></a>拦截器 Interceptor</h2><h3 id="Interceptor-基本介绍"><a href="#Interceptor-基本介绍" class="headerlink" title="Interceptor 基本介绍"></a>Interceptor 基本介绍</h3><blockquote><p>本文所说的拦截器指的是 Spring MVC 中的拦截器。</p></blockquote><p>拦截器 Interceptor 是 Spring MVC 中的高级组件之一,其作用是拦截用户的请求,并在请求处理前后做一些自定义的处理,如校验权限、记录日志等。这一点和 Filter 非常相似,但不同的是,Filter 在请求到达 Servlet 之前对请求进行拦截,而 Interceptor 则是在请求到达 Controller 之前对请求进行拦截,响应也同理。</p><p>与 Filter 一样,Interceptor 也具备链式结构,我们在项目中可以配置多个 Interceptor,当请求到达时,每个 Interceptor 根据其声明的顺序依次执行。</p><h3 id="创建-Interceptor"><a href="#创建-Interceptor" class="headerlink" title="创建 Interceptor"></a>创建 Interceptor</h3><p>创建 Interceptor 需要实现 org.springframework.web.servlet.HandlerInterceptor 接口,HandlerInterceptor 接口中定义了三个方法:</p><ul><li><p>preHandle:在 Controller 方法执行前被调用,可以对请求做预处理。该方法的返回值是一个 boolean 变量,只有当返回值为 true 时,程序才会继续向下执行。</p></li><li><p>postHandle:在 Controller 方法执行结束,DispatcherServlet 进行视图渲染之前被调用,该方法内可以操作 Controller 处理后的 ModelAndView 对象。</p></li><li><p>afterCompletion:在整个请求处理完成(包括视图渲染)后被调用,通常用来清理资源。</p></li></ul><p>注意,postHandle 方法和 afterCompletion 方法执行的前提条件是 preHandle 方法的返回值为 true。</p><p>下面我们创建一个 Interceptor:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">HandlerInterceptor</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">preHandle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Interceptor 拦截到了请求: "</span> <span class="token operator">+</span> request<span class="token punctuation">.</span><span class="token function">getRequestURL</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">postHandle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">,</span> ModelAndView modelAndView<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Interceptor 操作 modelAndView..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">afterCompletion</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">,</span> Exception ex<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Interceptor 清理资源..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="配置-Interceptor"><a href="#配置-Interceptor" class="headerlink" title="配置 Interceptor"></a>配置 Interceptor</h3><p>Interceptor 需要注册到 Spring 容器才能够生效,注册的方法是在配置类中实现 WebMvcConfigurer 接口,并重写 addInterceptors 方法:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestInterceptorConfig</span> <span class="token keyword">implements</span> <span class="token class-name">WebMvcConfigurer</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> TestInterceptor testInterceptor<span class="token punctuation">;</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addInterceptors</span><span class="token punctuation">(</span>InterceptorRegistry registry<span class="token punctuation">)</span> <span class="token punctuation">{</span> registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span>testInterceptor<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">addPathPatterns</span><span class="token punctuation">(</span><span class="token string">"/*"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">excludePathPatterns</span><span class="token punctuation">(</span><span class="token string">"/**/*.css"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.js"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.png"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.jpg"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.jpeg"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">order</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述代码中,addInterceptor 方法用于注册 Interceptor;addPathPatterns 方法用于设置拦截规则;excludePathPatterns 方法用于设置放行规则,order 方法用于设置 Interceptor 的优先级,数字越小优先级越高。</p><h3 id="测试-Interceptor"><a href="#测试-Interceptor" class="headerlink" title="测试 Interceptor"></a>测试 Interceptor</h3><p>下面我们通过一个简单的 Web 服务,来测试 Interceptor 是否生效:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>path <span class="token operator">=</span> <span class="token string">"/hello"</span><span class="token punctuation">,</span> method <span class="token operator">=</span> RequestMethod<span class="token punctuation">.</span>GET<span class="token punctuation">)</span> <span class="token keyword">public</span> String <span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"正在处理请求..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"请求处理完成~"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token string">"I'm fine, thank you."</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>启动项目,在浏览器中访问 <code>localhost:8080/hello</code>,请求处理完成后,控制台打印了如下信息:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/TestInterceptor.jpg" alt></p><p>可以看到,Interceptor 成功拦截到了访问 Controller 的 <code>/hello</code> 请求和访问静态资源的 <code>/favicon.ico</code> 请求,并在请求处理前后执行了相应的处理逻辑。</p><p>当需要设置多个 Interceptor 时,可以直接在配置类中添加 Interceptor 的配置规则,例如增加 TestInterceptor2:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestInterceptorConfig</span> <span class="token keyword">implements</span> <span class="token class-name">WebMvcConfigurer</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> TestInterceptor testInterceptor<span class="token punctuation">;</span> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> TestInterceptor2 testInterceptor2<span class="token punctuation">;</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addInterceptors</span><span class="token punctuation">(</span>InterceptorRegistry registry<span class="token punctuation">)</span> <span class="token punctuation">{</span> registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span>testInterceptor<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">addPathPatterns</span><span class="token punctuation">(</span><span class="token string">"/*"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">excludePathPatterns</span><span class="token punctuation">(</span><span class="token string">"/**/*.css"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.js"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.png"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.jpg"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.jpeg"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">order</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span>testInterceptor2<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">addPathPatterns</span><span class="token punctuation">(</span><span class="token string">"/*"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">excludePathPatterns</span><span class="token punctuation">(</span><span class="token string">"/**/*.css"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.js"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.png"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.jpg"</span><span class="token punctuation">,</span> <span class="token string">"/**/*.jpeg"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">order</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>Interceptor 的执行顺序由其配置的 order 决定,order 越小越先执行,注意这里指的是 preHandle 方法的执行顺序,postHandle 和 afterCompletion 的执行顺序与 preHandle 相反</strong>,例如在上述示例中,执行顺序为:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/%E5%A4%9A%E4%B8%AAInterceptor.jpg" alt></p><p>如果我们不配置 order,那么 Spring 默认将 order 设置为 0(可以查看 InterceptorRegistration 类的源码)。<strong>如果不同的 Interceptor 具有相同的 order,那么其执行顺序为配置类中的注册顺序</strong>。</p><h3 id="Interceptor-的应用场景"><a href="#Interceptor-的应用场景" class="headerlink" title="Interceptor 的应用场景"></a>Interceptor 的应用场景</h3><p>Interceptor 的应用场可以参考上文中介绍的 Filter 的应用场景,可以说 Filter 能做到的事 Interceptor 都能做。由于 Filter 在 Servlet 前后起作用,而 Interceptor 可以在 Controller 方法前后起作用,例如操作 Controller 处理后的 ModelAndView,因此 Interceptor 更加灵活,在 Spring 项目中,如果能使用 Interceptor 的话尽量使用 Interceptor。</p><h3 id="Interceptor-的实现原理"><a href="#Interceptor-的实现原理" class="headerlink" title="Interceptor 的实现原理"></a>Interceptor 的实现原理</h3><p>了解了 Interceptor 的使用方法之后,我们也来分析一下 Interceptor 的实现原理。Interceptor 的调用发生在 DispatcherServlet 的 doDispatch 方法中,DispatcherServlet 是 Spring MVC 中请求的统一入口,当客户端的请求到达时,DispatcherServlet 会调用 doDispatch 方法处理当前请求:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">doDispatch</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 省略部分代码</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> ModelAndView mv <span class="token operator">=</span> null<span class="token punctuation">;</span> Exception dispatchException <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> processedRequest <span class="token operator">=</span> <span class="token function">checkMultipart</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> multipartRequestParsed <span class="token operator">=</span> <span class="token punctuation">(</span>processedRequest <span class="token operator">!=</span> request<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 根据请求查找处理链 HandlerExecutionChain</span> mappedHandler <span class="token operator">=</span> <span class="token function">getHandler</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>mappedHandler <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">noHandlerFound</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">,</span> response<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 获取适配器 HandlerAdapter, HandlerAdapter 用于处理请求</span> HandlerAdapter ha <span class="token operator">=</span> <span class="token function">getHandlerAdapter</span><span class="token punctuation">(</span>mappedHandler<span class="token punctuation">.</span><span class="token function">getHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 省略部分代码</span> <span class="token comment" spellcheck="true">// 依次执行所有拦截器的 preHandle 方法</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>mappedHandler<span class="token punctuation">.</span><span class="token function">applyPreHandle</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">,</span> response<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 执行 Controller 方法, 返回 ModelAndView</span> mv <span class="token operator">=</span> ha<span class="token punctuation">.</span><span class="token function">handle</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">,</span> response<span class="token punctuation">,</span> mappedHandler<span class="token punctuation">.</span><span class="token function">getHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>asyncManager<span class="token punctuation">.</span><span class="token function">isConcurrentHandlingStarted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">applyDefaultViewName</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">,</span> mv<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 依次执行所有拦截器的 postHandle 方法, 顺序和 preHandle 方法相反</span> mappedHandler<span class="token punctuation">.</span><span class="token function">applyPostHandle</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">,</span> response<span class="token punctuation">,</span> mv<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span> dispatchException <span class="token operator">=</span> ex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> err<span class="token punctuation">)</span> <span class="token punctuation">{</span> dispatchException <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NestedServletException</span><span class="token punctuation">(</span><span class="token string">"Handler dispatch failed"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 进行视图渲染, 结束后执行拦截器的 afterCompletion 方法</span> <span class="token function">processDispatchResult</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">,</span> response<span class="token punctuation">,</span> mappedHandler<span class="token punctuation">,</span> mv<span class="token punctuation">,</span> dispatchException<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 如果发生异常, 那么执行拦截器的 afterCompletion 方法并抛出异常</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">triggerAfterCompletion</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">,</span> response<span class="token punctuation">,</span> mappedHandler<span class="token punctuation">,</span> ex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> err<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">triggerAfterCompletion</span><span class="token punctuation">(</span>processedRequest<span class="token punctuation">,</span> response<span class="token punctuation">,</span> mappedHandler<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">NestedServletException</span><span class="token punctuation">(</span><span class="token string">"Handler processing failed"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 省略部分代码</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol><li><p>首先获取当前请求对应的处理链 HandlerExecutionChain,<strong>HandlerExecutionChain 中包含了当前请求匹配的 Interceptor 列表</strong>。</p></li><li><p>然后根据 HandlerExecutionChain 获取适配器 HandlerAdapter,HandlerAdapter 用于处理当前请求。</p></li><li><p>依次执行所有 Interceptor 的 preHandle 方法,如果某个 preHandle 方法返回 false,那么程序将不再向下执行。</p></li><li><p>调用 HandlerAdapter 处理请求,返回 ModelAndView。</p></li><li><p>请求处理完成后,依次执行所有 Interceptor 的 postHandle 方法,执行的顺序与 preHandle 方法相反。</p></li><li><p>进行视图渲染,渲染结束后执行所有 Interceptor 的 afterCompletion 方法,执行顺序与 postHandle 方法相同。</p></li><li><p>如果以上操作发生异常,那么执行所有 Interceptor 的 afterCompletion 方法并抛出异常。</p></li></ol><blockquote><p>由上述过程我们得知,如果 Controller 抛出异常,那么 postHandle 方法将不会执行,afterCompletion 方法则一定执行。</p></blockquote><p>接下来我们重点分析一下 getHandler 方法(DispatcherServlet 类中定义的)背后的逻辑,看看 Interceptor 是如何加载到(或者说是封装到)HandlerExecutionChain 中的。getHandler 方法首先遍历所有的 HandlerMapping,然后找到匹配当前请求的 HandlerMapping,并调用该 HandlerMapping 的 getHandler 方法获取 HandlerExecutionChain:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/handlerMapping%E8%8E%B7%E5%8F%96handler.png" alt></p><p>此处的 “匹配当前请求的 HandlerMapping” 指的是 RequestMappingHandlerMapping,其在 WebMvcConfigurationSupport 类中完成初始化,且初始化时会加载 Interceptor,加载的过程如下图所示:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/RequestMappingHandlerMapping%E5%8A%A0%E8%BD%BD%E6%8B%A6%E6%88%AA%E5%99%A8.png" alt></p><ol><li><p>RequestMappingHandlerMapping 在加载 Interceptor 之前需要先调用 getInterceptors 方法获取 Interceptor。</p></li><li><p>getInterceptors 方法需要调用 addInterceptors 方法将 Interceptor 封装到 registry(InterceptorRegistry 对象)中。</p></li><li><p>addInterceptors 方法沿调用链向下可追踪到 WebMvcConfigurerComposite 类的 addInterceptors 方法,该方法会执行我们在配置类中重写的 addInterceptors 方法,这样我们自定义的所有 Interceptor 都会加载到 registry 中。</p></li><li><p>回到 getInterceptors 方法,执行 <code>registry.getInterceptors();</code> 就可以从 registry 中获取到 Interceptor 了。</p></li></ol><p>以上是 RequestMappingHandlerMapping 加载 Interceptor 的过程,下面我们继续分析 RequestMappingHandlerMapping 的 getHandler 方法,了解 Interceptor 是如何从 RequestMappingHandlerMapping 加载到 HandlerExecutionChain 中的:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/%E6%8B%A6%E6%88%AA%E5%99%A8%E5%8A%A0%E8%BD%BD%E5%88%B0HandlerExecutionChain.png" alt></p><ol><li><p>RequestMappingHandlerMapping 的 getHandler 方法在其父类 AbstractHandlerMapping 中定义,getHandler 方法会调用 getHandlerExecutionChain 方法获取 HandlerExecutionChain。</p></li><li><p>getHandlerExecutionChain 方法中有一段代码的处理逻辑是遍历当前 HandlerMapping(RequestMappingHandlerMapping)加载的 Interceptor,如果某个 Interceptor 与当前请求匹配,那么将其添加到 HandlerExecutionChain。MappedInterceptor 是我们自定义的 Interceptor 的类型,registry 在加载 Interceptor 时会将其封装为 MappedInterceptor 对象。</p></li></ol><p>HandlerExecutionChain 加载到 Interceptor 后,就可以调用 preHandle 等方法对请求/响应进行处理。</p><blockquote><p>之所以花费较多的篇幅分析 Interceptor 的实现原理,是因为许多文章都有讲到 Interceptor 是基于 Java 的反射机制(动态代理)来实现的,笔者之前在介绍 Interceptor 时也是这样写的 😂,看了源码后才知道这种说法是不正确的,感谢掘金上的小伙伴指出我文章的错误之处 👍。</p></blockquote><h2 id="Filter-和-Interceptor-的区别"><a href="#Filter-和-Interceptor-的区别" class="headerlink" title="Filter 和 Interceptor 的区别"></a>Filter 和 Interceptor 的区别</h2><p><strong>1. 规范不同</strong></p><p>Filter 在 Servlet 规范中定义,依赖于 Servlet 容器(如 Tomcat);Interceptor 由 Spring 定义,依赖于 Spring 容器(IoC 容器)。</p><p><strong>2. 适用范围不同</strong></p><p>Filter 仅可用于 Web 程序,因为其依赖于 Servlet 容器;Interceptor 不仅可以用于 Web 程序,还可以用于 Application、Swing 等程序。 </p><p><strong>3. 触发时机不同</strong></p><p>Filter 在请求进入 Servlet 容器,且到达 Servlet 之前对请求做预处理;在 Servlet 处理完请求后对响应做后处理。</p><p>Interceptor 在请求进入 Servlet,且到达 Controller 之前对请求做预处理;在 Controller 处理完请求后对 ModelAndView 做后处理,在视图渲染完成后再做一些收尾工作。</p><p>下图展示了二者的触发时机:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Filter_Interceptor/%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F%E5%8E%9F%E7%90%86.jpg" alt></p><p>当 Filter 和 Interceptor 同时存在时,Filter 对请求的预处理要先于 Interceptor 的 preHandle 方法;Filter 对响应的后处理要后于 Interceptor 的 postHandle 方法和 afterCompletion 方法。</p><h2 id="关于-Filter-和-Interceptor-的补充说明"><a href="#关于-Filter-和-Interceptor-的补充说明" class="headerlink" title="关于 Filter 和 Interceptor 的补充说明"></a>关于 Filter 和 Interceptor 的补充说明</h2><p><strong>1. 在 Filter 和 Interceptor 注入 Bean 的注意事项</strong></p><p>有些文章在介绍 Filter 和 Interceptor 的区别时强调 Filter 不能通过 IoC 注入 Bean,如果我们采用本文中的第一种创建 Filter,那么确实不能注入成功:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 自定义的 Filter, 未添加 @Component 注解</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestFilter</span> <span class="token keyword">implements</span> <span class="token class-name">Filter</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> UserService userService<span class="token punctuation">;</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">doFilter</span><span class="token punctuation">(</span>ServletRequest servletRequest<span class="token punctuation">,</span> ServletResponse servletResponse<span class="token punctuation">,</span> FilterChain filterChain<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException<span class="token punctuation">,</span> ServletException <span class="token punctuation">{</span> HttpServletRequest request <span class="token operator">=</span> <span class="token punctuation">(</span>HttpServletRequest<span class="token punctuation">)</span> servletRequest<span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userService<span class="token punctuation">)</span><span class="token punctuation">;</span> filterChain<span class="token punctuation">.</span><span class="token function">doFilter</span><span class="token punctuation">(</span>servletRequest<span class="token punctuation">,</span> servletResponse<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// ...</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 配置类</span><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FilterConfig</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Bean</span> <span class="token keyword">public</span> FilterRegistrationBean<span class="token operator"><</span>TestFilter<span class="token operator">></span> <span class="token function">registryFilter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> FilterRegistrationBean<span class="token operator"><</span>TestFilter<span class="token operator">></span> registration <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FilterRegistrationBean</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setFilter</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TestFilter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">addUrlPatterns</span><span class="token punctuation">(</span><span class="token string">"/*"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"TestFilter"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setOrder</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> registration<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述代码执行后,userService 输出为 null,因为注册到 IoC 容器中的是 new 出来的一个 TestFilter 对象(<code>registration.setFilter(new TestFilter());</code>),并不是 Spring 自动装配的。若要使 userService 注入成功,可改为如下写法:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 自定义的 Filter, 未添加 @Component 注解</span><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestFilter</span> <span class="token keyword">implements</span> <span class="token class-name">Filter</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> UserService userService<span class="token punctuation">;</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">doFilter</span><span class="token punctuation">(</span>ServletRequest servletRequest<span class="token punctuation">,</span> ServletResponse servletResponse<span class="token punctuation">,</span> FilterChain filterChain<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException<span class="token punctuation">,</span> ServletException <span class="token punctuation">{</span> HttpServletRequest request <span class="token operator">=</span> <span class="token punctuation">(</span>HttpServletRequest<span class="token punctuation">)</span> servletRequest<span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userService<span class="token punctuation">)</span><span class="token punctuation">;</span> filterChain<span class="token punctuation">.</span><span class="token function">doFilter</span><span class="token punctuation">(</span>servletRequest<span class="token punctuation">,</span> servletResponse<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// ...</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 配置类</span><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FilterConfig</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> TestFilter testFilter<span class="token punctuation">;</span> <span class="token annotation punctuation">@Bean</span> <span class="token keyword">public</span> FilterRegistrationBean<span class="token operator"><</span>TestFilter<span class="token operator">></span> <span class="token function">registryFilter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> FilterRegistrationBean<span class="token operator"><</span>TestFilter<span class="token operator">></span> registration <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FilterRegistrationBean</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setFilter</span><span class="token punctuation">(</span>testFilter<span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">addUrlPatterns</span><span class="token punctuation">(</span><span class="token string">"/*"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"TestFilter"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> registration<span class="token punctuation">.</span><span class="token function">setOrder</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> registration<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>与第一种写法的区别在于,TestFilter 类上添加了 @Component 注解,且配置类中通过 @Autowired 注入 TestFilter 对象。除了使用配置类外,本文介绍的其它几种方式(添加 @Component 注解或 @WebFilter 注解)都可以直接注入 Bean。</p><blockquote><p>所以还是采用继承 OncePerRequestFilter 的方式创建 Filter 比较方便。</p></blockquote><p>另外,使用本文介绍的创建 Interceptor 的写法是可以直接注入 Bean 的,该写法也是先在自定义的 Interceptor 上添加 @Component 注解,然后在配置类中使用 @Autowired 注入自定义的 Interceptor。</p><p><strong>2. Interceptor 可以拦截静态请求</strong></p><p>有文章提到 Interceptor 不能拦截静态请求,其实在 Spring 1.x 的版本中确实是这样的,但 Spring 2.x 对静态资源也进行了拦截,例如上文中我们在测试 TestInterceptor 是否生效时,发现其拦截到了 <code>/favicon.ico</code> 请求,该请求是一个由浏览器自动发送的静态请求。</p><p><strong>3. Interceptor 不是基于 Java 的反射机制(动态代理)来实现的</strong></p><p>详见上文中 Interceptor 的实现原理。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>书籍:《Java web 整合开发王者归来》<br><a href="https://www.cnblogs.com/paddix/p/8365558.html" target="_blank" rel="noopener">Spring Boot 实战:拦截器与过滤器</a><br><a href="https://juejin.cn/post/6844904179958284301" target="_blank" rel="noopener">过滤器和拦截器的 6 个区别,别再傻傻分不清了</a></p>]]></content>
<categories>
<category> 技术学习 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> Web </tag>
</tags>
</entry>
<entry>
<title>MVC 和三层架构</title>
<link href="/2022/051052.html"/>
<url>/2022/051052.html</url>
<content type="html"><![CDATA[<h2 id="MVC"><a href="#MVC" class="headerlink" title="MVC"></a>MVC</h2><p>MVC 是一种分层设计的软件开发架构模式,其将整个业务应用划分为三部分,分别为:</p><ul><li><p>View:视图层,负责界面的展示,与用户直接交互。用户可在视图层执行相关的操作,如查看数据或提交表单等(发送请求),视图层会向用户展示操作的结果。</p></li><li><p>Controller:控制器层,负责接收用户的请求,并将请求委托给 Model 层处理。接收到 Model 层处理的结果后,Controller 会选择合适的视图,并将处理结果交付给该视图。对于某些请求,Controller 还负责做请求的转发或重定向。</p></li><li><p>Model:模型层,负责处理用户的业务请求以及与数据库进行交互。当业务逻辑处理完成后,Model 层会将处理的结果返回给 Controller。</p></li></ul><p>MVC 模式中,一个请求的处理流程如下:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/Web/MVC/MVC%E5%92%8C%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84.jpg" alt></p><p>MVC 模式的优点:</p><ol><li><p>低耦合:降低了层与层之间的依赖,开发人员可只关注其中的某一层。</p></li><li><p>高复用:不同的视图、控制器可复用同一套业务处理逻辑。</p></li><li><p>可维护:分离不同的组件有利于提高程序的可维护性。</p></li></ol><p>MCV 模式的缺点:</p><ol><li><p>高复杂度:严格遵守 MVC 模式可能会产生一些冗余操作,尤其是在小型项目中。</p></li><li><p>低效率:由于 Model 称重不同的接口负责不同的功能,因此视图可能需要多次调用才能获取到全部的数据。对未变化数据的不必要的频繁访问,也将降低系统性能。</p></li></ol><h2 id="三层架构"><a href="#三层架构" class="headerlink" title="三层架构"></a>三层架构</h2><p>三层架构指的是将软件设计划分为表现层、业务逻辑层以及数据访问层:</p><ul><li><p>表现层:与用户直接交互,提供展示数据的界面。同时负责接收用户的请求,并调用业务逻辑层处理请求,请求处理完成后向用户展示处理的结果。</p></li><li><p>业务逻辑层:定义业务的处理逻辑,也就是我们常说的 Service 层。</p></li><li><p>数据访问层:提供访问和操作数据库的方法,即 Dao 层。</p></li></ul><p>MVC 中的 Model 就是三层架构中的业务逻辑层和数据访问层;三层架构中的表现层就是 MVC 中的 View 和 Controller。</p>]]></content>
<categories>
<category> 技术学习 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> Web </tag>
</tags>
</entry>
<entry>
<title>Java 语言实现简易版扫码登录</title>
<link href="/2022/0410823.html"/>
<url>/2022/0410823.html</url>
<content type="html">< --><!-- ## 实现 --><!-- ### 环境准备1. JDK 1.8;2. maven 3.3.6;3. Springboot 2.xx;4. Redis。### 实体对象LoginTicket 类定义如下:```Java@Datapublic class LoginTicket { private String userId; private String uuid; private int status;}```User 类简单封装用户的 id 和 name:```Java@Datapublic class User { private String userId; private String userName;}```### 登录接口1. 获取二维码```Java@RequestMapping(path = "/getQrCodeImg", method = RequestMethod.GET)public String createQrCodeImg(Model model) { // 生成uuid和loginTicket对象并存入Redis String uuid = loginService.createQrImg(); // 使用QrCodeUtil生成二维码 String QrCode = Base64.encodeBase64String(QrCodeUtil.generatePng(loginURL + uuid, 300, 300)); // 返回uuid和二维码 model.addAttribute("uuid", uuid); model.addAttribute("QrCode", QrCode); return "login";}```当访问 "localhost:8080/login/getQrCodeImg" 时,PC 端服务器会生成一个 uuid 和一个 LoginTicket 对象,然后将 uuid 作为 key,LoginTicket 对象作为 value 存入到 Redis 服务器中(设置其过期时间为 5 分钟)。接着将该 uuid 拼接到 URL 中(此 URL 即为手机端扫码后所访问的网址),并使用开源工具类 Hutool 中的 QrCodeUtil 生成二维码图片。>关于 Hutool 的使用可以参考 https://hutool.cn/ 。 --><!-- 2. 扫描二维码```Java@RequestMapping(path = "/scan/{uuid}/{userId}", method = RequestMethod.GET)public String scanQrCodeImg(Model model, @PathVariable("uuid") String uuid, @PathVariable("userId") String userId) { // 判断用户是否成功扫码 boolean scanned = loginService.scanQrCodeImg(uuid, userId); // 返回扫码信息 model.addAttribute("scanned", scanned); model.addAttribute("uuid", uuid); model.addAttribute("userId", userId); return "scan";}```二维码中封装的信息是一个 URL,手机端扫描二维码时,会访问该 URL 所代表的的网址。此时请求中会携带手机端用户的 token 和 uuid,token 用来确认用户的身份。在上述代码中,我们简化手机端的操作,直接传入 userId,利用 userId 代替 token 来识别用户。服务器(此处为手机端服务器,但我们使用 PC 端服务器模拟手机端服务器)首先根据 userId 查询用户是否已经登录,如果 Redis 中存在该用户的信息,则表示用户已经登录。如果用户未登录或二维码已经过期,则扫码失败,返回 false;否则将 LoginTicket 对象的状态设置为 1,表示已经扫码,等待确认。3. 确认登录```Java@RequestMapping(path = "/confirm/{uuid}/{userId}", method = RequestMethod.GET)@ResponseBodypublic Response confirmLogin(@PathVariable("uuid") String uuid, @PathVariable("userId") String userId) { // 判断用户是否成功确认 boolean logged = loginService.confirmLogin(uuid, userId); String msg = logged ? "登录成功!" : "二维码已过期!"; return Response.createResponse(msg, logged);}```同扫码请求一样,确认登录时也使用 userId 代替 token 进行身份识别。手机端(在浏览器中模拟手机端操作)发送确认请求时,服务器首先检查二维码是否过期(按理来说扫码后再确认,二维码应该不会过期)。如果确认成功,那么将 LoginTicket 对象的状态设置为 2,并将 userId 置为当前用户的 id(或许 userId 在 scan 在扫码请求就应该设置为用户 id?)。4. 轮询```Java@RequestMapping(path = "/getQrCodeState/{uuid}", method = RequestMethod.GET)@ResponseBodypublic Response getQrCodeState(@PathVariable("uuid") String uuid) throws InterruptedException { JSONObject data = new JSONObject(); // 检查二维码是否过期 String redisKey = CommonUtil.getTicketKey(uuid); LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey); if (loginTicket == null) { data.put("status", -1); return Response.createResponse("二维码已过期!", data); } // 检查status int status = loginTicket.getStatus(); data.put("status", status); if (status == 2) { // 用户已确认登录 String userId = loginTicket.getUserId(); User user = userService.getLoggedUser(userId); if (user != null) { // 生成token String token = TokenUtil.buildToken(userId, user.getUserName()); data.put("token", token); return Response.createResponse(null, data); } return Response.createErrorResponse("无用户信息!"); } // 2s轮询一次 Thread.sleep(2000); String msg = status == 0 ? null : "已扫描, 等待确认"; return Response.createResponse(msg, data);}```轮询的逻辑其实就是根据 uuid 检查 LoginTicket 对象的状态,如果 LoginTicket 对象为空,表示二维码已经过期;如果 status 为 0,表示等待扫码;如果 status 为 1,表示已扫码,等待确认;如果 status 为 2,表示已确认登录。当检测到用户确认登录后,服务器为用户生成 token(此 token 用于 PC 端服务器识别用户身份),然后将 token 返回给前端。注意,上述代码生成 token 之前,调用了 UserService 中的 getLoggedUser 方法来查询用户的身份信息,在此 demo 中,为了简化操作,凡是需要获取用户信息的地方我们都使用该方法去获取,如前面手机端服务器(其实也是在 PC 端模拟)根据 token (为了简化,实际上为 userId)查询用户信息时也调用了该方法。还有一点需要注意,最后一步的 token 也可以使用 cookie 来代替,这样也许会更加简单,因为想学习一下 JWT,所以采用 token(使用 token 访问时,token 应该怎样保存呢,苦恼!!!!!+10086)。getLoggedUser 方法其实就是检测 Redis 中有无用户的身份信息,代码如下:```Javapublic User getLoggedUser(String userId) { String redisKey = CommonUtil.getUserKey(userId); return (User) redisTemplate.opsForValue().get(redisKey);}```### Service 层Service 层对应的代码如下:```Java@Servicepublic class LoginService { private final int WAIT_EXPIRED_SECONDS = 60 * 5; private final int LOGIN_EXPIRED_SECONDS = 3600 * 24; @Autowired private RedisTemplate redisTemplate; public String createQrImg() { // 生成loginTicket String uuid = CommonUtil.generateUUID(); LoginTicket loginTicket = new LoginTicket(); loginTicket.setUuid(uuid); loginTicket.setStatus(0); // 存入redis String redisKey = CommonUtil.getTicketKey(loginTicket.getUuid()); redisTemplate.opsForValue().set(redisKey, loginTicket, WAIT_EXPIRED_SECONDS, TimeUnit.SECONDS); return uuid; } public boolean scanQrCodeImg(String uuid, String userId) { String ticketKey = CommonUtil.getTicketKey(uuid); String userKey = CommonUtil.getUserKey(userId); LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(ticketKey); User user = (User) redisTemplate.opsForValue().get(userKey); // 检测用户是否登录以及二维码是否过期 if (user == null || loginTicket == null) { return false; } else { // 将status置为1 loginTicket.setStatus(1); redisTemplate.opsForValue().set(ticketKey, loginTicket, redisTemplate.getExpire(ticketKey, TimeUnit.SECONDS), TimeUnit.SECONDS); } return true; } public boolean confirmLogin(String uuid, String userId) { String redisKey = CommonUtil.getTicketKey(uuid); LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey); boolean logged = true; if (loginTicket == null) { logged = false; } else { // 将userId置为用户id, 并将status置为2 loginTicket.setUserId(userId); loginTicket.setStatus(2); redisTemplate.opsForValue().set(redisKey, loginTicket, LOGIN_EXPIRED_SECONDS, TimeUnit.SECONDS); } return logged; }}```前端的几个 xx.html 文件的代码写得不太好,大家直接看源码吧,源码我会放在文末。 --><!-- ## 效果演示执行程序前,我们需要在 Redis 中存储当前用户的信息,表示用户在手机端已经登录,其中 key 的格式为 user:userId,value 为 User 对象。比如在演示前,我们在 Redis 中存储了 userId 为 "1" 的用户 "Join同学"。演示动图如下: --><!-- 1. 启动项目,打开浏览器,访问 localhost:8080/index,显示 '首页';2. 点击 '登录',进入二维码登录界面;3. 使用开发者工具获取生成的 uuid;4. 模仿手机端,在另外一个页面中访问 localhost:8080/login/scan/uuid/userId,注意 uuid 为步骤 3 中获取的 uuid,userId 所对应的 User 需要存储在 Redis 中(表示手机端用户已登录,Redis 中的 key 为 userId,value 为 User 对象)。5. 弹出确认登录窗口;6. 此时首页上显示等待确认;7. 点解确认登录8. 登陆成功;9. 首页上显示登录用户的信息。 --><!-- ## 待改进1. 整个流程应该存在手机端服务器和 PC 端服务器,但为了简化操作,我们利用 PC 端模拟手机端,比如扫码和确认请求应该由手机端服务器处理,而程序中我们直接在 PC 端访问对应的 Controller;2. 检查扫码状态时采用了轮询的方式,或许可以采用 Websocket;3. "手机端" 验证 "token" 时,我们使用 userId 来简化操作;4. 最后一步我们将 token 返回给了前端,前端发送请求时,需要在 header 中存放 token,但问题是 token 应该如何保存呢?之后需要解决此问题;5. 未学习过前端,所以代码不怎么规范。### **欢迎批评指正,源码见** -->]]></content>
<categories>
<category> 系统设计 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> Java </tag>
<tag> Spring Boot </tag>
</tags>
</entry>
<entry>
<title>利用 Nginx 实现动静分离</title>
<link href="/2022/032508.html"/>
<url>/2022/032508.html</url>
<content type="html"><![CDATA[<h2 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h2><p>动静分离指的是将动态请求和静态请求分隔开,然后分别路由到相应的后端服务器。通常用户的请求中,一部分需要后台程序处理,例如查询数据库或者进行一些数据运算,这类请求我们称之为动态请求;还有一部分不需要后台程序处理,如请求 css、html、js、图片等静态资源,这类请求我们称之为静态请求。Nginx 实现动静分离的基础是它可以根据配置对不同的请求做不同的转发,动静分离有利于提高整个服务器系统的性能。</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB/%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB.png" width="70%"></p><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>在本机(Linux 虚拟机)中安装 Nginx,并确保 Nginx 可以正常运行。为了便于实践,我们在一台机器上执行所有操作,也就是在本机中部署 Nginx 和处理动态请求的后端服务,同时本机也会作为静态资源服务器存放静态文件。注意,实际中可使用不同的机器分开部署。</p><p><strong>静态资源准备</strong></p><p>在本机 <code>/usr/local/</code> 目录下创建 <code>/data/img</code> 和 <code>/data/html</code> 文件夹,分别存放 test.png 和 test.html:</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB/test.png" width="70%"></p><p><strong>后端服务</strong></p><p>写一个简单的 “Hello World” 项目并启动,端口为 8080: </p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Controller</span><span class="token annotation punctuation">@RequestMapping</span><span class="token annotation punctuation">@Log</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HelloController</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>path <span class="token operator">=</span> <span class="token string">"/hello"</span><span class="token punctuation">,</span> method <span class="token operator">=</span> RequestMethod<span class="token punctuation">.</span>GET<span class="token punctuation">)</span> <span class="token annotation punctuation">@ResponseBody</span> <span class="token keyword">public</span> String <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">"Hello, world!"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>实际应用中的动态请求通常会涉及相关资源的调度,这里我们简化该过程,假设后端服务进行了一系列运算,最后返回字符串 “Hello, world!”。</p><h2 id="配置-Nginx"><a href="#配置-Nginx" class="headerlink" title="配置 Nginx"></a>配置 Nginx</h2><p>打开 nginx.conf 配置文件,在 server 块中添加如下内容:</p><pre class="line-numbers language-bash"><code class="language-bash">server <span class="token punctuation">{</span> listen 80<span class="token punctuation">;</span> server_name localhost<span class="token punctuation">;</span> location / <span class="token punctuation">{</span> root html<span class="token punctuation">;</span> index index.html index.htm<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">#拦截动态请求</span> location /hello <span class="token punctuation">{</span> proxy_pass http://localhost:8080<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">#拦截静态请求</span> location /img <span class="token punctuation">{</span> root /usr/local/data<span class="token punctuation">;</span> autoindex on<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">#拦截静态请求</span> location /html <span class="token punctuation">{</span> root /usr/local/data<span class="token punctuation">;</span> autoindex on<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>动态请求</strong></p><p>浏览器中输入 <code>localhost/hello</code> 得到如下响应:</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB/%E5%8A%A8%E6%80%81%E8%AF%B7%E6%B1%82%E7%BB%93%E6%9E%9C.png" width="70%"></p><p>这里我们并没有指定端口,因为 HTTP 的默认端口就是 80,而 Nginx 配置文件中也监听了 80 端口,真实的请求为 <code>localhost:8080/hello</code>。</p><p><strong>静态请求</strong></p><p>浏览器中输入 <code>localhost/img</code>,得到如下响应:</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB/%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84.png" width="70%"></p><p>上图中列举出了 <code>/img</code> 目录下的文件,这是因为配置文件中设置了 <code>autoindex on;</code>,它表示打开目录浏览功能。</p><p>浏览器中输入 <code>localhost/img/test.png</code>,得到如下响应:</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB/%E5%9B%BE%E7%89%87%E7%BB%93%E6%9E%9C.png" width="70%"></p><p>浏览器中输入 <code>localhost/html/test.html</code>,得到如下响应:</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB/html%E7%BB%93%E6%9E%9C.png" width="70%"></p><p>上述操作中,我们实现了静态请求和动态请求的分离,当访问静态资源(如 html 和图片)时,请求并不会到达 “Hello World” 后端服务,而是从静态资源服务器(这里是本机)中获取资源,当访问 <code>localhost/img/test.png</code> 时,真实的请求为 <code>localhost:80/usr/data/img/test.png</code>。另外,也可以将负责拦截静态请求的 location 块写成如下形式:</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true">#拦截静态请求</span>location ~ .*\.<span class="token punctuation">(</span>html<span class="token operator">|</span>htm<span class="token operator">|</span>gif<span class="token operator">|</span>jpg<span class="token operator">|</span>jpeg<span class="token operator">|</span>bmp<span class="token operator">|</span>png<span class="token operator">|</span>ico<span class="token operator">|</span>js<span class="token operator">|</span>css<span class="token punctuation">)</span>$ <span class="token punctuation">{</span> root /usr/local/data<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>下面解析一下上述配置中的匹配规则:</p><ul><li><p><code>~</code>:表示匹配时区分大小写。</p></li><li><p><code>.*</code>:<code>.</code> 表示匹配除换行符 <code>\n</code> 之外的任何单字符,<code>*</code> 表示零次或多次,所以 <code>.*</code> 表示任意字符出现零次或多次。</p></li><li><p><code>\.</code>:表示匹配字符 <code>.</code>。</p></li></ul><p>综上,该配置表示匹配以 <code>.html, ···, .css</code> 为后缀的请求,我们可以将 html、jpg 等静态资源存入到 <code>/usr/local/data</code> 下,然后通过指定的 URL 就可以访问到这些资源,当然不同类型的资源也可以分开存放。</p>]]></content>
<categories>
<category> 计算机网络 </category>
</categories>
<tags>
<tag> Nginx </tag>
<tag> 计网 </tag>
</tags>
</entry>
<entry>
<title>Nginx 快速入门</title>
<link href="/2022/0347606.html"/>
<url>/2022/0347606.html</url>
<content type="html"><![CDATA[<h2 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h2><p>Nginx 是一个高性能的 HTTP 和反向代理 Web 服务器,同时它提供了 IMAP、POP3 以及 SMTP 等服务,因此也可作为电子邮件代理服务器。Nginx 占用内存少,并发能力强,目前已被许多国内产商所使用。</p><h2 id="为什么使用-Nginx"><a href="#为什么使用-Nginx" class="headerlink" title="为什么使用 Nginx"></a>为什么使用 Nginx</h2><p>网站建设初期,由于用户数比较少,并发量比较低,一台服务器基本能够满足我们的需求,通过部署 Tomcat 就能将数据信息返回给用户。</p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/%E5%8D%95%E7%94%A8%E6%88%B7.png" width="70%"><p>慢慢的,随着用户数不断增大,较高的并发量使得服务器上处理请求的线程和数据库承受着巨大压力,这将导致请求的响应时间被不断拉长,大量服务超时,最终服务器的可用连接数被耗尽,不再接收新的请求。</p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/%E5%A4%9A%E7%94%A8%E6%88%B7.png" width="70%"><p>一个可行的解决办法是做水平扩展,增加多台服务器,这样不同用户的请求可以由不同的服务器受理,从而大大减小了每台服务器的压力。值得注意的是,分布式系统中各个服务器之间可能并不共享用户的 Session 信息,所以需要一个中间层服务器将一个用户的不同请求路由到同一台服务器上。不仅如此,该中间层服务器还需要使得各个服务器接收的请求量保持均衡,以获得最佳体验。</p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/%E4%B8%AD%E9%97%B4%E5%B1%82%E6%9C%8D%E5%8A%A1%E5%99%A8.png" width="70%"><p>基于上述考虑,我们可以使用 Nginx 进行转发和处理请求。Nginx 首先接收用户的请求,然后根据用户的身份以及服务器的性能将请求路由到对应的服务器节点上,该过程对用户来说是无感知的,用户并不知道具体是哪台服务器在处理请求。</p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/Nginx.png" width="70%"><h2 id="Nginx-主要功能"><a href="#Nginx-主要功能" class="headerlink" title="Nginx 主要功能"></a>Nginx 主要功能</h2><p>Nginx 可以作为反向代理和负载均衡服务器,还可以作为 HTTP 服务器实现 Web 服务的动静分离。</p><h3 id="正向代理"><a href="#正向代理" class="headerlink" title="正向代理"></a>正向代理</h3><p>正向代理类似于一个跳板机,代理客户端访问外部资源。比如国内用户直接访问 Google 时请求会被阻塞,但是挂上 VPN 后就可以正常访问了。这里的 VPN 实际上就是正向代理服务器,用户的请求首先发送给代理服务器,代理服务器能够访问 Google,之后代理服务器接收到 Google 的响应并将该响应返回给用户。对于 Google 服务器来说,它只知道代理服务器发送了多个请求,而并不知道这些请求具体来自于哪些用户。</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/%E6%AD%A3%E5%90%91%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8.png" width="70%"></p><p>正向代理的用途:</p><ol><li><p>访问原先无法访问的资源。</p></li><li><p>用做缓存,加快资源的访问。</p></li><li><p>对客户端做鉴权。</p></li><li><p>实现匿名访问,对后端服务器隐藏真实的 IP。</p></li></ol><h3 id="反向代理"><a href="#反向代理" class="headerlink" title="反向代理"></a>反向代理</h3><p>反向代理指的是指利用代理服务器接受用户的请求,代理服务器将请求转发给某一个内网服务器,并将得到的响应返回给对应的客户端,此时整个后端服务对外就表现为一个代理服务器。比如访问 Baidu,Baidu 的服务实际上是部署在多台机器上的,但是我们每次访问的都是同一个地址 www.<span></span>baidu<span></span>.com,这里的 www.<span></span>baidu.<span></span>com 其实就是反向代理服务器。对客户端来说,它只知道请求都是发送给了代理服务器,而并不知道具体是哪一个真实服务器在处理请求,后续无论动态扩容多少台服务器,客户端也总是访问 www.<span></span>baidu.<span></span>com。</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8.png" width="70%"></p><p>反向代理的用途:</p><ol><li><p>隐藏后端服务器集群的 IP,保证内网安全。通常反向代理服务器的 IP 会作为公网地址供用户访问,而代理服务器和后端服务器使用内网进行通信。</p></li><li><p>负载均衡,利用反向代理服务器可以优化各个后端服务器的负载。</p></li></ol><p><strong>正向代理和反向代理小结</strong></p><ul><li><p>正向代理代理的对象是客户端,它隐藏了真实的客户端,服务端不知道真正的客户端是谁。</p></li><li><p>反向代理代理的对象是服务端,它隐藏了真实的服务端,客户端不知道具体是哪台服务器做出的回应。</p></li></ul><h3 id="负载均衡"><a href="#负载均衡" class="headerlink" title="负载均衡"></a>负载均衡</h3><p>高并发场景下,如天猫双十一当天,某个服务的瞬时访问量及其庞大,此时即使将服务器升级到现有的顶级物理配置,也可能无法满足需求。这个时候可以拓展服务器的数量,将数据流量分摊到多个服务器上,减轻每台服务器的压力,这种应用就称为负载均衡。Nginx 支持的负载均衡策略分为内置策略和扩展策略,其中内置策略就是轮询,扩展策略需要借助第三方插件来实现。</p><p>内置策略有:</p><ol><li><p>轮询:将请求依次分发到不同的后端服务器,如果某个服务器宕机,Nginx 可自动将其剔除。</p></li><li><p>加权轮询:为每台服务器都分配一个权重值,权重值越高的机器被分配请求量也越大,加权轮询多用于后端服务器性能不均的情况。</p></li><li><p>ip_hash:对客户端请求的 IP 进行 Hash 运算,然后根据 Hash 结果将请求分发给对应的服务器。这种方法能够确保同一客户端的不同请求总是由相同的服务器处理,从而解决了 Session 不共享的问题。</p></li><li><p>最少连接分配 least_conn:将请求转发给连接数较少的后端服务器,轮询方案是将请求平均分配给各个服务器,使它们的负载大致相同。然而,某些请求处理起来可能非常耗时,这可能导致其所在后端的负载显著提高,此时使用 least_conn 就可以达到更好的负载均衡效果。</p></li></ol><p>扩展策略有:</p><ol><li><p>fair:按照服务器端的响应时间来分配请求,响应时间短的优先分配。</p></li><li><p>url_hash:对访问的 URL 进行 Hash 运算,然后根据 Hash 的结果来分配请求,这样同一个 URL 总会定向到同一个后端服务器。此种方式适用于后端服务器作为缓存时的情况,因为缓存的命中率比较高。 </p></li></ol><h3 id="动静分离"><a href="#动静分离" class="headerlink" title="动静分离"></a>动静分离</h3><p>由于 Nginx 可以根据配置对不同的请求做不同的转发,因此可用来实现动静分离。动静分离指的是将动态请求(需要后台处理的请求)和静态请求(不需要后台处理的请求,如请求 css、html、js、图片等静态资源)分离开,然后将静态请求对应的静态资源直接放在 Nginx 上做缓存,或者放在指定的服务器上,而动态请求仍由相应的后端服务器处理,这样可以使整个服务器系统的性能、效率更高。</p><h2 id="Nginx-配置"><a href="#Nginx-配置" class="headerlink" title="Nginx 配置"></a>Nginx 配置</h2><p><strong>基础配置文件</strong></p><p>nginx.conf 是 Nginx 的基础配置文件,其默认内容如下:</p><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/nginx.conf.png" width="70%"></p><p>该配置文件总共包含三个部分:全局块、events 块、http 块。</p><ol><li><p>全局块:从配置文件开始到 events 块之间的内容为全局块,全局块中设置了一些影响 Nginx 服务器整体运行性能的配置指令,包括配置运行 Nginx 的用户(组),允许生成的 worker process 数,进程 PID 存放路径、日志的级别和存放路径等。例如上述配置文件中,<code>worker_processes 1;</code> 表示 Nginx 并发处理的进程数,该值越大,可支持的并发处理量也越多。</p></li><li><p>events 块:<code>events{}</code> 括号所包含的内容为 events 块,events 块涉及的指令主要影响 Nginx 服务器和用户之间的网络连接,如 <code>worker_connections 1024;</code> 表示每个 worker process 支持的最大连接数为 1024。events 块中的配置对 Nginx 的性能影响较大,实际中应灵活配置。</p></li><li><p>http 块:<code>https{}</code> 括号所包含的内容为 http 块,http 块中主要用来设置反向代理、缓存、日志定义以及第三方模块的配置。http 块包含 http 全局块和 server 块。</p></li></ol><ul><li><p>http 全局块:http 全局块配置的指令包括文件引入、mime-type 定义、日志自定义、连接超时时间、单连接请求数上限等。</p></li><li><p>server 块:server 块与虚拟主机联系紧密,每个 http 块可以包含多个 server 块,而每个server 块就相当于一个虚拟主机。server 块也分为全局 server 块和 location 块。</p><ul><li><p>全局 server 块:主要配置虚拟主机的名称或 IP 地址、监听的端口号等。</p></li><li><p>location 块:主要配置请求的路由,各种页面的处理情况以及数据的缓存等。当请求的路径满足 location 块中设定的路径匹配规则时,程序就会进入 location 块执行相应的操作,如请求跳转等。下图中,Nginx 会监听主机 <code>localhost</code> 的 <code>80</code> 端口(由 server_name 和 listen 配置),当访问 <code>localhost:80</code> 时,会自动跳转到 <code>127.0.0.1:8080</code>。</p></li></ul></li></ul><p><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/Server.png" width="50%"></p><p><strong>location 匹配规则</strong></p><p>location 块中 <code>location</code> 关键词后跟的是一个字符串或正则表达式,其基本语法为 <code>location [=|~|~*|^~] uri {……}</code>。如果请求的 URI 匹配该正则表达式,那么就进入 location 块。</p><p><code>无任何修饰符</code>表示前缀匹配,当请求的 URI 的开头与 location 后的 URI 匹配时,则进入 location 块:</p><pre class="line-numbers language-bash"><code class="language-bash">server <span class="token punctuation">{</span> server_name baidu.com<span class="token punctuation">;</span> location /hello <span class="token punctuation">{</span> …… <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">#如下请求均匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/hello</span><span class="token comment" spellcheck="true">#http://baidu.com/hello?a=1</span><span class="token comment" spellcheck="true">#http://baidu.com/hello/</span><span class="token comment" spellcheck="true">#http://baidu.com/helloo 虽然 /helloo 与 /hello 匹配, 但最终访问的仍然是 http://真实服务器IP:端口/helloo</span><span class="token comment" spellcheck="true">#如下请求不匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/hell</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>=</code> 表示精确匹配:</p><pre class="line-numbers language-bash"><code class="language-bash">server <span class="token punctuation">{</span> server_name baidu.com location <span class="token operator">=</span> /hello <span class="token punctuation">{</span> …… <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">#如下请求均匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/hello</span><span class="token comment" spellcheck="true">#http://baidu.com/hello?a=1</span><span class="token comment" spellcheck="true">#如下请求均不匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/hello/</span><span class="token comment" spellcheck="true">#http://baidu.com/helloo</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>~</code> 表示区分大小写的前缀匹配:</p><pre class="line-numbers language-bash"><code class="language-bash">server <span class="token punctuation">{</span> server_name baidu.com<span class="token punctuation">;</span> location ~ /hello <span class="token punctuation">{</span> …… <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">#如下请求均匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/hello</span><span class="token comment" spellcheck="true">#http://baidu.com/hello?a=1</span><span class="token comment" spellcheck="true">#http://baidu.com/hello/</span><span class="token comment" spellcheck="true">#http://baidu.com/helloo</span><span class="token comment" spellcheck="true">#如下请求均不匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/Hello</span><span class="token comment" spellcheck="true">#http://baidu.com/hellO</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash">server <span class="token punctuation">{</span> server_name baidu.com<span class="token punctuation">;</span> location ~ ^/hello$ <span class="token punctuation">{</span> …… <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">#如下请求均匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/hello</span><span class="token comment" spellcheck="true">#http://baidu.com/hello?a=1</span><span class="token comment" spellcheck="true">#如下请求均不匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/hello/</span><span class="token comment" spellcheck="true">#http://baidu.com/helloo</span><span class="token comment" spellcheck="true">#http://baidu.com/Hello</span><span class="token comment" spellcheck="true">#http://baidu.com/hellO</span><span class="token comment" spellcheck="true">#$表示匹配字符串的结尾位置, 就是说必须在字符串的结尾匹配到 $ 前面的部分才行, 因此 /hello 能匹配成功而 /helloo 就不行, 与之类似, ^ 匹配字符串开始的位置</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>~*</code> 表示不区分大小写的前缀匹配:</p><pre class="line-numbers language-bash"><code class="language-bash">server <span class="token punctuation">{</span> server_name baidu.com<span class="token punctuation">;</span> location ~* /hello <span class="token punctuation">{</span> …… <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">#如下请求均匹配:</span><span class="token comment" spellcheck="true">#http://baidu.com/hello</span><span class="token comment" spellcheck="true">#http://baidu.com/hello?a=1</span><span class="token comment" spellcheck="true">#http://baidu.com/helloo</span><span class="token comment" spellcheck="true">#http://baidu.com/HELLO </span><span class="token comment" spellcheck="true">#http://baidu.com/hello/</span><span class="token comment" spellcheck="true">#所以无修饰符的前缀匹配与不区分大小的写前缀匹配有什么区别?</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>^~</code> 的匹配规则与无修饰符的情况相似,不同的是,匹配成功之后,会停止搜索后面的正则表达式。</p><p><strong>以上匹配规则均是在 Linux 操作系统下测试得出,可能 Windows 和 Mac OS 系统下请求的 URL 仍然是大小写不敏感的。另外,如有错误欢迎批评指正!</strong></p><p>Nginx 做请求转发时,如果请求的 URI 与多个 location 块中定义的 URI 匹配,那么应该如何做跳转呢?Nginx 中规定了匹配的优先级:</p><ol><li><p>带有 “=” 的精确匹配。</p></li><li><p>无修饰符的精确匹配。</p></li><li><p>带有 “^~” 修饰符的前缀匹配。</p></li><li><p>按照正则表达式在配置文件中定义的顺序进行匹配。</p></li><li><p>带有 “~“ 或 “~*” 修饰符的前缀匹配。</p></li><li><p>无修饰符的前缀匹配。</p></li><li><p>通用匹配 “/“。</p></li></ol><h2 id="Nginx-常用命令"><a href="#Nginx-常用命令" class="headerlink" title="Nginx 常用命令"></a>Nginx 常用命令</h2><p>Nginx 的启动、停止、更新配置文件后的重新加载命令如下:</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token function">cd</span> /usr/local/nginx/sbin/ <span class="token comment" spellcheck="true">#Linux 下 Nginx 的默认安装目录</span>./nginx <span class="token comment" spellcheck="true">#启动</span>./nginx -s stop <span class="token comment" spellcheck="true">#停止</span>./nginx -s quit <span class="token comment" spellcheck="true">#安全退出</span>./nginx -s reload <span class="token comment" spellcheck="true">#重新加载配置文件, Nginx 启动后如果修改了配置文件, 可执行该命令重新加载</span><span class="token function">ps</span> aux<span class="token operator">|</span><span class="token function">grep</span> nginx <span class="token comment" spellcheck="true">#查看 Nginx 进程</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="Nginx-实践"><a href="#Nginx-实践" class="headerlink" title="Nginx 实践"></a>Nginx 实践</h2><p><strong>设计思路</strong></p><p>首先在 Windows 主机中安装 Nginx,然后使用 VirtualBox 或 VMware 启动两台虚拟机,并将后端服务部署在这两台虚拟机中。此时,部署 Nginx 的 Windows 主机负责接收请求,而两台 Linux 虚拟机则充当后端服务器,当有请求到达时,Nginx 服务器根据配置将该请求路由到真实的服务器(虚拟机)中。</p><p><strong>后端服务</strong></p><p>写一个简单的 “Hello World” 项目: </p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Controller</span><span class="token annotation punctuation">@RequestMapping</span><span class="token annotation punctuation">@Log</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HelloController</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>path <span class="token operator">=</span> <span class="token string">"/hello"</span><span class="token punctuation">,</span> method <span class="token operator">=</span> RequestMethod<span class="token punctuation">.</span>GET<span class="token punctuation">)</span> <span class="token annotation punctuation">@ResponseBody</span> <span class="token keyword">public</span> String <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"I'm fine, thank you."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token string">"Hello, world!"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>将该项目打成 jar 包,分别部署在两台虚拟机中。正常情况下该服务会返回 “Hello, world!” 字符串,并打印日志 “I’m fine, thank you.”,为了更好地区分请求由哪台虚拟机处理,我们将其中一个服务中打印的日志修改为 “And you?”。</p><p><strong>Nginx 配置</strong></p><p>接着打开 Nginx 的配置文件 nginx.conf,配置反向代理和负载均衡策略:</p><pre class="line-numbers language-bash"><code class="language-bash">upstream nginxTest <span class="token punctuation">{</span> server 192.168.3.118:8080 weight<span class="token operator">=</span>1<span class="token punctuation">;</span> server 192.168.3.124:8080 weight<span class="token operator">=</span>1<span class="token punctuation">;</span><span class="token punctuation">}</span>server <span class="token punctuation">{</span> listen 80<span class="token punctuation">;</span> server_name localhost<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">#charset koi8-r;</span> <span class="token comment" spellcheck="true">#access_log logs/host.access.log main;</span> location / <span class="token punctuation">{</span> root html<span class="token punctuation">;</span> index index.html index.htm<span class="token punctuation">;</span> <span class="token punctuation">}</span> location /hello <span class="token punctuation">{</span> proxy_pass http://nginxTest<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">#路由到 http://192.128.3.xxx:8080/hello</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>upstream 块中定义的是负载均衡服务器的列表,weight 表示每个 server 的权重。</p><blockquote><p>小知识:上述配置文件的 15 和 16 行表明,当访问 localhost:80 时,服务器会先访问 html/index.html,如果没有找到则继续访问 html/index.htm。</p></blockquote><p><strong>测试</strong></p><p>在 Windows 主机的浏览器中访问 localhost:80/hello,得到响应 “Hello, world!”,多次访问后可以发现,两台 Linux 虚拟机均打印出了相应的日志:</p><p>主机 1:<br><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/Centos.png" width="70%"></p><p>主机 2:<br><img src="http://r9eatfbfc.hb-bkt.clouddn.com/Nginx%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/Ubuntu_result.png" width="70%"></p><p>这说明请求会被随机分配给两台主机,因为我们在配置文件中为它们设置了相同的权重。</p><p><strong>注意事项</strong></p><p>上述操作中,需要确保 Windows 主机与两台虚拟机可以相互 Ping 通。当然也可以使用一台主机(Windows 或 Linux)来完成测试,只需要在主机上启动多个后端服务并分别设置不同的服务端口,之后在 nginx.conf 中配置相关的路由规则。</p>]]></content>
<categories>
<category> 技术学习 </category>
</categories>
<tags>
<tag> Nginx </tag>
<tag> 计网 </tag>
</tags>
</entry>
<entry>
<title>MyBatis 实现分页查询的三种方式</title>
<link href="/2022/0318336.html"/>
<url>/2022/0318336.html</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>分页查询是许多网站都具备的一个基本功能,其作用是将查询到的结果集做分页展示,例如每页显示 100 条数据。当数据量较大时,分页查询可以减轻服务端的查询压力以及前端的渲染压力。MyBatis 提供了三种分页查询的方式,下面我们依次介绍其实现过程。</p><p>首先创建实体类 User:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">int</span> id<span class="token punctuation">;</span> <span class="token keyword">private</span> String userName<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">int</span> age<span class="token punctuation">;</span> <span class="token keyword">private</span> String address<span class="token punctuation">;</span> <span class="token keyword">private</span> Date createTime<span class="token punctuation">;</span> <span class="token keyword">private</span> Date updateTime<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后创建 user 表:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span><span class="token keyword">user</span><span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span><span class="token keyword">user</span><span class="token punctuation">`</span> <span class="token punctuation">(</span> <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>user_name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>age<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>address<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_create<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token operator">=</span> <span class="token number">16</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8mb4 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_0900_ai_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接着向 user 表中插入数据:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/Page/user%E8%A1%A8%E6%95%B0%E6%8D%AE.jpg" alt></p><h2 id="Limit-分页"><a href="#Limit-分页" class="headerlink" title="Limit 分页"></a>Limit 分页</h2><p>Limit 是 MySQL 提供的分页查询功能,其语法为:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">limit</span> ${<span class="token keyword">offset</span>}<span class="token punctuation">,</span> ${<span class="token keyword">rows</span>}<span class="token comment" spellcheck="true"># 或</span><span class="token keyword">limit</span> ${<span class="token keyword">rows</span>} <span class="token keyword">offset</span> ${<span class="token keyword">offset</span>}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>参数 offset 和 rows 分别指定检索的偏移量和记录数,例如 <code>limit 1, 5</code> 表示查询从第二行到第六行的 5 条记录。</p><p>创建 Page 类:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Page</span><span class="token operator"><</span>T<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">Serializable</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> Integer RANGE <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> Integer DEFAULT_PAGE_INDEX <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> Integer DEFAULT_PAGE_SIZE <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 总的记录数</span> <span class="token keyword">private</span> Integer count<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 当前页码, 默认为 1</span> <span class="token keyword">private</span> Integer pageIndex <span class="token operator">=</span> DEFAULT_PAGE_INDEX<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 每页显示的记录数, 默认为 5</span> <span class="token keyword">private</span> Integer pageSize <span class="token operator">=</span> DEFAULT_PAGE_SIZE<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 总的页数</span> <span class="token keyword">private</span> Integer total<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 数据内容</span> <span class="token keyword">private</span> List<span class="token operator"><</span>T<span class="token operator">></span> data<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 根据页码计算偏移量</span> <span class="token keyword">public</span> Integer <span class="token function">getStart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>pageIndex <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">*</span> pageSize<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 计算总共的页数</span> <span class="token keyword">public</span> Integer <span class="token function">getTotal</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> total <span class="token operator">=</span> count <span class="token operator">/</span> pageSize<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">%</span> pageSize <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> total<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> total<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 前端展示的页码范围</span> <span class="token keyword">public</span> Integer <span class="token function">getFrom</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> from <span class="token operator">=</span> pageIndex <span class="token operator">-</span> RANGE<span class="token punctuation">;</span> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>from<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> Integer <span class="token function">getTo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> to <span class="token operator">=</span> pageIndex <span class="token operator">+</span> RANGE<span class="token punctuation">;</span> <span class="token keyword">int</span> total <span class="token operator">=</span> <span class="token function">getTotal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>to<span class="token punctuation">,</span> total<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Mapper 接口:</p><pre class="line-numbers language-java"><code class="language-java">List<span class="token operator"><</span>User<span class="token operator">></span> <span class="token function">findUserPage</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Param</span><span class="token punctuation">(</span><span class="token string">"condition"</span><span class="token punctuation">)</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> condition<span class="token punctuation">,</span> <span class="token annotation punctuation">@Param</span><span class="token punctuation">(</span><span class="token string">"start"</span><span class="token punctuation">)</span> Integer start<span class="token punctuation">,</span> <span class="token annotation punctuation">@Param</span><span class="token punctuation">(</span><span class="token string">"pageSize"</span><span class="token punctuation">)</span> Integer pageSize<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserPage<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>condition.max_age !<span class="token punctuation">=</span> null and condition.max_age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token cdata"><![CDATA[age <= #{condition.max_age}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span> limit #{start}, #{pageSize}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserCount<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select count(1) from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>condition.max_age !<span class="token punctuation">=</span> null and condition.max_age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token cdata"><![CDATA[age <= #{condition.max_age}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>使用 Limit 实现分页查询时,我们需要额外添加一个返回总记录数的 SQL,以便计算总的页码数。</p><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">findUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"max_age"</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Page<span class="token operator"><</span>User<span class="token operator">></span> page <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Page</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> page<span class="token punctuation">.</span><span class="token function">setPageIndex</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> page<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUserPage</span><span class="token punctuation">(</span>map<span class="token punctuation">,</span> page<span class="token punctuation">.</span><span class="token function">getStart</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> page<span class="token punctuation">.</span><span class="token function">getPageSize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> page<span class="token punctuation">.</span><span class="token function">setCount</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUserCount</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>查询结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span><span class="token property">"count"</span><span class="token operator">:</span><span class="token number">7</span><span class="token punctuation">,</span><span class="token property">"dEFAULT_PAGE_INDEX"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token property">"dEFAULT_PAGE_SIZE"</span><span class="token operator">:</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token property">"data"</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"ShanXi"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">21</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"王五"</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"Hebei"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">12</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1653235200000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">5</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1653235200000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"朱六"</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"Henan"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">23</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1653321600000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">6</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1653321600000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"孙七"</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">"from"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token property">"pageIndex"</span><span class="token operator">:</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token property">"pageSize"</span><span class="token operator">:</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token property">"rANGE"</span><span class="token operator">:</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token property">"start"</span><span class="token operator">:</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token property">"to"</span><span class="token operator">:</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token property">"total"</span><span class="token operator">:</span><span class="token number">3</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="RowBounds-分页"><a href="#RowBounds-分页" class="headerlink" title="RowBounds 分页"></a>RowBounds 分页</h2><p>RowBounds 是 MyBatis 提供的一个分页类,它可以帮助我们省略 limit 子句的书写,使我们只需要关注业务层的分页逻辑即可。</p><p>Mapper 接口:</p><pre class="line-numbers language-java"><code class="language-java">List<span class="token operator"><</span>User<span class="token operator">></span> <span class="token function">findUserByRowBounds</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Param</span><span class="token punctuation">(</span><span class="token string">"condition"</span><span class="token punctuation">)</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> condition<span class="token punctuation">,</span> <span class="token annotation punctuation">@Param</span><span class="token punctuation">(</span><span class="token string">"RowBounds"</span><span class="token punctuation">)</span> RowBounds rowBounds<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserByRowBounds<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>condition.max_age !<span class="token punctuation">=</span> null and condition.max_age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token cdata"><![CDATA[age <= #{condition.max_age}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">findUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"max_age"</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span> RowBounds rowBounds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RowBounds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUserByRowBounds</span><span class="token punctuation">(</span>map<span class="token punctuation">,</span> rowBounds<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>RowRounds 类的构造方法接收两个参数,分别为 offset 和 limit:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">RowBounds</span><span class="token punctuation">(</span><span class="token keyword">int</span> offset<span class="token punctuation">,</span> <span class="token keyword">int</span> limit<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>offset <span class="token operator">=</span> offset<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>limit <span class="token operator">=</span> limit<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>offset 表示查询的偏移量,limit 表示查询的记录数,这两个参数与 Limit 查询中的两个参数的含义相同。</p><p>查询结果:</p><pre class="line-numbers language-bash"><code class="language-bash">Page<span class="token punctuation">{</span>count<span class="token operator">=</span>false, pageNum<span class="token operator">=</span>2, pageSize<span class="token operator">=</span>5, startRow<span class="token operator">=</span>1, endRow<span class="token operator">=</span>6, total<span class="token operator">=</span>-1, pages<span class="token operator">=</span>1, reasonable<span class="token operator">=</span>false, pageSizeZero<span class="token operator">=</span>false<span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token string">"address"</span><span class="token keyword">:</span><span class="token string">"Shanghai"</span>,<span class="token string">"age"</span>:27,<span class="token string">"createTime"</span>:1653148800000,<span class="token string">"id"</span>:2,<span class="token string">"updateTime"</span>:1653148800000,<span class="token string">"userName"</span><span class="token keyword">:</span><span class="token string">"张三"</span><span class="token punctuation">}</span>, <span class="token punctuation">{</span><span class="token string">"address"</span><span class="token keyword">:</span><span class="token string">"Beijing"</span>,<span class="token string">"age"</span>:23,<span class="token string">"createTime"</span>:1653148800000,<span class="token string">"id"</span>:3,<span class="token string">"updateTime"</span>:1653148800000,<span class="token string">"userName"</span><span class="token keyword">:</span><span class="token string">"李四"</span><span class="token punctuation">}</span>, <span class="token punctuation">{</span><span class="token string">"address"</span><span class="token keyword">:</span><span class="token string">"ShanXi"</span>,<span class="token string">"age"</span>:21,<span class="token string">"createTime"</span>:1653148800000,<span class="token string">"id"</span>:4,<span class="token string">"updateTime"</span>:1653148800000,<span class="token string">"userName"</span><span class="token keyword">:</span><span class="token string">"王五"</span><span class="token punctuation">}</span>, <span class="token punctuation">{</span><span class="token string">"address"</span><span class="token keyword">:</span><span class="token string">"Hebei"</span>,<span class="token string">"age"</span>:12,<span class="token string">"createTime"</span>:1653235200000,<span class="token string">"id"</span>:5,<span class="token string">"updateTime"</span>:1653235200000,<span class="token string">"userName"</span><span class="token keyword">:</span><span class="token string">"朱六"</span><span class="token punctuation">}</span>, <span class="token punctuation">{</span><span class="token string">"address"</span><span class="token keyword">:</span><span class="token string">"Henan"</span>,<span class="token string">"age"</span>:23,<span class="token string">"createTime"</span>:1653321600000,<span class="token string">"id"</span>:6,<span class="token string">"updateTime"</span>:1653321600000,<span class="token string">"userName"</span><span class="token keyword">:</span><span class="token string">"孙七"</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>RowRounds 一般不推荐使用</strong>,因为它属于逻辑分页查询,即一次性从数据库中查询出所有的数据并将其存储在服务器内存,然后从结果集中检索出分页的数据。此种查询方式的执行效率较低,内存占用率较高。</p><blockquote><p>物理分页指的是从数据库中查询出指定偏移量后的指定条数据。</p></blockquote><h2 id="分页插件-PageHelper"><a href="#分页插件-PageHelper" class="headerlink" title="分页插件 PageHelper"></a>分页插件 PageHelper</h2><p>PageHelper 是一个非常实用的分页插件,其实现原理是通过拦截器将 limit 信息拼接到 SQL 语句中,从而实现分页的功能。此外,PageHelper 也免去了 count 语句(查询总记录数)的书写。</p><p>pom.xml 文件中添加依赖:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>dependency</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>groupId</span><span class="token punctuation">></span></span>com.github.pagehelper<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>groupId</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>artifactId</span><span class="token punctuation">></span></span>pagehelper<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>artifactId</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>version</span><span class="token punctuation">></span></span>5.1.7<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>配置 MyBatis 的核心配置文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>plugins</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>plugin</span> <span class="token attr-name">interceptor</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>com.github.pagehelper.PageInterceptor<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>plugins</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Mapper 接口:</p><pre class="line-numbers language-java"><code class="language-java">List<span class="token operator"><</span>User<span class="token operator">></span> <span class="token function">findUserByPageHelper</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Param</span><span class="token punctuation">(</span><span class="token string">"condition"</span><span class="token punctuation">)</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> condition<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserByPageHelper<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>condition.max_age !<span class="token punctuation">=</span> null and condition.max_age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token cdata"><![CDATA[age <= #{condition.max_age}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">findUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"max_age"</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span> PageHelper<span class="token punctuation">.</span><span class="token function">startPage</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUserByPageHelper</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>PageHelper 的 startPage 方法接收两个参数,分别为 pageNum 和 pageSize:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token operator"><</span>E<span class="token operator">></span> Page<span class="token operator"><</span>E<span class="token operator">></span> <span class="token function">startPage</span><span class="token punctuation">(</span><span class="token keyword">int</span> pageNum<span class="token punctuation">,</span> <span class="token keyword">int</span> pageSize<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">startPage</span><span class="token punctuation">(</span>pageNum<span class="token punctuation">,</span> pageSize<span class="token punctuation">,</span> DEFAULT_COUNT<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>pageNum 表示页码,pageSize 表示每页的记录数。</p><blockquote><p>PageHelper 也可以自定义 count 语句。</p></blockquote><p>查询结果:</p><pre class="line-numbers language-bash"><code class="language-bash">Page<span class="token punctuation">{</span>count<span class="token operator">=</span>true, pageNum<span class="token operator">=</span>2, pageSize<span class="token operator">=</span>5, startRow<span class="token operator">=</span>5, endRow<span class="token operator">=</span>10, total<span class="token operator">=</span>7, pages<span class="token operator">=</span>2, reasonable<span class="token operator">=</span>false, pageSizeZero<span class="token operator">=</span>false<span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token string">"address"</span><span class="token keyword">:</span><span class="token string">"Henan"</span>,<span class="token string">"age"</span>:23,<span class="token string">"createTime"</span>:1653321600000,<span class="token string">"id"</span>:6,<span class="token string">"updateTime"</span>:1653321600000,<span class="token string">"userName"</span><span class="token keyword">:</span><span class="token string">"孙七"</span><span class="token punctuation">}</span>, <span class="token punctuation">{</span><span class="token string">"address"</span><span class="token keyword">:</span><span class="token string">"Jiangsu"</span>,<span class="token string">"age"</span>:32,<span class="token string">"createTime"</span>:1652976000000,<span class="token string">"id"</span>:7,<span class="token string">"updateTime"</span>:1653667200000,<span class="token string">"userName"</span><span class="token keyword">:</span><span class="token string">"周八"</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>]]></content>
<categories>
<category> 技术学习 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> Java </tag>
<tag> Spring Boot </tag>
<tag> MyBatis </tag>
</tags>
</entry>
<entry>
<title>MyBatis 动态 SQL</title>
<link href="/2022/0346848.html"/>
<url>/2022/0346848.html</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>动态 SQL 是 MyBatis 的强大特性之一,它可以根据传入的属性值自动拼接 SQL 语句,同时还可以修正 SQL 语法,例如添加必要的空格或删除多余的逗号。动态 SQL 在实际开发中非常常见,是必须要掌握的技能之一,下面我们通过一些简单的实例来演示动态 SQL 的应用。</p><p>首先创建实体类 User:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">int</span> id<span class="token punctuation">;</span> <span class="token keyword">private</span> String userName<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">int</span> age<span class="token punctuation">;</span> <span class="token keyword">private</span> String address<span class="token punctuation">;</span> <span class="token keyword">private</span> Date createTime<span class="token punctuation">;</span> <span class="token keyword">private</span> Date updateTime<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后创建 user 表:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span><span class="token keyword">user</span><span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span><span class="token keyword">user</span><span class="token punctuation">`</span> <span class="token punctuation">(</span> <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>user_name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>age<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>address<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_create<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token operator">=</span> <span class="token number">16</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8mb4 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_0900_ai_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接着向 user 表中插入数据:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/DynamicSQL/user%E8%A1%A8%E6%95%B0%E6%8D%AE.jpg" alt></p><h2 id="if-标签"><a href="#if-标签" class="headerlink" title="if 标签"></a>if 标签</h2><p><code><if></code> 标签主要用来实现条件判断的功能,例如根据不同的入参执行不同的查询策略。</p><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user where 1=1 <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_id !<span class="token punctuation">=</span> null and user_id !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> and <span class="token cdata"><![CDATA[id <= #{user_id}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>max_age !<span class="token punctuation">=</span> null and max_age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> and <span class="token cdata"><![CDATA[age <= #{max_age}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Mapper 接口:</p><pre class="line-numbers language-java"><code class="language-java">List<span class="token operator"><</span>User<span class="token operator">></span> <span class="token function">findUser</span><span class="token punctuation">(</span>Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>上述代码中,我们使用 <code><if></code> 标签判断 user_id 和 max_age 是否为空,如果都不为空,那么查询 id 小于等于 user_id 且 age 小于等于 max_age 的用户信息。<code><![CDATA[xxx]]></code> 的作用是将 xxx 转化为纯文本格式,因为 XML 文件会将 <code><</code> 等字符识别为转义字符,为了能够正常使用这些符号,我们需要将其转化为普通字符。</p><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">findUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"max_age"</span><span class="token punctuation">,</span> <span class="token number">25</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUser</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>查询结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"BUPT"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">24</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>select 操作执行的 SQL 语句为:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">select</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token keyword">user</span> <span class="token keyword">where</span> <span class="token number">1</span><span class="token operator">=</span><span class="token number">1</span> <span class="token operator">and</span> id <span class="token operator"><=</span> <span class="token number">2</span> <span class="token operator">and</span> age <span class="token operator"><=</span> <span class="token number">25</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="where-标签"><a href="#where-标签" class="headerlink" title="where 标签"></a>where 标签</h2><p>上文中,我们在条件查询语句中添加了 <code>where 1=1</code> 这样的代码段,其作用是维护 SQL 语法的正确性。如果我们将 <code>1=1</code> 删除,那么当 user_id 为空且 max_age 不为空时,SQL 语句就变成了 <code>select ... where and age <= 25</code>,这将导致程序出错。此外,如果 user_id 和 max_age 均为空,那么 SQL 语句将以 where 结尾,这也会导致程序出错。幸运的是,MyBatis 提供的 <code><where></code> 标签可以让我们不用添加 <code>where 1=1</code> 也能确保 SQL 的正确性。</p><p>将 mapper 文件修改为:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_id !<span class="token punctuation">=</span> null and user_id !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token cdata"><![CDATA[id <= #{user_id}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>max_age !<span class="token punctuation">=</span> null and max_age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> and <span class="token cdata"><![CDATA[age <= #{max_age}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果我们将 user_id 和 max_age 中的某一个或两个设置为空,那么程序仍然能够正确查询出用户信息,因为 <code>where</code> 元素只会在子元素有返回内容的情况下才插入 <code>where</code> 子句。若子句的开头为 <code>and</code> 或 <code>or</code>,<code>where</code> 元素也会将它们去除。</p><h2 id="choose、when、otherwise-标签"><a href="#choose、when、otherwise-标签" class="headerlink" title="choose、when、otherwise 标签"></a>choose、when、otherwise 标签</h2><p>有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 <code>choose</code> 元素,它有点像 Java 中的 switch 语句。</p><p>将 mapper 文件修改为:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>choose</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>when</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_id !<span class="token punctuation">=</span> null and user_id !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token cdata"><![CDATA[id <= #{user_id}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>when</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>when</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>max_age !<span class="token punctuation">=</span> null and max_age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token cdata"><![CDATA[age <= #{max_age}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>when</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>otherwise</span><span class="token punctuation">></span></span> id = 1 <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>otherwise</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>choose</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">findUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"max_age"</span><span class="token punctuation">,</span> <span class="token number">25</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUser</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>查询结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"BUPT"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">24</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"Beijing"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">23</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"李四"</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"ShanXi"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">21</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"王五"</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>select 操作执行的 SQL 语句为:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">select</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token keyword">user</span> <span class="token keyword">WHERE</span> age <span class="token operator"><=</span> <span class="token number">25</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>由于我们仅为 max_age 变量赋值,因此在该变量之后的所有 <code>when</code> 子句或 <code>otherwise</code> 子句都不会执行。如果 <code><when></code> 标签中的条件均不满足,那么 MyBatis 会根据 <code><otherwise></code> 标签设置的条件执行查询操作:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">findUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// map.put("max_age", 25);</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUser</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>查询结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"BUPT"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">24</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="set-标签"><a href="#set-标签" class="headerlink" title="set 标签"></a>set 标签</h2><p><code><set></code> 标签一般在 update 操作中使用,它与 <code><where></code> 一样有智能检测的功能,当子元素有返回内容时才会插入 <code>set</code> 子句,并且它可以帮助我们去除多余的逗号,下面测试一个 update 操作。</p><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>update</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> update user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>set</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name !<span class="token punctuation">=</span> null and user_name !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> user_name = #{user_name}, <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age !<span class="token punctuation">=</span> null and age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> age = #{age} <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>set</span><span class="token punctuation">></span></span> where id = #{user_id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>update</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Mapper 接口:</p><pre class="line-numbers language-java"><code class="language-java">Integer <span class="token function">updateUser</span><span class="token punctuation">(</span>Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">updateUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"user_name"</span><span class="token punctuation">,</span> <span class="token string">"王胖子"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">updateUser</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>更新结果:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/DynamicSQL/user%E8%A1%A8%E6%9B%B4%E6%96%B0.jpg" alt></p><p>update 操作执行的 SQL 为:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">update</span> <span class="token keyword">user</span> <span class="token keyword">set</span> user_name <span class="token operator">=</span> <span class="token string">'王胖子'</span> <span class="token keyword">where</span> user_id <span class="token operator">=</span> <span class="token number">4</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>可见,MyBatis 帮我们插入了 <code>set</code> 关键字,并删除了 <code>where</code> 关键字前的逗号。</p><h2 id="trim-标签"><a href="#trim-标签" class="headerlink" title="trim 标签"></a>trim 标签</h2><p>MyBatis 官方文档对 <code>trim</code> 的描述为 “可以使用 <code>trim</code> 元素来定制 <code>where</code> 元素的功能”。我们知道,<code>where</code> 元素会在子元素返回非空内容时插入 <code>where</code> 关键字,而 <code>trim</code> 元素不仅可以插入 <code>where</code> 关键字,它还允许我们自定义插入的内容。此外,<code>trim</code> 元素也可以帮助我们删除多余的符号,且这些符号是可以自定义的。下面用一个示例来演示 <code>trim</code> 元素的功能。</p><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>insert</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>insertUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> insert into user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>trim</span> <span class="token attr-name">prefix</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>(<span class="token punctuation">"</span></span> <span class="token attr-name">suffix</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>)<span class="token punctuation">"</span></span> <span class="token attr-name">suffixOverrides</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>,<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> gmt_create, gmt_modified, <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name !<span class="token punctuation">=</span> null and user_name !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> user_name, <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age !<span class="token punctuation">=</span> null and age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> age, <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address !<span class="token punctuation">=</span> null and address !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> address <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>trim</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>trim</span> <span class="token attr-name">prefix</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>values(<span class="token punctuation">"</span></span> <span class="token attr-name">suffix</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>)<span class="token punctuation">"</span></span> <span class="token attr-name">suffixOverrides</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>,<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> UTC_TIMESTAMP(), UTC_TIMESTAMP(), <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name !<span class="token punctuation">=</span> null and user_name !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> #{user_name}, <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age !<span class="token punctuation">=</span> null and age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> #{age}, <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address !<span class="token punctuation">=</span> null and address !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> #{address} <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>trim</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>insert</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Mapper 接口:</p><pre class="line-numbers language-java"><code class="language-java">Integer <span class="token function">insertUser</span><span class="token punctuation">(</span>Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"user_name"</span><span class="token punctuation">,</span> <span class="token string">"John"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"age"</span><span class="token punctuation">,</span> <span class="token number">12</span><span class="token punctuation">)</span><span class="token punctuation">;</span> userMapper<span class="token punctuation">.</span><span class="token function">insertUser</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>执行结果:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/DynamicSQL/user%E8%A1%A8%E6%96%B0%E5%A2%9E%E6%95%B0%E6%8D%AE.jpg" alt></p><p>上述代码中,我们通过 prefix 和 suffix 属性设置 <code>trim</code> 子句的前缀和后缀,通过 suffixOverrides 属性设置需要去除的多余符号。insert 操作执行的 SQL 语句为:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">insert</span> <span class="token keyword">into</span> <span class="token keyword">user</span> <span class="token punctuation">(</span> gmt_create<span class="token punctuation">,</span> gmt_modified<span class="token punctuation">,</span> user_name<span class="token punctuation">,</span> age <span class="token punctuation">)</span> <span class="token keyword">values</span><span class="token punctuation">(</span> UTC_TIMESTAMP<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> UTC_TIMESTAMP<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'John'</span><span class="token punctuation">,</span> <span class="token number">12</span> <span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>虽然我们并未传入 address 参数,但 MyBatis 还是将 age 后面多余的逗号删除了。由于 <code>trim</code> 元素可以自定义 SQL 子句的前缀、后缀等内容,因此 <code>trim</code> 比 <code>where</code> 更加灵活,下面我们利用 <code>trim</code> 元素实现 <code><where></code> 标签的功能。</p><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>trim</span> <span class="token attr-name">prefix</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>where<span class="token punctuation">"</span></span> <span class="token attr-name">prefixOverrides</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>and |or<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_id !<span class="token punctuation">=</span> null and user_id !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token cdata"><![CDATA[id <= #{user_id}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>max_age !<span class="token punctuation">=</span> null and max_age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> and <span class="token cdata"><![CDATA[age <= #{max_age}]]></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>trim</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>除了 <code><where></code> 标签,<code>trim</code> 元素还可以实现 <code><set></code> 标签的功能。</p><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>update</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> update user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>trim</span> <span class="token attr-name">prefix</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>set<span class="token punctuation">"</span></span> <span class="token attr-name">suffixOverrides</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>,<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name !<span class="token punctuation">=</span> null and user_name !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> user_name = #{user_name}, <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age !<span class="token punctuation">=</span> null and age !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> age = #{age} <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>trim</span><span class="token punctuation">></span></span> where id = #{user_id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>update</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以发现,<code>where</code> 和 <code>set</code> 其实都是 <code>trim</code> 的一种特殊情况。下面附上 <code>trim</code> 中各个属性的含义:</p><ul><li><p>prefix:需要增加的前缀。</p></li><li><p>prefixOverrides:需要去除的多余前缀。</p></li><li><p>suffix:需要增加的后缀。</p></li><li><p>suffixOverrides:需要去除的多余后缀。</p></li></ul><h2 id="foreach-标签"><a href="#foreach-标签" class="headerlink" title="foreach 标签"></a>foreach 标签</h2><p>当 SQL 的入参为一个集合或数组时,我们可以使用 <code><foreach></code> 标签遍历集合或数组中的元素,并将其拼接为特定的表达式,不同的表达式之间还可以添加自定义的分隔符。下面我们利用 <code><foreach></code> 标签查询 id 为 1、2、3 的用户数据。</p><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>foreach</span> <span class="token attr-name">collection</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ids<span class="token punctuation">"</span></span> <span class="token attr-name">item</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">open</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>(<span class="token punctuation">"</span></span> <span class="token attr-name">close</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>)<span class="token punctuation">"</span></span> <span class="token attr-name">separator</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>or<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> id=#{id} <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>foreach</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">findUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> List<span class="token operator"><</span>String<span class="token operator">></span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"ids"</span><span class="token punctuation">,</span> list<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUser</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>查询结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"BUPT"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">24</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"Shanghai"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">27</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"张三"</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"Beijing"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">23</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1653148800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"李四"</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>select 操作执行的 SQL 语句为:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">select</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token keyword">user</span> <span class="token keyword">WHERE</span> <span class="token punctuation">(</span> id<span class="token operator">=</span><span class="token number">1</span> <span class="token operator">or</span> id<span class="token operator">=</span><span class="token number">2</span> <span class="token operator">or</span> id<span class="token operator">=</span><span class="token number">3</span> <span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><code><foreach></code> 标签也常常与 <code>in</code> 关键字搭配来使用:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> id in <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>foreach</span> <span class="token attr-name">collection</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ids<span class="token punctuation">"</span></span> <span class="token attr-name">item</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">open</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>(<span class="token punctuation">"</span></span> <span class="token attr-name">close</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>)<span class="token punctuation">"</span></span> <span class="token attr-name">separator</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>,<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> id <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>foreach</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>select 操作执行的 SQL 语句为:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">select</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token keyword">user</span> <span class="token keyword">WHERE</span> id <span class="token operator">in</span> <span class="token punctuation">(</span> <span class="token number">1</span> <span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>下面附上 <code>foreach</code> 中各个属性的含义:</p><ul><li><p>collection:遍历的集合或数组。</p></li><li><p>item:集合或数组中的元素。</p></li><li><p>open:<code>foreach</code> 子句的开头。</p></li><li><p>close:<code>foreach</code> 子句的结尾。</p></li><li><p>separator:不同元素中间的分隔符。</p></li></ul><h2 id="bind-标签"><a href="#bind-标签" class="headerlink" title="bind 标签"></a>bind 标签</h2><p><code><bind></code> 标签可以将 OGNL 表达式值赋值给一个变量,这样下文中的 SQL 语句就可以引用该变量。</p><p>mapper 文件:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUser<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>bind</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pattern<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>%<span class="token punctuation">'</span> + user_name + <span class="token punctuation">'</span>%<span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> select * from user <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>where</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>if</span> <span class="token attr-name">test</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name !<span class="token punctuation">=</span> null and user_name !<span class="token punctuation">=</span> <span class="token punctuation">'</span><span class="token punctuation">'</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span> user_name like #{pattern} <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>if</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>where</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">findUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Map<span class="token operator"><</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"user_name"</span><span class="token punctuation">,</span> <span class="token string">"John"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>userMapper<span class="token punctuation">.</span><span class="token function">findUser</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>查询结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"BUPT"</span><span class="token punctuation">,</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">24</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token property">"age"</span><span class="token operator">:</span><span class="token number">12</span><span class="token punctuation">,</span><span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1653235200000</span><span class="token punctuation">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">5</span><span class="token punctuation">,</span><span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1653235200000</span><span class="token punctuation">,</span><span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John"</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>select 操作执行的 SQL 语句为:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">select</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token keyword">user</span> <span class="token keyword">WHERE</span> user_name <span class="token operator">like</span> <span class="token string">'%John%'</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>]]></content>
<categories>
<category> 技术学习 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> Java </tag>
<tag> Spring Boot </tag>
<tag> MyBatis </tag>
</tags>
</entry>
<entry>
<title>MyBatis 结果映射总结</title>
<link href="/2022/036441.html"/>
<url>/2022/036441.html</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>结果映射指的是将数据表中的字段与实体类中的属性关联起来,这样 MyBatis 就可以根据查询到的数据来填充实体对象的属性,帮助我们完成赋值操作。其实 MyBatis 的官方文档对映射规则的讲解还是非常清楚的,但考虑到自己马上就会成为一名 SQL Boy,以后免不了经常跟 SQL 打交道(公司使用的也是 MyBatis),所以希望用更加通俗的语言对官方文档所介绍的常用映射规则做一个总结,既为刚入门的同学提供一个参考,也方便自己以后查阅。本文会结合一些常见的应用场景,并通过简单的示例来介绍不同的映射方法。如有理解错误,还请大家批评指正!</p><h2 id="简单字段映射"><a href="#简单字段映射" class="headerlink" title="简单字段映射"></a>简单字段映射</h2><p>MyBatis 中的 resultType 和 resultMap 均支持结果映射,对于一些简单的映射操作,我们可以直接使用 resultType 来完成。但如果实体类中的属性为复杂类型,或者属性名和字段名无法对应,那么我们就需要使用 resultMap 来创建自定义的映射关系。下面用一个示例来演示 resultType 和 resultMap 的使用方法。</p><p>首先创建实体类 User:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">int</span> id<span class="token punctuation">;</span> <span class="token keyword">private</span> String userName<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">int</span> age<span class="token punctuation">;</span> <span class="token keyword">private</span> String address<span class="token punctuation">;</span> <span class="token keyword">private</span> Date createTime<span class="token punctuation">;</span> <span class="token keyword">private</span> Date updateTime<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后创建 user 表:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span><span class="token keyword">user</span><span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span><span class="token keyword">user</span><span class="token punctuation">`</span> <span class="token punctuation">(</span> <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>user_name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>age<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>address<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_create<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token operator">=</span> <span class="token number">16</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8mb4 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_0900_ai_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接着向 user 表中插入数据:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/user%E8%A1%A8%E6%95%B0%E6%8D%AE.jpg" alt></p><p>配置 MyBatis,启用别名和驼峰式命名映射:</p><pre class="line-numbers language-yml"><code class="language-yml">#application.yamlmybatis: mapper-locations: classpath:mapper/* # 指定 mapper.xml 文件的路径,该文件用于编写 SQL 语句 type-aliases-package: com.example.entity # 设置别名,它的作用是告诉 MyBatis 需要设置别名的实体类的所在的包。默认情况下,MyBatis 会使用实体类的非限定类名来作为它的别名,如将 com.example.entity.User 的别名设置为 User 或 user(别名不区分大小写) configuration: map-underscore-to-camel-case: true # 开启驼峰命名自动映射,如将数据表中的字段 user_name 映射到实体对象的属性 userName<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>创建 mapper 文件,其内容如下:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserById<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select id, user_name, age, address, gmt_create, gmt_modified from user where id = #{id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述代码中,我们使用 resultMap 来指定 SQL 语句的出参类型。默认情况下,如果数据表的字段名与实体类的属性名完全相同(如 id 对应 id),或者二者符合驼峰式命名映射的规则(如 user_name 对应 userName),那么 MyBatis 可以直接完成赋值操作。但 gmt_create 和 gmt_modified 不会映射为 createTime 和 updateTime,因此我们需要使用 <code>resultMap</code> 来创建新的映射关系。如果将 user 表的字段 gmt_create 和 gmt_modified 分别改为 create_time 和 update_time,那么就可以使用 <code>resulType="User"</code> 或 <code>resulType="user"</code> 来替换 <code>resultMap="UserMap"</code>。</p><p>调用 findUserById(参数 id 等于 1)查询用户信息,可得到如下结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"BUPT"</span><span class="token punctuation">,</span> <span class="token property">"age"</span><span class="token operator">:</span><span class="token number">24</span><span class="token punctuation">,</span> <span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span> <span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="利用-constructor-指定构造方法"><a href="#利用-constructor-指定构造方法" class="headerlink" title="利用 constructor 指定构造方法"></a>利用 <code>constructor</code> 指定构造方法</h2><p>MyBatis 查询出数据后,会调用实体类的无参构造方法创建实体对象,然后为该对象的属性赋值。有时候我们会在实体类中重载多个构造方法,例如在不同的构造方法中执行不同的初始化操作,这种情况下我们希望 MyBatis 能够调用指定的构造方法来初始化对象。此外,如果实体类中仅有带参的构造方法,那么也需要通知 MyBatis 调用指定的构造方法。对于这两个问题,我们可以使用 MyBatis 提供的 <code>constructor</code> 元素来解决。</p><blockquote><p>MyBatis 官方文档在介绍 <code>constructor</code> 时有提到,<code>constructor</code> 允许我们在始化对象时就为对象的属性赋值,这样可以不用暴露出公有方法。</p></blockquote><p>首先在 User 类中添加带参的构造方法:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">User</span><span class="token punctuation">(</span>String userName<span class="token punctuation">,</span> <span class="token keyword">int</span> age<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>userName <span class="token operator">=</span> userName<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">=</span> age<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>然后将 mapper 文件修改为:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>constructor</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>arg</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>String<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>arg</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>_int<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>constructor</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserById<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select id, user_name, age, address, gmt_create, gmt_modified from user where id = #{id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注意,<code><arg></code> 标签的定义顺序必须与构造方法中的参数顺序相同,因为 MyBatis 是根据 <code><constructor></code> 标签中的参数类型列表来匹配实体类的构造方法的,例如在本例中,匹配的构造方法为 <code>User.<init>(java.lang.String, int)</code>。如果将 xml 文件中的两个 <code><arg></code> 标签互换位置,那么 User 对象将不会被实例化成功,因为 User 类中并没有参数类型列表为 <code>(int, java.lang.String)</code> 的构造方法。如果我们不指定 <code>javaType</code> 属性的值,那么 MyBatis 默认将其置为 Object,此时构造方法中对应的参数类型也必须为 Object。</p><blockquote><p>MyBatis 中的 _int 类型对应 Java 中的 int 类型,int 类型对应 Integer 类型。</p></blockquote><p>经过上述配置,MyBatis 在实例化对象的时候就会调用我们指定的构造方法。另外,MyBatis 也支持跟据参数名称来匹配构造方法:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>constructor</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>arg</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>_int<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>arg</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>String<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>constructor</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code><arg></code> 标签中的 <code>name</code> 属性用于设置构造方法的参数名称,如果我们设置了 <code>name</code> 的值,那么 MyBatis 会根据该属性匹配对应的构造方法,且 <code><arg></code> 标签的位置可以随意放置。上述代码中,我们将两个 <code><arg></code> 标签互换位置,然后调用 findUserById,仍然可以查询出用户的信息。</p><h2 id="利用-association-关联一个复杂类型"><a href="#利用-association-关联一个复杂类型" class="headerlink" title="利用 association 关联一个复杂类型"></a>利用 <code>association</code> 关联一个复杂类型</h2><p>博客系统中,每个用户都担任着某种角色,如普通用户、管理员、版主等。为了更好地描述用户信息,我们需要为 User 类添加一个 Role 类型的成员变量,记录当前用户所属的角色。但 Role 类型与 String、int 等类型不同,Role 对象本身也存储了一些特定的属性,如 id、roleName 等,默认情况下 MyBatis 无法为这些属性赋值。为了能够正确初始化 Role 变量,我们需要使用 <code>association</code> 元素将查询到的结果与 Role 对象的属性关联起来。</p><p>首先修改 User 类/创建 Role 类:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 省略部分属性</span> <span class="token keyword">private</span> Role role<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Role</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">int</span> id<span class="token punctuation">;</span> <span class="token keyword">private</span> String roleName<span class="token punctuation">;</span> <span class="token keyword">private</span> Date createTime<span class="token punctuation">;</span> <span class="token keyword">private</span> Date updateTime<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后创建 role 表(存储角色信息)和 user_roles 表(存储用户和角色的关联信息):</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true"># 创建 `role` 表</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>role<span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>role<span class="token punctuation">`</span> <span class="token punctuation">(</span> <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>role_name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_create<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token operator">=</span> <span class="token number">3</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8mb4 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_0900_ai_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span class="token comment" spellcheck="true"># 创建 `user_roles` 表</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>user_roles<span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>user_roles<span class="token punctuation">`</span> <span class="token punctuation">(</span> <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>user_id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>role_id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_create<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8mb4 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_0900_ai_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接着向 role 表和 user_roles 表中插入数据:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/role%E5%92%8Cuser_roles%E8%A1%A8%E6%95%B0%E6%8D%AE.jpg" alt></p><p>MyBatis 为我们提供了三种处理子对象(如 Role 对象)的方式,分别为 <code>嵌套结果映射</code>、<code>嵌套查询</code> 和 <code>关联多个结果集</code>。</p><h3 id="1-嵌套结果映射"><a href="#1-嵌套结果映射" class="headerlink" title="1. 嵌套结果映射"></a>1. 嵌套结果映射</h3><p><code>嵌套结果映射</code> 指的是在 resultMap 中嵌套一个映射关系,mapper 文件的内容如下:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>association</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role<span class="token punctuation">"</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roleName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>association</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserById<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select u.id, u.user_name, u.age, u.address, u.gmt_create, u.gmt_modified, r.id as 'role_id', r.role_name from user as u left join user_roles as ur on ur.user_id = u.id left join role as r on ur.role_id = r.id where u.id = #{id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述代码中,我们将查询到的 role_id 和 role_name 分别映射到 Role 对象(User 对象的属性)的 id 和 roleName。</p><p>调用 findUserById 查询用户信息,可得到如下结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"BUPT"</span><span class="token punctuation">,</span> <span class="token property">"age"</span><span class="token operator">:</span><span class="token number">24</span><span class="token punctuation">,</span> <span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"role"</span><span class="token operator">:</span><span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"roleName"</span><span class="token operator">:</span><span class="token string">"管理员"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span> <span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>我们也可以将 <code>association</code> 中的映射关系独立出来,改写为如下形式,方便复用:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>association</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role<span class="token punctuation">"</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_id<span class="token punctuation">"</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>RoleMap<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>RoleMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roleName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-嵌套查询"><a href="#2-嵌套查询" class="headerlink" title="2. 嵌套查询"></a>2. 嵌套查询</h3><p><code>嵌套查询</code> 指的是在 <code>resultMap</code> 中嵌套一个查询语句,mapper 文件的内容如下:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>association</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role<span class="token punctuation">"</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{user_id<span class="token punctuation">=</span>id}<span class="token punctuation">"</span></span> <span class="token attr-name">select</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>selectUserRole<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>selectUserRole<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select r.id, r.role_name from user_roles as ur left join role as r on ur.role_id = r.id where ur.user_id = #{user_id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserById<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select id, user_name, age, address, gmt_create, gmt_modified from user where id = #{id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>resultMap 中嵌套了一个子查询 <code>selectUserRole</code>,MyBatis 首先从 user 表中查询出 id、user_name 等信息,然后将 user_id 作为参数传递给 <code>selectUserRole</code>,<code>selectUserRole</code> 负责从 role 表和 user_roles 表中查询出当前用户的角色信息。<code>column="{user_id=id}"</code> 指的是将查询到的 id 赋值给变量 user_id,然后将 user_id 作为子查询的入参(如果直接将 id 作为入参,那么 User 对象的 id 属性将不会被赋值),如果需要传入多个参数,那么可以使用一个复合属性,如 <code>column="{param1=value1, param2=value2}"</code>。注意,嵌套子查询时,子查询中的 parameterType 必须设置为 <code>Map</code> 或省略不写。</p><h3 id="3-关联多个结果集"><a href="#3-关联多个结果集" class="headerlink" title="3. 关联多个结果集"></a>3. 关联多个结果集</h3><p><code>关联多个结果集</code> 指的是一次性执行多个查询语句,并得到多个结果集,然后利用某个结果集的数据来填充对象的属性。</p><p>首先在 MySQL 数据库中创建存储过程 findUserAndRole:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true">-- 将结束标志符更改为 $$</span><span class="token keyword">delimiter</span> $$<span class="token keyword">create</span> <span class="token keyword">procedure</span> findUserAndRole<span class="token punctuation">(</span><span class="token operator">in</span> user_id <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token keyword">begin</span> <span class="token keyword">select</span> id<span class="token punctuation">,</span> user_name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> address<span class="token punctuation">,</span> gmt_create<span class="token punctuation">,</span> gmt_modified <span class="token keyword">from</span> <span class="token keyword">user</span> <span class="token keyword">where</span> id <span class="token operator">=</span> user_id<span class="token punctuation">;</span> <span class="token keyword">select</span> r<span class="token punctuation">.</span>id <span class="token keyword">as</span> role_id<span class="token punctuation">,</span> r<span class="token punctuation">.</span>role_name <span class="token keyword">as</span> role_name<span class="token punctuation">,</span> ur<span class="token punctuation">.</span>user_id <span class="token keyword">as</span> user_id <span class="token keyword">from</span> user_roles <span class="token keyword">as</span> ur <span class="token keyword">left</span> <span class="token keyword">join</span> role <span class="token keyword">as</span> r <span class="token keyword">on</span> ur<span class="token punctuation">.</span>role_id <span class="token operator">=</span> r<span class="token punctuation">.</span>id<span class="token punctuation">;</span><span class="token keyword">end</span> $$<span class="token comment" spellcheck="true">-- 将结束标志符改回 ;</span><span class="token keyword">delimiter</span> <span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后修改 mapper 文件的内容:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>association</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role<span class="token punctuation">"</span></span> <span class="token attr-name">javaType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span> <span class="token attr-name">resultSet</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role<span class="token punctuation">"</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">foreignColumn</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_id<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roleName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>association</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserById<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span> <span class="token attr-name">resultSets</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user,role<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">statementType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>CALLABLE<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> {call findUserAndRole(#{user_id,jdbcType=INTEGER,mode=IN})}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>解释一下上述操作的含义,我们在存储过程 findUserAndRole 中定义了两条 SQL 语句,第一条的执行逻辑是利用 user_id 从 user 表中查询出当前用户的 id,user_name 等信息;第二条的执行逻辑是利用关联查询从 role 表和 user_roles 表中查询出 user_id、role_id 以及 role_name 等信息。我们将两次查询得到的结果集分别表示为 user 和 role,即 <code>resultSets="user,role"</code>,然后通过 <code>association</code> 将结果集 role 中的 role_id 和 role_name 分别映射到 Role 对象的 id 和 roleName 属性。<code>column="id" foreignColumn="user_id"</code> 用于关联两个结果集中的数据,因为结果集 role 中包含了所有用户的角色信息(虽然本例中我们只设置了一个用户,但实际上结果集 role 中包含着所有用户的信息),因此在进行属性填充之前,我们需要指明利用哪一个角色信息进行属性填充,<code>column="id" foreignColumn="user_id"</code> 的作用就是从结果集 role 中筛选出 user_id 为 id 的角色信息。</p><blockquote><p>resultSets 中不同的结果集之间用逗号分隔,中间千万不能加空格!</p></blockquote><h2 id="利用-collection-关联多个复杂类型"><a href="#利用-collection-关联多个复杂类型" class="headerlink" title="利用 collection 关联多个复杂类型"></a>利用 <code>collection</code> 关联多个复杂类型</h2><p>上文中我们分析了一个用户担任一种角色的情况,然而在实际开发中,每个用户都有可能同时担任多种角色,例如 “John同学” 既可以是管理员,又可以是版主。此时使用 <code>association</code> 无法正确查询出用户的角色信息,因为 <code>association</code> 处理的是一对一的映射关系。当需要关联多个对象时,我们需要使用 <code>collection</code> 元素。</p><p>首先修改实体类:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 省略部分属性</span> <span class="token keyword">private</span> List<span class="token operator"><</span>Role<span class="token operator">></span> roles<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后在 user_roles 表中插入一条记录:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/user_roles%E8%A1%A8%E6%95%B0%E6%8D%AE2.jpg" alt></p><p><code>collection</code> 的使用方法和 <code>association</code> 非常相似,在上文中介绍的三种方法中,我们只需要做一些简单的修改,就可以查询出用户的所有角色信息。</p><h3 id="1-嵌套结果映射-1"><a href="#1-嵌套结果映射-1" class="headerlink" title="1. 嵌套结果映射"></a>1. 嵌套结果映射</h3><p>mapper 文件的内容如下:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>collection</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roles<span class="token punctuation">"</span></span> <span class="token attr-name">ofType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roleName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>collection</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserById<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select u.id, u.user_name, u.age, u.address, u.gmt_create, u.gmt_modified, r.id as 'role_id', r.role_name from user as u left join user_roles as ur on ur.user_id = u.id left join role as r on ur.role_id = r.id where u.id = #{id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>与上文中使用 <code>association</code> 嵌套结果映射的区别在于,我们将 javaType 替换为了 ofType,以此来指定 Java 集合中的泛型类型。</p><p>调用 findUserById 查询用户信息,可得到如下结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"address"</span><span class="token operator">:</span><span class="token string">"BUPT"</span><span class="token punctuation">,</span> <span class="token property">"age"</span><span class="token operator">:</span><span class="token number">24</span><span class="token punctuation">,</span> <span class="token property">"createTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"roles"</span><span class="token operator">:</span><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"roleName"</span><span class="token operator">:</span><span class="token string">"管理员"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token property">"roleName"</span><span class="token operator">:</span><span class="token string">"版主"</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"updateTime"</span><span class="token operator">:</span><span class="token number">1637164800000</span><span class="token punctuation">,</span> <span class="token property">"userName"</span><span class="token operator">:</span><span class="token string">"John同学"</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-嵌套查询-1"><a href="#2-嵌套查询-1" class="headerlink" title="2. 嵌套查询"></a>2. 嵌套查询</h3><p>mapper 文件的内容如下:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>collection</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roles<span class="token punctuation">"</span></span> <span class="token attr-name">ofType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_id<span class="token punctuation">=</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">select</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>selectUserRole<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>selectUserRole<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select r.id, r.role_name from user_roles as ur left join role as r on ur.role_id = r.id where ur.user_id = #{user_id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserById<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select id, user_name, age, address, gmt_create, gmt_modified from user where id = #{id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>同样地,我们将 javaType 改为 ofType。</p><h3 id="3-关联多个结果集-1"><a href="#3-关联多个结果集-1" class="headerlink" title="3. 关联多个结果集"></a>3. 关联多个结果集</h3><p>mapper 文件的内容如下:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>User<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>userName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>age<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_create<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>createTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>gmt_modified<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DATE<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateTime<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>collection</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roles<span class="token punctuation">"</span></span> <span class="token attr-name">ofType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Role<span class="token punctuation">"</span></span> <span class="token attr-name">resultSet</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roles<span class="token punctuation">"</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">foreignColumn</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user_id<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_id<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>role_name<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>roleName<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>collection</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findUserById<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Integer<span class="token punctuation">"</span></span> <span class="token attr-name">resultSets</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user,roles<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UserMap<span class="token punctuation">"</span></span> <span class="token attr-name">statementType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>CALLABLE<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> {call findUserAndRole(#{user_id,jdbcType=INTEGER,mode=IN})}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>同理,存储过程中的执行逻辑保持不变,只需将 javaType 改为 ofType。</p><blockquote><p>改用 <code>collection</code> 后,还要注意将 property 由 role 改为 roles。当然,这个名称可自由定义。</p></blockquote><h2 id="查询具有树形结构的数据"><a href="#查询具有树形结构的数据" class="headerlink" title="查询具有树形结构的数据"></a>查询具有树形结构的数据</h2><p>树形结构数据在实际开发中非常常见,比较典型的就是菜单表,每个父菜单都可能包含一个或多个子菜单,而每个子菜单也可能包含孙子菜单。有时候我们希望查询出某个菜单下的所有子菜单,并分级展示,这种情况应该如何处理呢?其实上文中介绍的三种方法均支持多级结果映射,我们只需要在 mapper 文件中做一些简单的处理。</p><p>首先创建 Menu 类:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Menu</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">long</span> id<span class="token punctuation">;</span> <span class="token keyword">private</span> String name<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">long</span> parentId<span class="token punctuation">;</span> <span class="token keyword">private</span> List<span class="token operator"><</span>Menu<span class="token operator">></span> childMenus<span class="token punctuation">;</span> <span class="token keyword">private</span> Date createTime<span class="token punctuation">;</span> <span class="token keyword">private</span> Date updateTime<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后创建 menu 表:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>menu<span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>menu<span class="token punctuation">`</span> <span class="token punctuation">(</span> <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>parent_id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_create<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8mb4 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8mb4_0900_ai_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接着向 menu 表中插入数据:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/menu%E8%A1%A8%E6%95%B0%E6%8D%AE.jpg" alt></p><p>为了更直观地展示各层级菜单之间的关系,我们将数据整理在下面的表格中:</p><table><thead><tr><th align="center">id <div style="width:100px"></div></th><th align="center">name <div style="width:100px"></div></th><th align="center">parent_id <div style="width:100px"></div></th></tr></thead><tbody><tr><td align="center">1</td><td align="center">文章</td><td align="center">0</td></tr><tr><td align="center">11</td><td align="center">所有文章</td><td align="center">1</td></tr><tr><td align="center">12</td><td align="center">写文章</td><td align="center">1</td></tr><tr><td align="center">121</td><td align="center">载入草稿</td><td align="center">12</td></tr><tr><td align="center">2</td><td align="center">用户</td><td align="center">0</td></tr><tr><td align="center">21</td><td align="center">个人资料</td><td align="center">2</td></tr><tr><td align="center">3</td><td align="center">附件</td><td align="center">0</td></tr></tbody></table><p>可以看到,菜单表总共有三个层级(不包含第 0 级),第一级的 “所有文章” 下有子菜单 “写文章”,第二级的 “写文章” 下有子菜单 “载入草稿”。每个层级的菜单都可能有零个、一个或多个子菜单,为了将所有的菜单查询出来,我们既要修改 SQL 语句,又要修改 <code>resultMap</code> 中的映射关系,下面介绍三种查询方式。</p><h3 id="1-嵌套结果映射-2"><a href="#1-嵌套结果映射-2" class="headerlink" title="1. 嵌套结果映射"></a>1. 嵌套结果映射</h3><p>mapper 文件的内容如下:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>menuMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Menu<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parentId<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>collection</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>childMenus<span class="token punctuation">"</span></span> <span class="token attr-name">ofType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Menu<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id2<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name2<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id2<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parentId<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>collection</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>childMenus<span class="token punctuation">"</span></span> <span class="token attr-name">ofType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Menu<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id3<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name3<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id3<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parentId<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>collection</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>collection</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findMenus<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>menuMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select m1.id as id, m1.name as name, m1.parent_id as parent_id, m2.id as id2, m2.name as name2, m2.parent_id as parent_id2, m3.id as id3, m3.name as name3, m3.parent_id as parent_id3 from menu as m1 left join menu as m2 on m1.id = m2.parent_id left join menu as m3 on m2.id = m3.parent_id where m1.parent_id = #{menu_id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>因为菜单表中最多有三个层级,所以我们在 SQL 语句中使用了三表联查,分别从表 m1、m2、m3(均为 menu 表)中查询出各个级别(从上到下)的菜单,然后在 <code>collection</code> 中新增一个嵌套,表 m2 和表 m3 中查出的数据均用于填充前一级别的 childMenus 属性。</p><p>调用 findMenus(参数 menu_id 等于 0)查询菜单信息,可得到如下结果:</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"childMenus"</span><span class="token operator">:</span><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"childMenus"</span><span class="token operator">:</span><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">121</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span><span class="token string">"载入草稿"</span><span class="token punctuation">,</span> <span class="token property">"parentId"</span><span class="token operator">:</span><span class="token number">12</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span><span class="token string">"写文章"</span><span class="token punctuation">,</span> <span class="token property">"parentId"</span><span class="token operator">:</span><span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"childMenus"</span><span class="token operator">:</span><span class="token punctuation">[</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">11</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span><span class="token string">"所有文章"</span><span class="token punctuation">,</span> <span class="token property">"parentId"</span><span class="token operator">:</span><span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span><span class="token string">"文章"</span><span class="token punctuation">,</span> <span class="token property">"parentId"</span><span class="token operator">:</span><span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"childMenus"</span><span class="token operator">:</span><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"childMenus"</span><span class="token operator">:</span><span class="token punctuation">[</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">21</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span><span class="token string">"个人资料"</span><span class="token punctuation">,</span> <span class="token property">"parentId"</span><span class="token operator">:</span><span class="token number">2</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span><span class="token string">"用户"</span><span class="token punctuation">,</span> <span class="token property">"parentId"</span><span class="token operator">:</span><span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"childMenus"</span><span class="token operator">:</span><span class="token punctuation">[</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"id"</span><span class="token operator">:</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span><span class="token string">"附件"</span><span class="token punctuation">,</span> <span class="token property">"parentId"</span><span class="token operator">:</span><span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注意,<code>嵌套结果映射</code> 的方式不具备通用性,因为菜单表的结构可能不止三层。如果有多个层级的菜单,那么我们就需要继续修改 SQL 语句并新增嵌套。</p><h3 id="2-嵌套查询-2"><a href="#2-嵌套查询-2" class="headerlink" title="2. 嵌套查询"></a>2. 嵌套查询</h3><p>mapper 文件的内容如下:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>menuMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Menu<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parentId<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>collection</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{menu_id<span class="token punctuation">=</span>id}<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>childMenus<span class="token punctuation">"</span></span> <span class="token attr-name">ofType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Menu<span class="token punctuation">"</span></span> <span class="token attr-name">select</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findMenus<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findMenus<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>menuMap<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> select id, name, parent_id from menu where parent_id = #{menu_id} <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述代码中,我们将嵌套的子查询设置为 findMenus 本身,MyBatis 首先调用 findMenus 查询出 parent_id 为 menu_id 的菜单,然后将查询出的菜单的 id 赋值给 menu_id,继续调用 findMenus 查询出下一层级的菜单。此种方式可以递归的查询出所有菜单,无论菜单表有多少个层级。</p><h3 id="3-关联多个结果集-2"><a href="#3-关联多个结果集-2" class="headerlink" title="3. 关联多个结果集"></a>3. 关联多个结果集</h3><p>首先创建存储过程 findMenu:</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">delimiter</span> $$<span class="token keyword">create</span> <span class="token keyword">procedure</span> findMenu<span class="token punctuation">(</span><span class="token operator">in</span> menu_id <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token keyword">begin</span> <span class="token keyword">select</span> id <span class="token keyword">as</span> id1<span class="token punctuation">,</span> name <span class="token keyword">as</span> name1<span class="token punctuation">,</span> parent_id <span class="token keyword">as</span> parent_id1 <span class="token keyword">from</span> menu <span class="token keyword">where</span> parent_id <span class="token operator">=</span> menu_id<span class="token punctuation">;</span> <span class="token keyword">select</span> id <span class="token keyword">as</span> id2<span class="token punctuation">,</span> name <span class="token keyword">as</span> name2<span class="token punctuation">,</span> parent_id <span class="token keyword">as</span> parent_id2 <span class="token keyword">from</span> menu<span class="token punctuation">;</span> <span class="token keyword">select</span> id <span class="token keyword">as</span> id3<span class="token punctuation">,</span> name <span class="token keyword">as</span> name3<span class="token punctuation">,</span> parent_id <span class="token keyword">as</span> parent_id3 <span class="token keyword">from</span> menu<span class="token punctuation">;</span><span class="token keyword">end</span> $$<span class="token comment" spellcheck="true">-- 将结束标志符改回 ;</span><span class="token keyword">delimiter</span> <span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后将 mapper 文件的内容修改为:</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>resultMap</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>MenuMap<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Menu<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id1<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name1<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id1<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parentId<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>collection</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>childMenus<span class="token punctuation">"</span></span> <span class="token attr-name">ofType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Menu<span class="token punctuation">"</span></span> <span class="token attr-name">resultSet</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>menu2<span class="token punctuation">"</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id1<span class="token punctuation">"</span></span> <span class="token attr-name">foreignColumn</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id2<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name2<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id2<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parentId<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>collection</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>childMenus<span class="token punctuation">"</span></span> <span class="token attr-name">ofType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Menu<span class="token punctuation">"</span></span> <span class="token attr-name">resultSet</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>menu3<span class="token punctuation">"</span></span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id2<span class="token punctuation">"</span></span> <span class="token attr-name">foreignColumn</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id3<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id3<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>INTEGER<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name3<span class="token punctuation">"</span></span> <span class="token attr-name">jdbcType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>VARCHAR<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>result</span> <span class="token attr-name">column</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parent_id3<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>parentId<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>collection</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>collection</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>resultMap</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>findMenus<span class="token punctuation">"</span></span> <span class="token attr-name">parameterType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Map<span class="token punctuation">"</span></span> <span class="token attr-name">resultSets</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>menu1,menu2,menu3<span class="token punctuation">"</span></span> <span class="token attr-name">resultMap</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>MenuMap<span class="token punctuation">"</span></span> <span class="token attr-name">statementType</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>CALLABLE<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> {call findMenu(#{menu_id,jdbcType=INTEGER,mode=IN})}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>findMenu 中定义了三条 SQL 语句,第一条的执行逻辑是从 menu 表中查询出 parent_id 为 menu_id 的菜单,其它两条的执行逻辑是从 menu 表中查询出所有的菜单。我们将三条查询返回的结果集分别表示为 menu1、menu2 和 menu3,然后利用 menu2 和 menu3 中的数据分别填充子菜单和孙子菜单的属性。</p><p><code>关联多个结果集</code> 和 <code>嵌套结果映射</code> 一样,在查询树形结构数据时不具备通用性。若菜单表的层级大于 3,那么我们就需要修改存储过程和映射关系。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://mybatis.net.cn/sqlmap-xml.html#Auto-mapping" target="_blank" rel="noopener">MyBatis 官方文档</a><br><a href="https://juejin.cn/post/6844903858477481992" target="_blank" rel="noopener">Mybatis 中强大的 resultMap</a></p>]]></content>
<categories>
<category> 技术学习 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> Java </tag>
<tag> Spring Boot </tag>
<tag> MyBatis </tag>
</tags>
</entry>
<entry>
<title>Spring Boot 整合 MyBatis</title>
<link href="/2022/0340561.html"/>
<url>/2022/0340561.html</url>
<content type="html"><![CDATA[<h2 id="MyBatis-简介"><a href="#MyBatis-简介" class="headerlink" title="MyBatis 简介"></a>MyBatis 简介</h2><p>MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Ordinary Java Objects,简单 Java 对象)为数据库中的记录。</p><blockquote><p>参考自 MyBatis 的官方简介。</p></blockquote><p>MyBatis 作为一款优秀的持久层框架,具有如下优点:</p><ol><li><p>小巧并且简单易学。</p></li><li><p>相比于 JDBC 减少了大量冗余的代码。</p></li><li><p>将 SQL 语句与程序代码进行分离,降低了耦合,便于管理。</p></li><li><p>提供 XML 标签,支持编写动态 SQL 语句。</p></li><li><p>提供映射标签,支持 Java 对象与数据库 ORM 字段的映射关系。</p></li></ol><!-- MyBatis 的缺点1. SQL 语句编写工作量较大,尤其是字段和关联表比较多时。2. SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。 --><h2 id="MyBatis-实践"><a href="#MyBatis-实践" class="headerlink" title="MyBatis 实践"></a>MyBatis 实践</h2><p>下面我们创建一个 Spring Boot 项目,整合 MyBatis,实现简单的 CRUD 功能。</p><p><strong>1. 引入依赖</strong></p><p>POM 文件如下:</p><pre class="line-numbers language-XML"><code class="language-XML"><?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springboot-mybatis</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-mybatis</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build></project><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>2. 配置 MySQL 和 MyBatis</strong></p><p>配置文件 application.yml 的内容如下: </p><pre class="line-numbers language-yml"><code class="language-yml"># 配置 MySQLspring: datasource: url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver# 配置 MyBatismybatis: mapper-locations: classpath:mapper/* type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>MyBatis 的配置项中:</p><ul><li><p><code>mapper-locations</code>:用来指定 mapper.xml 文件的路径,该文件用于编写 SQL 语句。</p></li><li><p><code>type-aliases-package</code>:用来设置别名,它的作用是告诉 MyBatis 需要设置别名的实体类的所在的包。默认情况下,MyBatis 会使用实体类的非限定类名来作为它的别名,如将 <code>com.example.entity.User</code> 的别名设置为 <code>User</code> 或 <code>user</code>(别名不区分大小写)。当然,MyBatis 也支持自定义别名,这个我们在后文中再聊。</p></li><li><p><code>map-underscore-to-camel-case</code>:用来开启驼峰命名自动映射,如将数据表中的字段 user_name 映射到实体对象的属性 userName。</p><!-- 这样在 mapper.xml 文件中就可以直接使用非限定类名来作为它的别名,如 "com.example.entity.User" 可使用别名 "User" 代替 --></li></ul><p><strong>3. 实体类</strong></p><p>编写简单的 User 类:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>example<span class="token punctuation">.</span>entity<span class="token punctuation">;</span><span class="token keyword">import</span> lombok<span class="token punctuation">.</span>Data<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>Date<span class="token punctuation">;</span><span class="token comment" spellcheck="true">/** * @Author john * @Date 2021/11/14 */</span><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">long</span> id<span class="token punctuation">;</span> <span class="token keyword">private</span> String userName<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">int</span> age<span class="token punctuation">;</span> <span class="token keyword">private</span> String address<span class="token punctuation">;</span> <span class="token keyword">private</span> Date createTime<span class="token punctuation">;</span> <span class="token keyword">private</span> Date updateTime<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>User 类中封装了用户的 id、姓名、年龄、地址、创建时间以及修改时间等信息。</p><p><strong>4. 创建 user 表</strong></p><p>user 表的字段设计如下:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/user_table.png" alt></p><p><strong>5. 编写 Mapper 接口和 mapper 文件</strong></p><p>首先编写 UserMapper 接口:</p><pre class="line-numbers language-Java"><code class="language-Java">package com.example.mapper;import com.example.entity.User;/** * @Author john * @Date 2021/11/16 */public interface UserMapper { void insertUser(User user); User findUserById(long id);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接口中定义了两个方法,insertUser 用来向数据表中插入一条记录,findUserById 用来通过 id 查询 User。</p><p>上述操作完成后,我们在 resources 文件夹中创建 mapper/user-mapper.xml 文件(文件路径在配置文件 application.yml 中设置)。user-mapper.xml 文件的内容如下:</p><pre class="line-numbers language-XML"><code class="language-XML"><?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.UserMapper"> <sql id="insertFields"> user_name, age, address, gmt_create, gmt_modified </sql> <sql id="selectFields"> id, user_name, age, address, gmt_create, gmt_modified </sql> <resultMap id="UserMap" type="User"> <id column="id" jdbcType="INTEGER" property="id"/> <result column="user_name" jdbcType="VARCHAR" property="userName"/> <result column="age" jdbcType="INTEGER" property="age"/> <result column="address" jdbcType="VARCHAR" property="address"/> <result column="gmt_create" jdbcType="DATE" property="createTime" /> <result column="gmt_modified" jdbcType="DATE" property="updateTime" /> </resultMap> <select id="findUserById" parameterType="Long" resultMap="UserMap"> select <include refid="selectFields"/> from user where id = #{id} </select> <insert id="insertUser" parameterType="User" keyProperty="id"> insert into user (<include refid="insertFields"/>) values(#{userName}, #{age}, #{address}, UTC_TIMESTAMP(), UTC_TIMESTAMP()) </insert></mapper><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看到,Mapper 接口中定义的是 CRUD 相关的方法,mapper.xml 文件中定义的是具体的 SQL 语句。MyBatis 允许我们将 Mapper 接口与 mapper.xml 文件关联在一起,这样当调用 Mapper 接口中的方法时,实际的处理逻辑为执行 mapper.xml 文件中对应的 SQL 语句。关联 Mapper 接口和 mapper.xml 文件时需要保证:</p><ul><li><p>Mapper 接口的全限定名对应 mapper.xml 文件的 namespace 值。</p></li><li><p>Mapper 接口的方法名对应 statement(每一个 SQL 就是一个 statement)的 id 值。</p></li><li><p>Mapper 接口中方法接收的参数对应 statement 的入参。</p></li><li><p>Mapper 接口中方法的返回值对应 statement 的出参。</p></li></ul><p>下面介绍一下 mapper.xml 文件中几个重要标签的含义:</p><ul><li><p><code><sql></code> 标签:用于定义复用的 SQL 片段,如果多个 SQL 需要操作相同的字段集,那么就可以使用 <code><sql></code> 标签将这些字段提取出来,然后在 SQL 语句中直接引用即可。引用的语法为 <code><include refid=" "/></code>,其中 refid 的值就是 <code><sql></code> 的 id 值。</p></li><li><p><code><resultMap></code> 标签:用于创建数据表字段与实体属性的映射关系,在查询操作中,MyBatis 会根据查询到的字段名找到 POJO 对应的属性名,然后调用该属性的 setter 方法进行赋值。如果数据表的字段名与实体类的属性名完全相同,或者符合驼峰式命名映射的规则,那么 MyBatis 可以直接完成赋值操作。否则的话,就需要我们使用 <code><resultMap></code> 标签创建自定义的映射规则,告诉 MyBatis 字段和属性之间应该如何映射。本实验中,user 表的 id 会自动映射为 User 对象的 id,user 表的 user_name 也会自动映射为 User 对象的 userName。但是 gmt_create 和 gmt_modified 不会映射为 createTime 和 updateTime,因为字段名和属性名既不完全一致,也不符合驼峰式命名映射的规则,所以这里我们需要使用 <code><resultMap></code> 来创建新的映射关系,其中属性 id 用于指明该 resultMap 的标志,属性 type 用于指明映射的实体类。</p></li><li><p><code><select></code> 标签:用于执行查询操作。 </p></li><li><p><code><insert></code> 标签:用于执行插入操作。</p></li></ul><blockquote><p>实际上,MyBatis 赋值时不一定会调用实体类属性的 setter 方法,因为我们在编码时可能并没有添加该方法。以 User 类的属性 id 为例,如果我们添加了 setId 方法,那么 MyBatis 会通过反射获取到 setId 对应的 MethodInvoker,然后调用 setId 方法为 id 赋值;如果未设置 setId 方法,那么 MyBatis 会获取属性 id 对应的 SetFieldInvoker,然后为属性赋值。详见 MetaObject 类的 setValue 方法。</p></blockquote><p>接下来介绍 SQL 语句中几个重要属性的含义:</p><ul><li><p>parameterType:用于指定 SQL 语句的入参类型(可以是基本数据类型或者 JavaBean),该类型需要与对应的接口方法的入参类型一致。如果我们设置了别名,那么也可以使用别名作为参数,例如使用 <code>User</code> 或 <code>user</code> 代替 <code>com.example.entity.User</code>。</p></li><li><p>resultMap:用于指定 SQL 语句的出参类型,以 insertUser 方法为例,在 Mapper 接口中,该方法的返回值为 User 类型,所以对应的 SQL 语句的返回值也应为 User 类型,由于 User 对象需要使用 <code><resultMap></code> 进行属性映射,所以我们将自定义的 <code>UserMap</code> 来作为 SQL 语句的返回值类型。</p></li><li><p>keyProperty:用于指定主键在 POJO 中对应的属性名,需要配合数据库的自增主键来使用。以 user 表为例,我们在建表的时候将表的主键 id 设置为了数据库自增 id,因此在将 User 对象持久化到数据库之前不需要为属性 id 设置初始值,MySQL 会自动帮我们赋值,keyProperty 的作用就是告诉 MyBatis 哪个属性是主键。</p></li></ul><!-- parameterType 和 resultType(本实验中未使用)分别用来指定参数和返回值的类型,因为我们在配置文件中启用了别名,所以 parameterType 和 resultType 可以直接设置为 "User" 或 "user",而非全限定名 "com.example.entity.User"。 --><!-- mapper 文件中还有一个关键标签 resultMap,resultMap 主要用来创建自定义的映射关系。以查询为例,MyBatis 的映射规则是:根据查询到的字段名找到 POJO 对应的属性名,然后调用该属性的 setter 方法进行赋值。本实验中,user 表的 id 会自动映射为 User 对象的 id,因为字段名和属性名是完全一致的;user 表的 user_name 也会自动映射为 User 对象的 userName,因为我们开启了驼峰式命名映射。但是 gmt_create 不会映射到 createTime,因为字段名和属性名既不完全一致,也不符合驼峰式命名映射的规则。所以这里我们使用 resultMap 来创建新的映射关系,将 gmt_create 和 gmt_modified 分别映射到 createTime 和 updateTime,然后再将 resultType 替换为 resultMap(如果不需要自定义映射,本实验中的 resultMap='UserMap' 可替换为 resultType='User')。 --><p>除了 resultMap 外,resultType 属性也可用于指定出参类型。如果我们将 user 表中的字段 gmt_create 和 gmt_modified 分别改为 create_time 和 update_time,那么就不需要使用 <code><resultMap></code> 标签来配置映射规则,因为 user 表的所有字段都可以和 User 对象的属性一一对应,这样在 SQL 语句中,就可以将 <code>resultMap="UserMap"</code> 替换为 <code>resulType="User"</code> 或 <code>resulType="user"</code>。另外,在本实验中,resultMap 标签也可以定义为:</p><pre class="line-numbers language-XML"><code class="language-XML"><resultMap id="UserMap" type="User"> <result column="gmt_create" jdbcType="DATE" property="createTime" /> <result column="gmt_modified" jdbcType="DATE" property="updateTime" /></resultMap><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>因为其他字段会自动映射,不需要额外书写。</p><p><strong>6. 编写 Service</strong></p><p>创建 UserService:</p><pre class="line-numbers language-Java"><code class="language-Java">package com.example.service;import com.example.entity.User;import com.example.mapper.UserMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * @Author john * @Date 2021/11/16 */@Servicepublic class UserService { @Autowired private UserMapper userMapper; public void insertUser(User user) { userMapper.insertUser(user); } public User findUserById(long id) { return userMapper.findUserById(id); }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在 UserService 中注入 UserMapper 对象,并调用相关方法来添加/查询 User。</p><p>为了能够正常注入 UserMapper 对象,我们还需要再启动类上添加 @MapperScan 注解,并指定 Mapper 接口所在的包:</p><pre class="line-numbers language-Java"><code class="language-Java">package com.example;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication@MapperScan("com.example.mapper")public class SpringbootMybatisApplication { public static void main(String[] args) { SpringApplication.run(SpringbootMybatisApplication.class, args); }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>com.example.mapper</code> 包下的所有 Mapper 接口都会被 Spring 扫描。</p><p>除了在启动类上添加 @MapperScan 注解外,还可以在 Mapper 接口上直接添加 @Mapper 注解,这种方法相对比较麻烦,因为实际中我们可能会有多个 Mapper 接口,这样就需要添加多个注解。</p><p><strong>7. 测试</strong></p><p>编写测试接口:</p><pre class="line-numbers language-Java"><code class="language-Java">package com.example;import com.example.entity.User;import com.example.service.UserService;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTestclass SpringbootMybatisApplicationTests { @Autowired private UserService service; @Test public void addUser(){ User user = new User(); user.setUserName("John"); user.setAge(24); user.setAddress("BUPT"); service.insertUser(user); } @Test public void findUser(){ System.out.println(service.findUserById(1)); }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>首先执行 addUser() 方法,执行成功后查询数据表,得到如下信息:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/select.png" alt></p><p>然后执行 findUser() 方法,执行结果如下:</p><p><img src="https://johnlearning.oss-cn-beijing.aliyuncs.com/blog/technology/MyBatis/select_test.png" alt></p><p>至此,SpringBoot 整合 MyBatis 测试成功!</p><h2 id="MyBatis-设置别名的方式"><a href="#MyBatis-设置别名的方式" class="headerlink" title="MyBatis 设置别名的方式"></a>MyBatis 设置别名的方式</h2><p><strong>方式一</strong>:在配置文件 application.yml 中添加配置。</p><p>yml 文件的配置内容如下:</p><pre class="line-numbers language-yml"><code class="language-yml">mybatis: type-aliases-package: com.example.entity<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>本实验采用此种方式设置别名,默认情况下实体类的别名为其类名,严格来说是首字母小写的非限定类名,由于别名不区分大小写,所以 <code>User</code>、<code>user</code>、<code>uSer</code> 的效果都是相同的。</p><p>MyBatis 也支持自定义别名,我们只需要在实体类上添加 @Alias 注解,就可以为其设置别名:</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>example<span class="token punctuation">.</span>entity<span class="token punctuation">;</span><span class="token keyword">import</span> lombok<span class="token punctuation">.</span>Data<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>ibatis<span class="token punctuation">.</span>type<span class="token punctuation">.</span>Alias<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>Date<span class="token punctuation">;</span><span class="token comment" spellcheck="true">/** * @Author john * @Date 2021/11/14 */</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@Alias</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">long</span> id<span class="token punctuation">;</span> <span class="token keyword">private</span> String userName<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">int</span> age<span class="token punctuation">;</span> <span class="token keyword">private</span> String address<span class="token punctuation">;</span> <span class="token keyword">private</span> Date createTime<span class="token punctuation">;</span> <span class="token keyword">private</span> Date updateTime<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述代码中,我们将 User 类的别名设置为了 <code>hello</code>。注意,若要使 @Alias 注解生效,必须配置 <code>type-aliases-package</code> 来指定实体类的包路径。另外,@Alias 会使默认的别名变得无效,例如在本实验中,User 类的别名只能是 <code>hello</code>,而不能是 <code>User</code> 或 <code>user</code> 等。 </p><p><strong>方式二</strong>:使用 MyBatis 的配置文件 filename.xml。</p><p>首先在 yml 文件中设置 MyBatis 配置文件 filename.xml(filename 是配置文件的名称)的路径:</p><pre class="line-numbers language-YML"><code class="language-YML"># 配置MyBatismybatis: mapper-locations: classpath:mapper/* config-location: classpath:mybatis/mybatis-config.xml #MyBatis配置文件<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>然后在 resource 文件夹下创建 MyBatis 的配置文件 mapper/mybatis-config.xml(路径和文件名在 config-location 中设置),配置文件内容如下:</p><pre class="line-numbers language-XML"><code class="language-XML"><?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <typeAliases> <package name="com.example.entity"/> </typeAliases></configuration><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>几个重要标签的含义为:</p><ul><li><p><code><setting></code> 标签:用于开启驼峰命名映射,其效果与在 yml 文件中配置 <code>map-underscore-to-camel-case: true</code> 是相同的。</p></li><li><p><code><typeAliases></code> 标签:用于配置别名,子标签 <code><package></code> 可以让 MyBatis 扫描指定包下的实体类,其效果与在 yml 文件中配置 <code>type-aliases-package: com.example.entity</code> 是相同的。</p></li></ul><p>在方式一中,我们可以使用 @Alias 注解自定义别名,而在方式二中,我们可以通过<code><typeAliases></code> 的子标签 <code><typeAlias></code> 来设置别名:</p><pre class="line-numbers language-XML"><code class="language-XML"><?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <typeAliases> <typeAlias type="com.example.entity.User" alias="hello"/> </typeAliases></configuration><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code><typeAlias></code> 标签不需要配置 <code>type-aliases-package</code> 就可以生效,且该标签与 <code><package></code> 标签并不冲突,也就是说如果我们添加了 <code><package name="com.example.entity"/></code>,那么 User 类的别名既可以是 <code>hello</code>,也可以是 <code>User</code> 或 <code>user</code> 等。当然,方式二中也可以添加 @Alias 注解,但添加了该注解后,User 类的别名只能为 <code>hello</code> 或 @Alias 注解指定的别名。</p>]]></content>
<categories>
<category> 技术学习 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> Java </tag>
<tag> Spring Boot </tag>
<tag> MyBatis </tag>
</tags>
</entry>
<entry>
<title>Git 常用指令(持续更新)</title>
<link href="/2020/0632512.html"/>
<url>/2020/0632512.html</url>
<content type="html"><![CDATA[<h2 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h2><p>Git 是目前最流行的版本控制工具,熟练使用 Git 是每个程序员必备的技能。奈何自己非科班出身,对 Git 的使用少之又少,因此撰写本篇博文来记录常用的 Git 指令,以作备忘。</p><h2 id="查看分支"><a href="#查看分支" class="headerlink" title="查看分支"></a>查看分支</h2><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 查看当前分支</span><span class="token function">git</span> branch<span class="token comment" spellcheck="true"># 查看远程分支</span><span class="token function">git</span> branch -r<span class="token comment" spellcheck="true"># 查看所有分支</span><span class="token function">git</span> branch -a<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="新建分支"><a href="#新建分支" class="headerlink" title="新建分支"></a>新建分支</h2><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 1. 新建本地分支,但不会切换到该分支,如果在 xxx 分支下执行该命令,那么 new_branch 分支的代码与 xxx 分支相同</span><span class="token function">git</span> branch new_branch<span class="token comment" spellcheck="true"># 2. 切换分支</span><span class="token function">git</span> checkout new_branch<span class="token comment" spellcheck="true"># 3. 上述两条命令等效于如下命令(新建本地分支,并切换到该分支)</span><span class="token function">git</span> checkout -b new_branch<span class="token comment" spellcheck="true"># 4. 基于 master 分支的代码创建新分支 new_branch,并切换到该分支</span><span class="token function">git</span> checkout master<span class="token function">git</span> checkout -b new_branch<span class="token comment" spellcheck="true"># 5. 基于远程分支 remote_branch 的代码创建新分支 new_branch,并切换到该分支</span><span class="token comment" spellcheck="true"># 法一:先创建再拉取</span><span class="token function">git</span> checkout -b new_branch<span class="token function">git</span> push origin remote_branch<span class="token comment" spellcheck="true"># 法二:上述两条命令等效于如下命令</span><span class="token function">git</span> checkout -b new_branch origin/remote_branch<span class="token comment" spellcheck="true"># 6. 远程仓库创建了新的分支,但本地仓库并未同步到(使用 git branch -a 获取不到新创建的分支),可先执行如下命令,然后就能看到新的分支了</span><span class="token function">git</span> fetch<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="查看关联信息"><a href="#查看关联信息" class="headerlink" title="查看关联信息"></a>查看关联信息</h2><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 查看本地分支与远程分支的对应关系(使用下面三条命令中的任何一个)</span><span class="token function">git</span> branch -vv<span class="token function">git</span> remote show origin<span class="token function">cat</span> .git/config<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h2 id="提交代码"><a href="#提交代码" class="headerlink" title="提交代码"></a>提交代码</h2><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 1. 提交代码到本地缓冲区</span><span class="token comment" spellcheck="true"># 1.1 逐个添加被修改的文件</span><span class="token function">git</span> add file1.java file2.java file3.xml<span class="token comment" spellcheck="true"># 1.2 添加所有被修改的文件</span><span class="token function">git</span> add <span class="token keyword">.</span><span class="token comment" spellcheck="true"># 2. 提交代码并添加修改说明</span><span class="token function">git</span> commit -m <span class="token string">'修改说明'</span><span class="token comment" spellcheck="true"># 3. 确保当前分支 current_branch 上的代码是最新的,即拉取最新的代码(可省略该步骤,若不是最新的代码,git 会返回提示信息)</span><span class="token function">git</span> pull<span class="token comment" spellcheck="true"># 4. 将代码推送到远程分支</span><span class="token function">git</span> push origin current_branch<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="合并分支"><a href="#合并分支" class="headerlink" title="合并分支"></a>合并分支</h2><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 将当前分支 current_branch 合并到 master</span><span class="token function">git</span> checkout master<span class="token function">git</span> merge current_branch <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h2 id="删除分支"><a href="#删除分支" class="headerlink" title="删除分支"></a>删除分支</h2><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 1. 删除本地分支 current_branch(不能处于即将被删除的分支,需要切换到其它分支)</span><span class="token function">git</span> branch -d current_branch<span class="token comment" spellcheck="true"># 2. 删除远程分支 remote_branch</span><span class="token function">git</span> push origin -d remote_branch <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="修改分支名称"><a href="#修改分支名称" class="headerlink" title="修改分支名称"></a>修改分支名称</h2><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 1. 将本地分支 old_branch 和远程分支 old_branch 的名称均修改为 new_branch</span><span class="token comment" spellcheck="true"># 1.1 切换到 old_branch 分支下</span><span class="token function">git</span> checkout old_branch<span class="token comment" spellcheck="true"># 1.2 确保本地分支的代码是最新的</span><span class="token function">git</span> pull origin old_branch<span class="token comment" spellcheck="true"># 1.3 修改本地分支的名称</span><span class="token function">git</span> branch -m old_branch new_branch<span class="token comment" spellcheck="true"># 1.4 删除远程分支</span><span class="token function">git</span> push origin --d old_branch<span class="token comment" spellcheck="true"># 1.5 将本地分支推送到远程仓库</span><span class="token function">git</span> push origin new_branch<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://mp.weixin.qq.com/s/swnwBiuyVmhs5iPqv3H6BQ" target="_blank" rel="noopener">Git操作与命令看这篇足矣</a></p>]]></content>
<categories>
<category> 工具使用 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> Git </tag>
</tags>
</entry>
<entry>
<title>后端开发知识点合集</title>
<link href="/2020/0637666.html"/>
<url>/2020/0637666.html</url>
<content type="html"><![CDATA[<h2 id="仓库简介"><a href="#仓库简介" class="headerlink" title="仓库简介"></a>仓库简介</h2><p>🎓本人毕业于<a href="https://www.bupt.edu.cn/" target="_blank" rel="noopener">北京邮电大学</a>信息与通信工程学院,研二下半学期自学转码,作为 “半路出家” 的非科班程序员,我深切体会到了扎实基础的重要性,故打算利用空闲时间维护一个 Git 仓库来记录后端开发的常用知识点,该仓库涵盖了 Java 编程、计网、操作系统、数据库、框架、中间件、数据结构与算法等相关内容,希望对大家有所帮助。</p><h2 id="仓库地址"><a href="#仓库地址" class="headerlink" title="仓库地址"></a>仓库地址</h2><p>😁欢迎访问:<a href="https://JavaerJohn.github.io/" target="_blank" rel="noopener">JohnLearning</a>📚。</p>]]></content>
<categories>
<category> 后端 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> 开发 </tag>
</tags>
</entry>
<entry>
<title>简单知识点备忘</title>
<link href="/2019/0660486.html"/>
<url>/2019/0660486.html</url>
<content type="html"><![CDATA[<h2 id="带宽的单位是-Hz-还是-bps"><a href="#带宽的单位是-Hz-还是-bps" class="headerlink" title="带宽的单位是 Hz 还是 bps"></a>带宽的单位是 Hz 还是 bps</h2><p> 严格来讲带宽的单位确实是 Hz,带宽指的是介质能够传输的信号的频带范围,所以其单位为赫兹(Hz),Hz 通常在模拟通信系统中(传输波形信号)使用。实际应用中,介质的带宽值和单位时间内传输的数据量(即速率,bps)是有一定比例关系的,就像马路的宽度和通过的车的数量有关系一样,bps 通常在数字通信系统中(传输数字信号)使用。另外,现在的模拟系统通过调制技术也可以传输数字信号,因此现在基本上都是用 bps 来描述带宽,以此来表明信道的数据的传输能力。</p>]]></content>
<categories>
<category> 杂记 </category>
</categories>
<tags>
<tag> 后端 </tag>
<tag> 知识点 </tag>
</tags>
</entry>
<entry>
<title>动态规划算法题解</title>
<link href="/2019/0663066.html"/>
<url>/2019/0663066.html</url>
<content type="html"><![CDATA[<h2 id="926-将字符串翻转到单调递增(Medium)"><a href="#926-将字符串翻转到单调递增(Medium)" class="headerlink" title="926. 将字符串翻转到单调递增(Medium)"></a>926. 将字符串翻转到单调递增(Medium)</h2><p><a href="https://leetcode.com/problems/flip-string-to-monotone-increasing/" target="_blank" rel="noopener">Leetcode</a> / <a href="https://leetcode.cn/problems/flip-string-to-monotone-increasing/" target="_blank" rel="noopener">力扣</a></p><p>题目描述:如果一个二进制字符串,是以一些 <code>0</code>(可能没有 <code>0</code>)后面跟着一些 <code>1</code>(也可能没有 <code>1</code>)的形式组成的,那么该字符串是 <code>单调递增</code> 的。给你一个二进制字符串 <code>s</code>,你可以将任何 <code>0</code> 翻转为 <code>1</code> 或者将 <code>1</code> 翻转为 <code>0</code>,返回使 <code>s</code> 单调递增的最小翻转次数。</p><pre class="line-numbers language-html"><code class="language-html">Input:s = "00110"Output:1<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>解法一(模拟):假设下标 <code>i</code> 为递增字符串中字符 <code>0</code> 和字符 <code>1</code> 的分界线,即下标位于 <code>[0, i - 1]</code> 的所有字符均为 <code>0</code>,之后的所有字符均为 <code>1</code>,<code>i</code> 可能是 <code>0</code> 到 <code>s.length() - 1</code> 的任何值。在字符串 <code>s</code> 中,计算从下标 <code>0</code> 到下标 <code>i - 1</code> 的所有字符 <code>0</code> 的个数,记为 <code>count0</code>;计算从下标 <code>i</code> 到下标 <code>s.length() - 1</code> 的所有字符 <code>1</code> 的个数,记为 <code>count1</code>。<code>s.length() - count0 - count1</code> 就是需要翻转的次数,通过比较 <code>i</code> 取不同值时的次数便可得出答案。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">minFlipsMonoIncr</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> chars <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">toCharArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> n <span class="token operator">=</span> chars<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> countsZero <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> countsOne <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">int</span> count0 <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> count1 <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>chars<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token string">'0'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> count0<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>chars<span class="token punctuation">[</span>n <span class="token operator">-</span> i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token string">'1'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> count1<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> countsZero<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> count0<span class="token punctuation">;</span> countsOne<span class="token punctuation">[</span>n <span class="token operator">-</span> i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> count1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">int</span> ans <span class="token operator">=</span> n<span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ans <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>ans<span class="token punctuation">,</span> n <span class="token operator">-</span> countsZero<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">-</span> countsOne<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> ans<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>时间复杂度:<code>O(n)</code>,空间复杂度:<code>O(n)</code>。</p><p>解法二(动态规划):令 <code>dp[i]</code> 表示将子串 <code>s[0 : i]</code>转化为递增子串所需的翻转次数,如果 <code>s[i]</code> 为 <code>1</code>,那么该字符不会影响翻转次数,即 <code>dp[i] = dp[i - 1]</code>。若 <code>s[i]</code> 为 <code>0</code>,则需要分两种情况:</p><ol><li><p>将 <code>s[i]</code> 由 <code>0</code> 翻转到 <code>1</code>。</p></li><li><p>将子串 <code>s[0 : i - 1]</code> 中的所有 <code>1</code> 翻转到 <code>0</code>。</p></li></ol><p><code>dp[i]</code> 为两种情况取较小值,故有 <code>dp[i] = min{dp[i - 1] + 1, oneCount}</code>,因此我们需要记录字符 <code>1</code> 的数量。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">minFlipsMonoIncr</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> dp <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> cnt <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'1'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">++</span>cnt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> dp <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>dp <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> cnt<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> dp<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>时间复杂度:<code>O(n)</code>,空间复杂度:<code>O(1)</code>。</p>]]></content>
<categories>
<category> 算法 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> LeetCode </tag>
</tags>
</entry>
</search>