-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
360 lines (260 loc) · 131 KB
/
index.html
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
<!doctype html>
<html lang="zh"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><title>塑料内存</title><link rel="manifest" href="/manifest.json"><meta name="application-name" content="塑料内存"><meta name="msapplication-TileImage" content="/images/favicon.svg"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-title" content="塑料内存"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="description" content="随笔"><meta property="og:type" content="website"><meta property="og:title" content="塑料内存"><meta property="og:url" content="http://blog.yamato.moe/"><meta property="og:site_name" content="塑料内存"><meta property="og:description" content="随笔"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="http://blog.yamato.moe/img/og_image.png"><meta property="article:author" content="NightWish417"><meta property="twitter:card" content="summary"><meta property="twitter:image" content="/img/og_image.png"><script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","mainEntityOfPage":{"@type":"WebPage","@id":"http://blog.yamato.moe"},"headline":"塑料内存","image":["http://blog.yamato.moe/img/og_image.png"],"author":{"@type":"Person","name":"NightWish417"},"description":"随笔"}</script><link rel="icon" href="/images/favicon.svg"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.0/css/all.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/atom-one-light.css"><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;600&family=Source+Code+Pro"><link rel="stylesheet" href="/css/default.css"><style>body>.footer,body>.navbar,body>.section{opacity:0}</style><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/lightgallery.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/justifiedGallery.min.css"><!--!--><!--!--><script src="https://cdn.jsdelivr.net/npm/[email protected]/pace.min.js"></script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/outdatedbrowser/outdatedbrowser.min.css"><!--!--><!--!--><!--!--><meta name="generator" content="Hexo 5.4.0"></head><body class="is-3-column"><nav class="navbar navbar-main"><div class="container"><div class="navbar-brand justify-content-center"><a class="navbar-item navbar-logo" href="/"><img src="/img/logo.svg" alt="塑料内存" height="28"></a></div><div class="navbar-menu"><div class="navbar-start"><a class="navbar-item is-active" href="/">Home</a><a class="navbar-item" href="/archives">Archives</a><a class="navbar-item" href="/categories">Categories</a><a class="navbar-item" href="/tags">Tags</a><a class="navbar-item" href="/about">About</a></div><div class="navbar-end"><a class="navbar-item" target="_blank" rel="noopener" title="Download on GitHub" href="http://github.com/sage417/sage417.github.io"><i class="fab fa-github"></i></a><a class="navbar-item search" title="搜索" href="javascript:;"><i class="fas fa-search"></i></a></div></div></div></nav><section class="section"><div class="container"><div class="columns"><div class="column order-2 column-main is-8-tablet is-8-desktop is-6-widescreen"><div class="card"><div class="card-image"><a class="image is-7by3" href="/2023/01/30/2023/2023-01-30_Sharding_JDBC%20%E5%85%A5%E9%97%A8/"><img class="fill" src="https://s2.loli.net/2023/02/01/ILVjupP8kXUzhEy.jpg" alt="Sharding-JDBC入门"></a></div><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2023-01-29T16:00:00.000Z" title="2023/1/30 00:00:00">2023-01-30</time>发表</span><span class="level-item"><time dateTime="2023-02-01T14:36:23.442Z" title="2023/2/1 22:36:23">2023-02-01</time>更新</span><span class="level-item"><a class="link-muted" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/">中间件</a><span> / </span><a class="link-muted" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/mysql/">mysql</a></span><span class="level-item">1 小时读完 (大约8589个字)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2023/01/30/2023/2023-01-30_Sharding_JDBC%20%E5%85%A5%E9%97%A8/">Sharding-JDBC入门</a></h1><div class="content"><h2 id="一、Sharding-JDBC-简介"><a href="#一、Sharding-JDBC-简介" class="headerlink" title="一、Sharding-JDBC 简介"></a><strong>一、Sharding-JDBC 简介</strong></h2><p>Sharding-JDBC 最早是当当网内部使用的一款分库分表框架,到2017年的时候才开始对外开源,这几年在大量社区贡献者的不断迭代下,功能也逐渐完善,现已更名为 ShardingSphere,2020年4⽉16⽇正式成为 Apache 软件基⾦会的顶级项⽬。</p>
<p>随着版本的不断更迭 ShardingSphere 的核心功能也变得多元化起来。从最开始 Sharding-JDBC 1.0 版本只有数据分片,到 Sharding-JDBC 2.0 版本开始支持数据库治理(注册中心、配置中心等等),再到 Sharding-JDBC 3.0版本又加分布式事务 (支持 Atomikos、Narayana、Bitronix、Seata),如今已经迭代到了 Sharding-JDBC 4.0 版本。</p>
<p><img src="https://s2.loli.net/2023/01/31/THGQO8vKcp9weW1.png" alt="37cd2d870a2f68a5304997aec17b53d81f7f49.png"></p>
<p>现在的 ShardingSphere 不单单是指某个框架而是一个生态圈,这个生态圈 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 这三款开源的分布式数据库中间件解决方案所构成。</p>
<p>ShardingSphere 的前身就是 Sharding-JDBC,所以它是整个框架中最为经典、成熟的组件,我们先从 Sharding-JDBC 框架入手学习分库分表。</p>
<h2 id="二、核心概念"><a href="#二、核心概念" class="headerlink" title="二、核心概念"></a><strong>二、核心概念</strong></h2><p>在开始 Sharding-JDBC分库分表具体实战之前,我们有必要先了解分库分表的一些核心概念。</p>
<h3 id="分片"><a href="#分片" class="headerlink" title="分片"></a><em><strong>分片</strong></em></h3><p>一般我们在提到分库分表的时候,大多是以水平切分模式(水平分库、分表)为基础来说的,数据分片将原本一张数据量较大的表 t_order 拆分生成数个表结构完全一致的小数据量表 t_order_0、t_order_1、···、t_order_n,每张表只存储原大表中的一部分数据,当执行一条SQL时会通过 分库策略、分片策略 将数据分散到不同的数据库、表内。</p>
<p><img src="https://s2.loli.net/2023/01/31/BcwTpZsRJN8OfhH.png" alt="357c9a6065cf897f9ac02664575f01f8fd7f8f.png"></p>
<h3 id="数据节点"><a href="#数据节点" class="headerlink" title="数据节点"></a><strong>数据节点</strong></h3><p>数据节点是分库分表中一个不可再分的最小数据单元(表),它由数据源名称和数据表组成,例如上图中 order_db_1.t_order_0、order_db_2.t_order_1 就表示一个数据节点。</p>
<h3 id="逻辑表"><a href="#逻辑表" class="headerlink" title="逻辑表"></a><strong>逻辑表</strong></h3><p>逻辑表是指一组具有相同逻辑和数据结构表的总称。比如我们将订单表t_order 拆分成 t_order_0 ··· t_order_9 等 10张表。此时我们会发现分库分表以后数据库中已不在有 t_order 这张表,取而代之的是 t_order_n,但我们在代码中写 SQL 依然按 t_order 来写。此时 t_order 就是这些拆分表的逻辑表。</p>
<h3 id="真实表"><a href="#真实表" class="headerlink" title="真实表"></a><strong>真实表</strong></h3><p>真实表也就是上边提到的 t_order_n 数据库中真实存在的物理表。</p>
<h3 id="分片键"><a href="#分片键" class="headerlink" title="分片键"></a><strong>分片键</strong></h3><p>用于分片的数据库字段。我们将 t_order 表分片以后,当执行一条SQL时,通过对字段 order_id 取模的方式来决定,这条数据该在哪个数据库中的哪个表中执行,此时 order_id 字段就是 t_order 表的分片健。</p>
<p><img src="https://s2.loli.net/2023/01/31/4QznMO2TCwKaESg.png" alt="391fef3850fd6eaef447679258468417b4fabf.png"></p>
<p>这样以来同一个订单的相关数据就会存在同一个数据库表中,大幅提升数据检索的性能,不仅如此 sharding-jdbc 还支持根据多个字段作为分片健进行分片。</p>
<h3 id="分片算法"><a href="#分片算法" class="headerlink" title="分片算法"></a><strong>分片算法</strong></h3><p>上边我们提到可以用分片健取模的规则分片,但这只是比较简单的一种,在实际开发中我们还希望用 >=、<=、>、<、BETWEEN 和 IN 等条件作为分片规则,自定义分片逻辑,这时就需要用到分片策略与分片算法。</p>
<p>从执行 SQL 的角度来看,分库分表可以看作是一种路由机制,把 SQL 语句路由到我们期望的数据库或数据表中并获取数据,分片算法可以理解成一种路由规则。</p>
<p>咱们先捋一下它们之间的关系,分片策略只是抽象出的概念,它是由分片算法和分片健组合而成,分片算法做具体的数据分片逻辑。</p>
<p>分库、分表的分片策略配置是相对独立的,可以各自使用不同的策略与算法,每种策略中可以是多个分片算法的组合,每个分片算法可以对多个分片健做逻辑判断。</p>
<p><img src="https://s2.loli.net/2023/01/31/cwdgNeqvKskS73F.png" alt="17e8c31099548f64886625a65b19ab87893fd8.png"></p>
<p>分片算法和分片策略的关系</p>
<p>注意:sharding-jdbc 并没有直接提供分片算法的实现,需要开发者根据业务自行实现。</p>
<p>sharding-jdbc 提供了4种分片算法:</p>
<h4 id="1、精确分片算法"><a href="#1、精确分片算法" class="headerlink" title="1、精确分片算法"></a><strong>1、精确分片算法</strong></h4><p>精确分片算法(PreciseShardingAlgorithm)用于单个字段作为分片键,SQL中有 = 与 IN 等条件的分片,需要在标准分片策略(StandardShardingStrategy )下使用。</p>
<h4 id="2、范围分片算法"><a href="#2、范围分片算法" class="headerlink" title="2、范围分片算法"></a><strong>2、范围分片算法</strong></h4><p>范围分片算法(RangeShardingAlgorithm)用于单个字段作为分片键,SQL中有 BETWEEN AND、>、<、>=、<= 等条件的分片,需要在标准分片策略(StandardShardingStrategy )下使用。</p>
<h4 id="3、复合分片算法"><a href="#3、复合分片算法" class="headerlink" title="3、复合分片算法"></a><strong>3、复合分片算法</strong></h4><p>复合分片算法(ComplexKeysShardingAlgorithm)用于多个字段作为分片键的分片操作,同时获取到多个分片健的值,根据多个字段处理业务逻辑。需要在复合分片策略(ComplexShardingStrategy )下使用。</p>
<h4 id="4、Hint分片算法"><a href="#4、Hint分片算法" class="headerlink" title="4、Hint分片算法"></a><strong>4、Hint分片算法</strong></h4><p>Hint分片算法(HintShardingAlgorithm)稍有不同,上边的算法中我们都是解析SQL 语句提取分片键,并设置分片策略进行分片。但有些时候我们并没有使用任何的分片键和分片策略,可还想将 SQL 路由到目标数据库和表,就需要通过手动干预指定SQL的目标数据库和表信息,这也叫强制路由。</p>
<h3 id="分片策略"><a href="#分片策略" class="headerlink" title="分片策略"></a><strong>分片策略</strong></h3><p>上边讲分片算法的时候已经说过,分片策略是一种抽象的概念,实际分片操作的是由分片算法和分片健来完成的。</p>
<h4 id="1、标准分片策略"><a href="#1、标准分片策略" class="headerlink" title="1、标准分片策略"></a><strong>1、标准分片策略</strong></h4><p>标准分片策略适用于单分片键,此策略支持 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 两个分片算法。</p>
<p>其中 PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片。RangeShardingAlgorithm 是可选的,用于处理BETWEEN AND, >, <,>=,<= 条件分片,如果不配置RangeShardingAlgorithm,SQL中的条件等将按照全库路由处理。</p>
<h4 id="2、复合分片策略"><a href="#2、复合分片策略" class="headerlink" title="2、复合分片策略"></a><strong>2、复合分片策略</strong></h4><p>复合分片策略,同样支持对 SQL语句中的 =,>, <, >=, <=,IN和 BETWEEN AND 的分片操作。不同的是它支持多分片键,具体分配片细节完全由应用开发者实现。</p>
<h4 id="3、行表达式分片策略"><a href="#3、行表达式分片策略" class="headerlink" title="3、行表达式分片策略"></a><strong>3、行表达式分片策略</strong></h4><p>行表达式分片策略,支持对 SQL语句中的 = 和 IN 的分片操作,但只支持单分片键。这种策略通常用于简单的分片,不需要自定义分片算法,可以直接在配置文件中接着写规则。</p>
<p>t_order_$->{t_order_id % 4} 代表 t_order 对其字段 t_order_id取模,拆分成4张表,而表名分别是t_order_0 到 t_order_3。</p>
<h4 id="4、Hint分片策略"><a href="#4、Hint分片策略" class="headerlink" title="4、Hint分片策略"></a><strong>4、Hint分片策略</strong></h4><p>Hint分片策略,对应上边的Hint分片算法,通过指定分片健而非从 SQL中提取分片健的方式进行分片的策略。</p>
<h3 id="分布式主键"><a href="#分布式主键" class="headerlink" title="分布式主键"></a><strong>分布式主键</strong></h3><p>数据分⽚后,不同数据节点⽣成全局唯⼀主键是⾮常棘⼿的问题,同⼀个逻辑表(t_order)内的不同真实表(t_order_n)之间的⾃增键由于⽆法互相感知而产⽣重复主键。</p>
<p>尽管可通过设置⾃增主键 初始值 和 步⻓ 的⽅式避免ID碰撞,但这样会使维护成本加大,乏完整性和可扩展性。如果后去需要增加分片表的数量,要逐一修改分片表的步长,运维成本非常高,所以不建议这种方式。</p>
<p>实现分布式主键⽣成器的方式很多,可以参考我之前写的<a href="https://mp.weixin.qq.com/s/aJNatXLSmZnI_xZRsAo9fg">《9种分布式ID生成方式》</a>。</p>
<p>为了让上手更加简单,ApacheShardingSphere 内置了UUID、SNOWFLAKE 两种分布式主键⽣成器,默认使⽤雪花算法(snowflake)⽣成64bit的⻓整型数据。不仅如此它还抽离出分布式主键⽣成器的接口,⽅便我们实现⾃定义的⾃增主键⽣成算法。</p>
<h3 id="广播表"><a href="#广播表" class="headerlink" title="广播表"></a><strong>广播表</strong></h3><p>广播表:存在于所有的分片数据源中的表,表结构和表中的数据在每个数据库中均完全一致。一般是为字典表或者配置表 t_config,某个表一旦被配置为广播表,只要修改某个数据库的广播表,所有数据源中广播表的数据都会跟着同步。</p>
<h3 id="绑定表"><a href="#绑定表" class="headerlink" title="绑定表"></a><strong>绑定表</strong></h3><p>绑定表:那些分片规则一致的主表和子表。比如:t_order 订单表和 t_order_item 订单服务项目表,都是按 order_id 字段分片,因此两张表互为绑定表关系。</p>
<p>那绑定表存在的意义是啥呢?</p>
<p>通常在我们的业务中都会使用 t_order 和 t_order_item 等表进行多表联合查询,但由于分库分表以后这些表被拆分成N多个子表。如果不配置绑定表关系,会出现笛卡尔积关联查询,将产生如下四条SQL。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_0 o <span class="keyword">JOIN</span> t_order_item_0 i <span class="keyword">ON</span> o.order_id<span class="operator">=</span>i.order_id </span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_0 o <span class="keyword">JOIN</span> t_order_item_1 i <span class="keyword">ON</span> o.order_id<span class="operator">=</span>i.order_id </span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_1 o <span class="keyword">JOIN</span> t_order_item_0 i <span class="keyword">ON</span> o.order_id<span class="operator">=</span>i.order_id </span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_1 o <span class="keyword">JOIN</span> t_order_item_1 i <span class="keyword">ON</span> o.order_id<span class="operator">=</span>i.order_id</span><br></pre></td></tr></table></figure>
<p><img src="https://s2.loli.net/2023/01/31/1uDGhNOJVq54wKm.png" alt="476aa9d21749fad3d77018b69ecac4c58f5d4c.png"></p>
<p>笛卡尔积查询</p>
<p>而配置绑定表关系后再进行关联查询时,只要对应表分片规则一致产生的数据就会落到同一个库中,那么只需 t_order_0 和 t_order_item_0 表关联即可。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_0 o <span class="keyword">JOIN</span> t_order_item_0 i <span class="keyword">ON</span> o.order_id<span class="operator">=</span>i.order_id </span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_1 o <span class="keyword">JOIN</span> t_order_item_1 i <span class="keyword">ON</span> o.order_id<span class="operator">=</span>i.order_id</span><br></pre></td></tr></table></figure>
<p><img src="https://s2.loli.net/2023/01/31/R5L1wl8PJHtBC4h.png" alt="420c8da9949389203424118e98818afec717d9.png"></p>
<p> 绑定表关系</p>
<p>注意:在关联查询时 t_order 它作为整个联合查询的主表。所有相关的路由计算都只使用主表的策略,t_order_item 表的分片相关的计算也会使用 t_order 的条件,所以要保证绑定表之间的分片键要完全相同。</p>
<h2 id="三、和JDBC的猫腻"><a href="#三、和JDBC的猫腻" class="headerlink" title="三、和JDBC的猫腻"></a><strong>三、和JDBC的猫腻</strong></h2><p>从名字上不难看出,Sharding-JDBC 和 JDBC有很大关系,我们知道 JDBC 是一种 Java 语言访问关系型数据库的规范,其设计初衷就是要提供一套用于各种数据库的统一标准,不同厂家共同遵守这套标准,并提供各自的实现方案供应用程序调用。</p>
<p><img src="https://s2.loli.net/2023/01/31/QzH2FoJ7DvsUbT5.png" alt="77c415b51b33e9ed0b8844aaaa64fcba718a7d.png"></p>
<p>但其实对于开发人员而言,我们只关心如何调用 JDBC API 来访问数据库,只要正确使用 DataSource、Connection、Statement 、ResultSet 等 API 接口,直接操作数据库即可。所以如果想在 JDBC 层面实现数据分片就必须对现有的 API 进行功能拓展,而 Sharding-JDBC 正是基于这种思想,重写了 JDBC 规范并完全兼容了 JDBC 规范。</p>
<p><img src="https://s2.loli.net/2023/01/31/xD3Z8PrmApVMYg2.png" alt="a5eceb153a77640d6f4504e893dc74ae00507a.png"></p>
<h3 id="JDBC流程"><a href="#JDBC流程" class="headerlink" title="JDBC流程"></a>JDBC流程</h3><p>对原有的 DataSource、Connection 等接口扩展成 ShardingDataSource、ShardingConnection,而对外暴露的分片操作接口与 JDBC 规范中所提供的接口完全一致,只要你熟悉 JDBC 就可以轻松应用 Sharding-JDBC 来实现分库分表。</p>
<p><img src="https://s2.loli.net/2023/01/31/ojFqS98TcU2e3Rh.png" alt="c86fa7c9708dfeaf0dc590c13b018b96ec07b9.png"></p>
<p>因此它适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate,Mybatis,Spring JDBC Template 或直接使用的 JDBC。完美兼容任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP,Druid, HikariCP 等,几乎对主流关系型数据库都支持。</p>
<p><strong>那</strong> Sharding-JDBC <strong>又是如何拓展这些接口的呢?</strong>想知道答案我们就的从源码入手了,下边我们以 JDBC API 中的 DataSource 为例看看它是如何被重写扩展的。</p>
<p>数据源 DataSource 接口的核心作用就是获取数据库连接对象 Connection,我们看其内部提供了两个获取数据库连接的方法 ,并且继承了 CommonDataSource 和 Wrapper 两个接口。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">DataSource</span> <span class="keyword">extends</span> <span class="title">CommonDataSource</span>, <span class="title">Wrapper</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <p>Attempts to establish a connection with the data source that</span></span><br><span class="line"><span class="comment"> * this {<span class="doctag">@code</span> DataSource} object represents.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> a connection to the data source</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function">Connection <span class="title">getConnection</span><span class="params">()</span> <span class="keyword">throws</span> SQLException</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <p>Attempts to establish a connection with the data source that</span></span><br><span class="line"><span class="comment"> * this {<span class="doctag">@code</span> DataSource} object represents.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> username the database user on whose behalf the connection is</span></span><br><span class="line"><span class="comment"> * being made</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> password the user's password</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function">Connection <span class="title">getConnection</span><span class="params">(String username, String password)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> SQLException</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中 CommonDataSource 是定义数据源的根接口这很好理解,而 Wrapper 接口则是拓展 JDBC 分片功能的关键。</p>
<p>由于数据库厂商的不同,他们可能会各自提供一些超越标准 JDBC API 的扩展功能,但这些功能非 JDBC 标准并不能直接使用,而 Wrapper 接口的作用就是把一个由第三方供应商提供的、非 JDBC 标准的接口包装成标准接口,也就是适配器模式。</p>
<p>既然讲到了适配器模式就多啰嗦几句,也方便后边的理解。</p>
<p>适配器模式个种比较常用的设计模式,它的作用是将某个类的接口转换成客户端期望的另一个接口,使原本因接口不匹配(或者不兼容)而无法在一起工作的两个类能够在一起工作。比如用耳机听音乐,我有个圆头的耳机,可手机插孔却是扁口的,如果我想要使用耳机听音乐就必须借助一个转接头才可以,这个转接头就起到了适配作用。举个栗子:假如我们 Target 接口中有 hello() 和 word() 两个方法。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public interface Target {</span><br><span class="line"></span><br><span class="line"> void hello();</span><br><span class="line"></span><br><span class="line"> void world();</span><br><span class="line">}1.2.3.4.5.6.</span><br></pre></td></tr></table></figure>
<p>可由于接口版本迭代Target 接口的 word() 方法可能会被废弃掉或不被支持,Adaptee 类的 greet()方法将代替hello() 方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Adaptee</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">graeet</span><span class="params">()</span></span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">world</span><span class="params">()</span></span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>但此时旧版本仍然有大量 word() 方法被使用中,解决此事最好的办法就是创建一个适配器Adapter,这样就适配了 Target 类,解决了接口升级带来的兼容性问题。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Adapter</span> <span class="keyword">extends</span> <span class="title">Adaptee</span> <span class="keyword">implements</span> <span class="title">Target</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">world</span><span class="params">()</span> </span>{</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">hello</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.greet();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">greet</span><span class="params">()</span> </span>{</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>而 Sharding-JDBC 提供的正是非 JDBC 标准的接口,所以它也提供了类似的实现方案,也使用到了 Wrapper 接口做数据分片功能的适配。除了 DataSource 之外,Connection、Statement、ResultSet 等核心对象也都继承了这个接口。</p>
<p>下面我们通过 ShardingDataSource 类源码简单看下实现过程,下图是继承关系流程图。</p>
<p><img src="https://s2.loli.net/2023/01/31/VzXIaTsuYUmpOBP.png" alt="49b41c48798299718fa1014a36868eea76537d.png"></p>
<h3 id="ShardingDataSource实现流程"><a href="#ShardingDataSource实现流程" class="headerlink" title="ShardingDataSource实现流程"></a>ShardingDataSource实现流程</h3><p>ShardingDataSource 类它在原 DataSource 基础上做了功能拓展,初始化时注册了分片SQL路由包装器、SQL重写上下文和结果集处理引擎,还对数据源类型做了校验,因为它要同时支持多个不同类型的数据源。到这好像也没看出如何适配,那接着向上看 ShardingDataSource 的继承类 AbstractDataSourceAdapter 。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Getter</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ShardingDataSource</span> <span class="keyword">extends</span> <span class="title">AbstractDataSourceAdapter</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ShardingRuntimeContext runtimeContext;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 注册路由、SQl重写上下文、结果集处理引擎</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> NewInstanceServiceLoader.register(RouteDecorator.class);</span><br><span class="line"> NewInstanceServiceLoader.register(SQLRewriteContextDecorator.class);</span><br><span class="line"> NewInstanceServiceLoader.register(ResultProcessEngine.class);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 初始化时校验数据源类型 并根据数据源 map、分片规则、数据库类型得到一个分片上下文,用来获取数据库连接</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">ShardingDataSource</span><span class="params">(<span class="keyword">final</span> Map<String, DataSource> dataSourceMap, <span class="keyword">final</span> ShardingRule shardingRule, <span class="keyword">final</span> Properties props)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">super</span>(dataSourceMap);</span><br><span class="line"> checkDataSourceType(dataSourceMap);</span><br><span class="line"> runtimeContext = <span class="keyword">new</span> ShardingRuntimeContext(dataSourceMap, shardingRule, props, getDatabaseType());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">checkDataSourceType</span><span class="params">(<span class="keyword">final</span> Map<String, DataSource> dataSourceMap)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (DataSource each : dataSourceMap.values()) {</span><br><span class="line"> Preconditions.checkArgument(!(each <span class="keyword">instanceof</span> MasterSlaveDataSource), <span class="string">"Initialized data sources can not be master-slave data sources."</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 数据库连接</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> ShardingConnection <span class="title">getConnection</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ShardingConnection(getDataSourceMap(), runtimeContext, TransactionTypeHolder.get());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>AbstractDataSourceAdapter 抽象类内部主要获取不同类型的数据源对应的数据库连接对象,实现 AutoCloseable 接口是为在使用完资源后可以自动将这些资源关闭(调用 close方法),那再看看继承类 AbstractUnsupportedOperationDataSource 。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Getter</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractDataSourceAdapter</span> <span class="keyword">extends</span> <span class="title">AbstractUnsupportedOperationDataSource</span> <span class="keyword">implements</span> <span class="title">AutoCloseable</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Map<String, DataSource> dataSourceMap;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> DatabaseType databaseType;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AbstractDataSourceAdapter</span><span class="params">(<span class="keyword">final</span> Map<String, DataSource> dataSourceMap)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">this</span>.dataSourceMap = dataSourceMap;</span><br><span class="line"> databaseType = createDatabaseType();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AbstractDataSourceAdapter</span><span class="params">(<span class="keyword">final</span> DataSource dataSource)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> dataSourceMap = <span class="keyword">new</span> HashMap<>(<span class="number">1</span>, <span class="number">1</span>);</span><br><span class="line"> dataSourceMap.put(<span class="string">"unique"</span>, dataSource);</span><br><span class="line"> databaseType = createDatabaseType();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> DatabaseType <span class="title">createDatabaseType</span><span class="params">()</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> DatabaseType result = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">for</span> (DataSource each : dataSourceMap.values()) {</span><br><span class="line"> DatabaseType databaseType = createDatabaseType(each);</span><br><span class="line"> Preconditions.checkState(<span class="keyword">null</span> == result || result == databaseType, String.format(<span class="string">"Database type inconsistent with '%s' and '%s'"</span>, result, databaseType));</span><br><span class="line"> result = databaseType;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 不同数据源类型获取数据库连接</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> DatabaseType <span class="title">createDatabaseType</span><span class="params">(<span class="keyword">final</span> DataSource dataSource)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">if</span> (dataSource <span class="keyword">instanceof</span> AbstractDataSourceAdapter) {</span><br><span class="line"> <span class="keyword">return</span> ((AbstractDataSourceAdapter) dataSource).databaseType;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> (Connection connection = dataSource.getConnection()) {</span><br><span class="line"> <span class="keyword">return</span> DatabaseTypes.getDatabaseTypeByURL(connection.getMetaData().getURL());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> Connection <span class="title">getConnection</span><span class="params">(<span class="keyword">final</span> String username, <span class="keyword">final</span> String password)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">return</span> getConnection();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">close</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> close(dataSourceMap.keySet());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>AbstractUnsupportedOperationDataSource 实现DataSource 接口并继承了 WrapperAdapter 类,它内部并没有什么具体方法只起到桥接的作用,但看着是不是和我们前边讲适配器模式的例子方式有点相似。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractUnsupportedOperationDataSource</span> <span class="keyword">extends</span> <span class="title">WrapperAdapter</span> <span class="keyword">implements</span> <span class="title">DataSource</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">getLoginTimeout</span><span class="params">()</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SQLFeatureNotSupportedException(<span class="string">"unsupported getLoginTimeout()"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setLoginTimeout</span><span class="params">(<span class="keyword">final</span> <span class="keyword">int</span> seconds)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SQLFeatureNotSupportedException(<span class="string">"unsupported setLoginTimeout(int seconds)"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>WrapperAdapter 是一个包装器的适配类,实现了 JDBC 中的 Wrapper 接口,其中有两个核心方法 recordMethodInvocation 用于添加需要执行的方法和参数,而 replayMethodsInvocation 则将添加的这些方法和参数通过反射执行。仔细看不难发现两个方法中都用到了 JdbcMethodInvocation类。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">WrapperAdapter</span> <span class="keyword">implements</span> <span class="title">Wrapper</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Collection<JdbcMethodInvocation> jdbcMethodInvocations = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 添加要执行的方法</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@SneakyThrows</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">recordMethodInvocation</span><span class="params">(<span class="keyword">final</span> Class<?> targetClass, <span class="keyword">final</span> String methodName, <span class="keyword">final</span> Class<?>[] argumentTypes, <span class="keyword">final</span> Object[] arguments)</span> </span>{</span><br><span class="line"> jdbcMethodInvocations.add(<span class="keyword">new</span> JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments));</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 通过反射执行 上边添加的方法</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">replayMethodsInvocation</span><span class="params">(<span class="keyword">final</span> Object target)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (JdbcMethodInvocation each : jdbcMethodInvocations) {</span><br><span class="line"> each.invoke(target);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>JdbcMethodInvocation 类主要应用反射通过传入的 method 方法和 arguments 参数执行对应的方法,这样就可以通过 JDBC API 调用非 JDBC 方法了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcMethodInvocation</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Getter</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Method method;</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Getter</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Object[] arguments;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Invoke JDBC method.</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> target target object</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@SneakyThrows</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">invoke</span><span class="params">(<span class="keyword">final</span> Object target)</span> </span>{</span><br><span class="line"> method.invoke(target, arguments);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>那 Sharding-JDBC 拓展 JDBC API 接口后,在新增的分片功能里又做了哪些事情呢?</p>
<p>一张表经过分库分表后被拆分成多个子表,并分散到不同的数据库中,在不修改原业务 SQL 的前提下,Sharding-JDBC 就必须对 SQL进行一些改造才能正常执行。</p>
<p>大致的执行流程:SQL 解析 -> 执⾏器优化 -> SQL 路由 -> SQL 改写 -> SQL 执⾏ -> 结果归并 六步组成,一起瞅瞅每个步骤做了点什么。<br><img src="https://s2.loli.net/2023/01/31/Zesz5XyntvPljWO.png" alt="83520a2514c3691be5b36638329a602e3feef1.png"></p>
<h4 id="SQL-解析"><a href="#SQL-解析" class="headerlink" title="SQL 解析"></a><strong>SQL 解析</strong></h4><p>SQL解析过程分为词法解析和语法解析两步,比如下边这条查询用户订单的SQL,先用词法解析将SQL拆解成不可再分的原子单元。在根据不同数据库方言所提供的字典,将这些单元归类为关键字,表达式,变量或者操作符等类型。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> order_no,price <span class="keyword">FROM</span> t_order_ <span class="keyword">where</span> user_id <span class="operator">=</span> <span class="number">10086</span> <span class="keyword">and</span> order_status <span class="operator">></span> <span class="number">0</span></span><br></pre></td></tr></table></figure>
<p>接着语法解析会将拆分后的SQL转换为抽象语法树,通过对抽象语法树遍历,提炼出分片所需的上下文,上下文包含查询字段信息(Field)、表信息(Table)、查询条件(Condition)、排序信息(Order By)、分组信息(Group By)以及分页信息(Limit)等,并标记出 SQL中有可能需要改写的位置。</p>
<p><img src="https://s2.loli.net/2023/01/31/5T9aFAmKj1ZNsSo.png" alt="e4bc5c402444ed05610427bea57676e831fc5e.png"></p>
<p>抽象语法树</p>
<h4 id="执⾏器优化"><a href="#执⾏器优化" class="headerlink" title="执⾏器优化"></a><strong>执⾏器优化</strong></h4><p>执⾏器优化对SQL分片条件进行优化,处理像关键字 OR这种影响性能的坏味道。</p>
<h4 id="SQL-路由"><a href="#SQL-路由" class="headerlink" title="SQL 路由"></a><strong>SQL 路由</strong></h4><p>SQL 路由通过解析分片上下文,匹配到用户配置的分片策略,并生成路由路径。简单点理解就是可以根据我们配置的分片策略计算出 SQL该在哪个库的哪个表中执行,而SQL路由又根据有无分片健区分出 分片路由 和 广播路由。</p>
<p><img src="https://s2.loli.net/2023/01/31/BMxRqZ4CcOmSsak.png" alt="984dca3599293c51191234fb2fae220084e8a8.png"></p>
<h4 id="官方路由图谱"><a href="#官方路由图谱" class="headerlink" title="官方路由图谱"></a>官方路由图谱</h4><p>有分⽚键的路由叫分片路由,细分为直接路由、标准路由和笛卡尔积路由这3种类型。</p>
<h5 id="标准路由"><a href="#标准路由" class="headerlink" title="标准路由"></a><strong>标准路由</strong></h5><p>标准路由是最推荐也是最为常⽤的分⽚⽅式,它的适⽤范围是不包含关联查询或仅包含绑定表之间关联查询的SQL。</p>
<p>当 SQL分片健的运算符为 = 时,路由结果将落⼊单库(表),当分⽚运算符是BETWEEN 或IN 等范围时,路由结果则不⼀定落⼊唯⼀的库(表),因此⼀条逻辑SQL最终可能被拆分为多条⽤于执⾏的真实SQL。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order <span class="keyword">where</span> t_order_id <span class="keyword">in</span> (<span class="number">1</span>,<span class="number">2</span>)</span><br></pre></td></tr></table></figure>
<p>SQL路由处理后</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_0 <span class="keyword">where</span> t_order_id <span class="keyword">in</span> (<span class="number">1</span>,<span class="number">2</span>)</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_1 <span class="keyword">where</span> t_order_id <span class="keyword">in</span> (<span class="number">1</span>,<span class="number">2</span>)</span><br></pre></td></tr></table></figure>
<h5 id="直接路由"><a href="#直接路由" class="headerlink" title="直接路由"></a><strong>直接路由</strong></h5><p>直接路由是通过使用 HintAPI 直接将 SQL路由到指定⾄库表的一种分⽚方式,而且直接路由可以⽤于分⽚键不在SQL中的场景,还可以执⾏包括⼦查询、⾃定义函数等复杂情况的任意SQL。</p>
<p>比如根据 t_order_id 字段为条件查询订单,此时希望在不修改SQL的前提下,加上 user_id作为分片条件就可以使用直接路由。</p>
<h5 id="笛卡尔积路由"><a href="#笛卡尔积路由" class="headerlink" title="笛卡尔积路由"></a><strong>笛卡尔积路由</strong></h5><p>笛卡尔路由是由⾮绑定表之间的关联查询产生的,查询性能较低尽量避免走此路由模式。</p>
<p>无分⽚键的路由又叫做广播路由,可以划分为全库表路由、全库路由、 全实例路由、单播路由和阻断路由这 5种类型。</p>
<h5 id="全库表路由"><a href="#全库表路由" class="headerlink" title="全库表路由"></a><strong>全库表路由</strong></h5><p>全库表路由针对的是数据库 DQL和 DML,以及 DDL等操作,当我们执行一条逻辑表 t_order SQL时,在所有分片库中对应的真实表 t_order_0 ··· t_order_n 内逐一执行。</p>
<h5 id="全库路由"><a href="#全库路由" class="headerlink" title="全库路由"></a><strong>全库路由</strong></h5><p>全库路由主要是对数据库层面的操作,比如数据库 SET 类型的数据库管理命令,以及 TCL 这样的事务控制语句。</p>
<p>对逻辑库设置 autocommit 属性后,所有对应的真实库中都执行该命令。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SET</span> autocommit<span class="operator">=</span><span class="number">0</span>;</span><br></pre></td></tr></table></figure>
<h5 id="全实例路由"><a href="#全实例路由" class="headerlink" title="全实例路由"></a><strong>全实例路由</strong></h5><p>全实例路由是针对数据库实例的 DCL 操作(设置或更改数据库用户或角色权限),比如:创建一个用户 order ,这个命令将在所有的真实库实例中执行,以此确保 order 用户可以正常访问每一个数据库实例。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="keyword">order</span><span class="variable">@127</span><span class="number">.0</span><span class="number">.0</span><span class="number">.1</span> identified <span class="keyword">BY</span> <span class="string">'程序员内点事'</span>;</span><br></pre></td></tr></table></figure>
<h5 id="单播路由"><a href="#单播路由" class="headerlink" title="单播路由"></a><strong>单播路由</strong></h5><p>单播路由用来获取某一真实表信息,比如获得表的描述信息:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DESCRIBE</span> t_order; </span><br></pre></td></tr></table></figure>
<p>t_order 的真实表是 t_order_0 ···· t_order_n,他们的描述结构相完全同,我们只需在任意的真实表执行一次就可以。</p>
<h5 id="阻断路由"><a href="#阻断路由" class="headerlink" title="阻断路由"></a><strong>阻断路由</strong></h5><p>⽤来屏蔽SQL对数据库的操作,例如:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">USE order_db;</span><br></pre></td></tr></table></figure>
<p>这个命令不会在真实数据库中执⾏,因为ShardingSphere 采⽤的是逻辑 Schema(数据库的组织和结构) ⽅式,所以无需将切换数据库的命令发送⾄真实数据库中。</p>
<h4 id="SQL-改写"><a href="#SQL-改写" class="headerlink" title="SQL 改写"></a><strong>SQL 改写</strong></h4><p>将基于逻辑表开发的SQL改写成可以在真实数据库中可以正确执行的语句。比如查询 t_order 订单表,我们实际开发中 SQL是按逻辑表 t_order 写的。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order</span><br></pre></td></tr></table></figure>
<p>但分库分表以后真实数据库中 t_order 表就不存在了,而是被拆分成多个子表 t_order_n 分散在不同的数据库内,还按原SQL执行显然是行不通的,这时需要将分表配置中的逻辑表名称改写为路由之后所获取的真实表名称。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> t_order_n</span><br></pre></td></tr></table></figure>
<h4 id="SQL执⾏"><a href="#SQL执⾏" class="headerlink" title="SQL执⾏"></a><strong>SQL执⾏</strong></h4><p>将路由和改写后的真实 SQL 安全且高效发送到底层数据源执行。但这个过程并不是简单的将 SQL 通过JDBC 直接发送至数据源执行,而是平衡数据源连接创建以及内存占用所产生的消耗,它会自动化的平衡资源控制与执行效率。</p>
<h4 id="结果归并"><a href="#结果归并" class="headerlink" title="结果归并"></a><strong>结果归并</strong></h4><p>将从各个数据节点获取的多数据结果集,合并成一个大的结果集并正确的返回至请求客户端,称为结果归并。而我们SQL中的排序、分组、分页和聚合等语法,均是在归并后的结果集上进行操作的。</p>
<h2 id="四、快速实践"><a href="#四、快速实践" class="headerlink" title="四、快速实践"></a><strong>四、快速实践</strong></h2><p>下面我们结合 Springboot + mybatisplus 快速搭建一个分库分表案例。</p>
<h3 id="1、准备工作"><a href="#1、准备工作" class="headerlink" title="1、准备工作"></a><strong>1、准备工作</strong></h3><p>先做准备工作,创建两个数据库 ds-0、ds-1,两个库中分别建表 t_order_0、t_order_1、t_order_2 、t_order_item_0、t_order_item_1、t_order_item_2,t_config,方便后边验证广播表、绑定表的场景。</p>
<p><img src="https://dl-harmonyos.51cto.com/images/202206/f10969202396d72593b5129e8b60e5a8a93413.png"></p>
<p> 表结构如下:</p>
<p>t_order_0 订单表</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `t_order_0` (</span><br><span class="line"> `order_id` <span class="type">bigint</span>(<span class="number">200</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> `order_no` <span class="type">varchar</span>(<span class="number">100</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> `create_name` <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> `price` <span class="type">decimal</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="keyword">PRIMARY</span> KEY (`order_id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8 ROW_FORMAT<span class="operator">=</span><span class="keyword">DYNAMIC</span>;</span><br></pre></td></tr></table></figure>
<p>t_order_0 与 t_order_item_0 互为关联表</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `t_order_item_0` (</span><br><span class="line"> `item_id` <span class="type">bigint</span>(<span class="number">100</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> `order_no` <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> `item_name` <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> `price` <span class="type">decimal</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> <span class="keyword">PRIMARY</span> KEY (`item_id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8 ROW_FORMAT<span class="operator">=</span><span class="keyword">DYNAMIC</span>;</span><br></pre></td></tr></table></figure>
<p>广播表 t_config</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> `id` <span class="type">bigint</span>(<span class="number">30</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> `remark` <span class="type">varchar</span>(<span class="number">50</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8 <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line"> `create_time` <span class="type">timestamp</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span>,</span><br><span class="line"> `last_modify_time` <span class="type">timestamp</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> UPDATE <span class="built_in">CURRENT_TIMESTAMP</span>,</span><br><span class="line"> <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>latin1;</span><br></pre></td></tr></table></figure>
<p>ShardingSphere 提供了4种分片配置方式:</p>
<ul>
<li><p>Java 代码配置</p>
</li>
<li><p>Yaml 、properties 配置</p>
</li>
<li><p>Spring 命名空间配置</p>
</li>
<li><p>Spring Boot配置</p>
</li>
</ul>
<p>为让代码看上去更简洁和直观,后边统一使用 properties 配置的方式,引入 shardingsphere 对应的 sharding-jdbc-spring-boot-starter 和 sharding-core-common 包,版本统一用的 4.0.0-RC1。</p>
<h3 id="2、分片配置"><a href="#2、分片配置" class="headerlink" title="2、分片配置"></a><strong>2、分片配置</strong></h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.shardingsphere<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>sharding-jdbc-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>4.0.0-RC1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.shardingsphere<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>sharding-core-common<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>4.0.0-RC1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<p>准备工作做完( mybatis 搭建就不赘述了),接下来我们逐一解读分片配置信息。</p>
<p>我们首先定义两个数据源 ds-0、ds-1,并分别加上数据源的基础信息。</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义两个全局数据源</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.names</span>=<span class="string">ds-0,ds-1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置数据源 ds-0</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-0.type</span>=<span class="string">com.alibaba.druid.pool.DruidDataSource</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-0.driverClassName</span>=<span class="string">com.mysql.jdbc.Driver</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-0.url</span>=<span class="string">jdbc:mysql://127.0.0.1:3306/ds-0?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-0.username</span>=<span class="string">root</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-0.password</span>=<span class="string">root</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置数据源 ds-1</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-1.type</span>=<span class="string">com.alibaba.druid.pool.DruidDataSource</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-1.driverClassName</span>=<span class="string">com.mysql.jdbc.Driver</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-1.url</span>=<span class="string">jdbc:mysql://127.0.0.1:3306/ds-1?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-1.username</span>=<span class="string">root</span></span><br><span class="line"><span class="meta">spring.shardingsphere.datasource.ds-1.password</span>=<span class="string">root</span></span><br></pre></td></tr></table></figure>
<p>配置完数据源接下来为表添加分库和分表策略,使用 sharding-jdbc 做分库分表需要我们为每一个表单独设置分片规则。</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置分片表 t_order</span></span><br><span class="line"><span class="comment"># 指定真实数据节点</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.tables.t_order.actual-data-nodes</span>=<span class="string">ds-$->{0..1}.t_order_$->{0..2}</span></span><br></pre></td></tr></table></figure>
<p>actual-data-nodes 属性指定分片的真实数据节点,$是一个占位符,{0..1}表示实际拆分的数据库表数量。</p>
<p>ds-$->{0..1}.t_order_$->{0..2} 表达式相当于 6个数据节点</p>
<ul>
<li> ds-0.t_order_0</li>
<li> ds-0.t_order_1</li>
<li> ds-0.t_order_2</li>
<li> ds-1.t_order_0</li>
<li> ds-1.t_order_1</li>
<li> ds-1.t_order_2</li>
</ul>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### 分库策略</span></span><br><span class="line"><span class="comment"># 分库分片健</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column</span>=<span class="string">order_id</span></span><br><span class="line"><span class="comment"># 分库分片算法</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression</span>=<span class="string">ds-$->{order_id % 2}</span></span><br></pre></td></tr></table></figure>
<p>为表设置分库策略,上边讲了 sharding-jdbc 它提供了四种分片策略,为快速搭建我们先以最简单的行内表达式分片策略来实现,在下一篇会介绍四种分片策略的详细用法和使用场景。</p>
<p>database-strategy.inline.sharding-column 属性中 database-strategy 为分库策略,inline 为具体的分片策略,sharding-column 代表分片健。</p>
<p>database-strategy.inline.algorithm-expression 是当前策略下具体的分片算法,ds-$->{order_id % 2} 表达式意思是 对 order_id字段进行取模分库,2 代表分片库的个数,不同的策略对应不同的算法,这里也可以是我们自定义的分片算法类。</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 分表策略</span></span><br><span class="line"><span class="comment"># 分表分片健</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column</span>=<span class="string">order_id</span></span><br><span class="line"><span class="comment"># 分表算法</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression</span>=<span class="string">t_order_$->{order_id % 3}</span></span><br><span class="line"><span class="comment"># 自增主键字段</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.tables.t_order.key-generator.column</span>=<span class="string">order_id</span></span><br><span class="line"><span class="comment"># 自增主键ID 生成方案</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.tables.t_order.key-generator.type</span>=<span class="string">SNOWFLAKE</span></span><br></pre></td></tr></table></figure>
<p>分表策略 和 分库策略 的配置比较相似,不同的是分表可以通过 key-generator.column 和 key-generator.type 设置自增主键以及指定自增主键的生成方案,目前内置了SNOWFLAKE 和 UUID 两种方式,还能自定义的主键生成算法类,后续会详细的讲解。</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 绑定表关系</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.binding-tables</span>= <span class="string">t_order,t_order_item</span></span><br></pre></td></tr></table></figure>
<p>必须按相同分片健进行分片的表才能互为成绑定表,在联合查询时就能避免出现笛卡尔积查询。</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置广播表</span></span><br><span class="line"><span class="meta">spring.shardingsphere.sharding.broadcast-tables</span>=<span class="string">t_config</span></span><br></pre></td></tr></table></figure>
<p>广播表,开启 SQL解析日志,能清晰的看到 SQL分片解析的过程</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 是否开启 SQL解析日志</span></span><br><span class="line"><span class="meta">spring.shardingsphere.props.sql.show</span>=<span class="string">true</span></span><br></pre></td></tr></table></figure>
<h3 id="3、验证分片"><a href="#3、验证分片" class="headerlink" title="3、验证分片"></a><strong>3、验证分片</strong></h3><p>分片配置完以后我们无需在修改业务代码了,直接执行业务逻辑的增、删、改、查即可,接下来验证一下分片的效果。</p>
<p>我们同时向 t_order、t_order_item 表插入 5条订单记录,并不给定主键 order_id ,item_id 字段值。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">insertOrder</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">4</span>; i++) {</span><br><span class="line"> TOrder order = <span class="keyword">new</span> TOrder();</span><br><span class="line"> order.setOrderNo(<span class="string">"A000"</span> + i);</span><br><span class="line"> order.setCreateName(<span class="string">"订单 "</span> + i);</span><br><span class="line"> order.setPrice(<span class="keyword">new</span> BigDecimal(<span class="string">""</span> + i));</span><br><span class="line"> orderRepository.insert(order);</span><br><span class="line"></span><br><span class="line"> TOrderItem orderItem = <span class="keyword">new</span> TOrderItem();</span><br><span class="line"> orderItem.setOrderId(order.getOrderId());</span><br><span class="line"> orderItem.setOrderNo(<span class="string">"A000"</span> + i);</span><br><span class="line"> orderItem.setItemName(<span class="string">"服务项目"</span> + i);</span><br><span class="line"> orderItem.setPrice(<span class="keyword">new</span> BigDecimal(<span class="string">""</span> + i));</span><br><span class="line"> orderItemRepository.insert(orderItem);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"success"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>看到订单记录被成功分散到了不同的库表中, order_id 字段也自动生成了主键ID,基础的分片功能就完成了。</p>
<p><img src="https://dl-harmonyos.51cto.com/images/202206/c9659ed09146d1f30102003134083ba4822e1b.png"></p>
<h4 id="基础分片"><a href="#基础分片" class="headerlink" title="基础分片"></a>基础分片</h4><p>那向广播表 t_config 中插入一条数据会是什么效果呢?</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">config</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> TConfig tConfig = <span class="keyword">new</span> TConfig();</span><br><span class="line"> tConfig.setRemark(<span class="string">"我是广播表"</span>);</span><br><span class="line"> tConfig.setCreateTime(<span class="keyword">new</span> Date());</span><br><span class="line"> tConfig.setLastModifyTime(<span class="keyword">new</span> Date());</span><br><span class="line"> configRepository.insert(tConfig);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"success"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>发现所有库中 t_config 表都执行了这条SQL,广播表和 MQ广播订阅的模式很相似,所有订阅的客户端都会收到同一条消息。<img src="https://dl-harmonyos.51cto.com/images/202206/e69650f675d20a2a1984705a20b45040916f54.png"></p>
<h4 id="广播表-1"><a href="#广播表-1" class="headerlink" title="广播表"></a>广播表</h4><p>简单SQL操作验证没问通,接下来在试试复杂一点的联合查询,前边我们已经把 t_order 、t_order_item 表设为绑定表,直接联表查询执行一下。</p>
<p><img src="https://dl-harmonyos.51cto.com/images/202206/4947964415e5193aa46532953115a50172f341.png"></p>
<h4 id="关联查询"><a href="#关联查询" class="headerlink" title="关联查询"></a>关联查询</h4><p>通过控制台日志发现,逻辑表SQL 经过解析以后,只对 t_order_0 和 t_order_item_0 表进行了关联产生一条SQL。</p>
<p><img src="https://dl-harmonyos.51cto.com/images/202206/841cfb701eead438c0675810179c7f578966b8.png"></p>
<h4 id="绑定表SQL"><a href="#绑定表SQL" class="headerlink" title="绑定表SQL"></a>绑定表SQL</h4><p>那如果不互为绑定表又会是什么情况呢?去掉 spring.shardingsphere.sharding.binding-tables试一下。</p>
<p>发现控制台解析出了 3条真实表SQL,而去掉 order_id 作为查询条件再次执行后,结果解析出了 9条SQL,进行了笛卡尔积查询。所以相比之下绑定表的优点就不言而喻了。</p>
<p><img src="https://dl-harmonyos.51cto.com/images/202206/a1fba5b86c90156075953157ecb9cbee54a56d.png"></p>
<p> 笛卡尔积查询</p>
<h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a><strong>五、总结</strong></h2><p>以上对分库分表中间件 sharding-jdbc 的基础概念做了简单梳理,快速的搭建了一个分库分表案例,但这只是实践分库分表的第一步,下一篇我们会详细的介绍四种分片策略的具体用法和使用场景(必知必会),后边将陆续讲解自定义分布式主键、分布式数据库事务、分布式服务治理,数据脱敏等。</p>
</div></article></div><div class="card"><div class="card-image"><a class="image is-7by3" href="/2022/11/30/2022/2022-11-30-Mongo%20Decimal128%20%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E8%A7%A3%E5%86%B3/"><img class="fill" src="https://i.loli.net/2021/03/25/1iIC9XgetrluNRQ.jpg" alt="Mongo Decimal128 类型转换问题排查解决"></a></div><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2022-11-29T16:00:00.000Z" title="2022/11/30 00:00:00">2022-11-30</time>发表</span><span class="level-item"><time dateTime="2022-11-15T06:11:04.368Z" title="2022/11/15 14:11:04">2022-11-15</time>更新</span><span class="level-item"><a class="link-muted" href="/categories/%E5%B7%A5%E4%BD%9C/">工作</a><span> / </span><a class="link-muted" href="/categories/%E5%B7%A5%E4%BD%9C/%E6%8C%91%E6%88%98/">挑战</a></span><span class="level-item">7 分钟读完 (大约1098个字)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2022/11/30/2022/2022-11-30-Mongo%20Decimal128%20%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E8%A7%A3%E5%86%B3/">Mongo Decimal128 类型转换问题排查解决</a></h1><div class="content"><h2 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h2><p>java中对于精确小数,我们通常使用Bigdecimal进行存储,而mongo中是不存在Bigdecimal类型,对应的是Decimal128。<br>项目中使用mongo的地方,为了能够在插入mongo时将Bigdecimal转为Decimal128,查询时将Decimal128转回Bigdecimal,可以利用spring中的org.springframework.core.convert.converter.Converter。如下</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@WritingConverter</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BigDecimalToDecimal128Converter</span> <span class="keyword">implements</span> <span class="title">Converter</span><<span class="title">BigDecimal</span>, <span class="title">Decimal128</span>> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Decimal128 <span class="title">convert</span><span class="params">(BigDecimal bigDecimal)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Decimal128(bigDecimal);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ReadingConverter</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Decimal128ToBigDecimalConverter</span> <span class="keyword">implements</span> <span class="title">Converter</span><<span class="title">Decimal128</span>, <span class="title">BigDecimal</span>> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> BigDecimal <span class="title">convert</span><span class="params">(Decimal128 decimal128)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> decimal128.bigDecimalValue();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在向容器中注入MongoTemplate时添加自定义的Converter后<br>我们就可以自由的对BigDecimal类型进行mongo存储和读取了。</p>
<h2 id="真的没有问题了吗"><a href="#真的没有问题了吗" class="headerlink" title="真的没有问题了吗"></a>真的没有问题了吗</h2><p>当使用Map类型去接收Mongo查询结果时,上面的自定义Converter就会失效:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> mongoTemplate.find(query, JSONObject.class, TABLE_NAME + year);</span><br></pre></td></tr></table></figure>
<p>![企业微信截图_16535653001637.png](<a href="https://object-storage.mihoyo.com:9000/plat-knowledge-management/prod/92d2afc9c6cac8dd3ca5391f046577f0_1653616202505.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=plat-knowledge-management-admin/20221130//s3/aws4_request&X-Amz-Date=20221130T124433Z&X-Amz-Expires=21600&X-Amz-SignedHeaders=host&X-Amz-Signature=9df161b1d3a1811f4b65f2e064e1a2084aae5d5b340c2c82da580e165c5f2226">https://object-storage.mihoyo.com:9000/plat-knowledge-management/prod/92d2afc9c6cac8dd3ca5391f046577f0_1653616202505.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=plat-knowledge-management-admin%2F20221130%2F%2Fs3%2Faws4_request&X-Amz-Date=20221130T124433Z&X-Amz-Expires=21600&X-Amz-SignedHeaders=host&X-Amz-Signature=9df161b1d3a1811f4b65f2e064e1a2084aae5d5b340c2c82da580e165c5f2226</a> =100%x)<br>经过代码调试,定位到MappingMongoConverter负责类型转换,其核心代码为:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Object <span class="title">getPotentiallyConvertedSimpleRead</span><span class="params">(<span class="meta">@Nullable</span> Object value, <span class="meta">@Nullable</span> Class<?> target)</span> </span>{</span><br><span class="line"> <span class="comment">// 使用Map<String,Object>接收结果,target=Object.class 而Decimal128 是 Object子类,因此不进行转换</span></span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span> || target == <span class="keyword">null</span> || ClassUtils.isAssignableValue(target, value)) {</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (conversions.hasCustomReadTarget(value.getClass(), target)) {</span><br><span class="line"> <span class="keyword">return</span> conversionService.convert(value, target);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (Enum.class.isAssignableFrom(target)) {</span><br><span class="line"> <span class="keyword">return</span> Enum.valueOf((Class<Enum>) target, value.toString());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> conversionService.convert(value, target);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>由此发现使用Map<String,Object>接收结果时,target=Object.class ,而Decimal128属于Object子类,因此自定义Converter不生效</p>
<h2 id="一波三折的解决方案-增加自定义Converter"><a href="#一波三折的解决方案-增加自定义Converter" class="headerlink" title="一波三折的解决方案-增加自定义Converter"></a>一波三折的解决方案-增加自定义Converter</h2><p>由于Decimal128在下游系统中不存在,因此下游接收到数字被转换为Map类型且很难再次转换,因此准备从源头解决问题,修改类型转换逻辑,尝试将Decimal128转换为BigDecimal输出至下游,因此增加新的自定义Converter</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ReadingConverter</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Decimal128ToObjectConverter</span> <span class="keyword">implements</span> <span class="title">Converter</span><<span class="title">Decimal128</span>, <span class="title">Object</span>> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Decimal128ToObjectConverter</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">convert</span><span class="params">(Decimal128 decimal128)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> decimal128.bigDecimalValue();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>结论是不行的,原因为子类判断优先自定义Converter转换:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Object <span class="title">getPotentiallyConvertedSimpleRead</span><span class="params">(<span class="meta">@Nullable</span> Object value, <span class="meta">@Nullable</span> Class<?> target)</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span> || target == <span class="keyword">null</span> || ClassUtils.isAssignableValue(target, value)) {</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 优先判断是否为子类,所以即使存在自定义Converter,也不会走该逻辑</span></span><br><span class="line"> <span class="keyword">if</span> (conversions.hasCustomReadTarget(value.getClass(), target)) {</span><br><span class="line"> <span class="keyword">return</span> conversionService.convert(value, target);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (Enum.class.isAssignableFrom(target)) {</span><br><span class="line"> <span class="keyword">return</span> Enum.valueOf((Class<Enum>) target, value.toString());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> conversionService.convert(value, target);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="第二折-自定义MappingMongoConverter"><a href="#第二折-自定义MappingMongoConverter" class="headerlink" title="第二折 自定义MappingMongoConverter"></a>第二折 自定义MappingMongoConverter</h2><p>我们自定义转换逻辑,重写核心转换逻辑,使得自定义Converter优先转换,是否还有问题?</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">private</span> Object <span class="title">getPotentiallyConvertedSimpleRead</span><span class="params">(<span class="meta">@Nullable</span> Object value, <span class="meta">@Nullable</span> Class<?> target)</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span> || target == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (conversions.hasCustomReadTarget(value.getClass(), target)) {</span><br><span class="line"> <span class="keyword">return</span> conversionService.convert(value, target);</span><br><span class="line"> }</span><br><span class="line"><span class="comment">// 将 子类判断移动至自定义Converter之后</span></span><br><span class="line"> <span class="keyword">if</span> (ClassUtils.isAssignableValue(target, value)) {</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (Enum.class.isAssignableFrom(target)) {</span><br><span class="line"> <span class="keyword">return</span> Enum.valueOf((Class<Enum>) target, value.toString());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> conversionService.convert(value, target);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>可以看到,当target=Object.class时,已经可以成功将Decimal128转换为BigDecimal,但是target=null时还是无法转换</p>
<h2 id="第三折-解决嵌套转换问题"><a href="#第三折-解决嵌套转换问题" class="headerlink" title="第三折 解决嵌套转换问题"></a>第三折 解决嵌套转换问题</h2><p>当对象存在嵌套时,MappingMongoConverter默认使用Map类型进行类型转换,此时target=null,造成了嵌套对象中的Decimal128对象并没有走到自定义Converter转换逻辑,因此再次修改代码:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Object <span class="title">getPotentiallyConvertedSimpleRead</span><span class="params">(<span class="meta">@Nullable</span> Object value, <span class="meta">@Nullable</span> Class<?> target)</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// target == null 时,尝试寻找Object.class类型转换器</span></span><br><span class="line"> <span class="keyword">if</span> (conversions.hasCustomReadTarget(value.getClass(), ObjectUtils.defaultIfNull(target, Object.class))) {</span><br><span class="line"> <span class="keyword">return</span> conversionService.convert(value, ObjectUtils.defaultIfNull(target, Object.class));</span><br><span class="line"> }</span><br><span class="line"><span class="comment">// 如果没有匹配到 converter 则返回原始值</span></span><br><span class="line"> <span class="keyword">if</span> (target == <span class="keyword">null</span> || ClassUtils.isAssignableValue(target, value)) {</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (Enum.class.isAssignableFrom(target)) {</span><br><span class="line"> <span class="keyword">return</span> Enum.valueOf((Class<Enum>) target, value.toString());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> conversionService.convert(value, target);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>至此使用Map接收Decimal128将被正确的转换至BigDecimal类型</p>
<h2 id="总结与收获"><a href="#总结与收获" class="headerlink" title="总结与收获"></a>总结与收获</h2><p>此方案需要改动两处代码</p>
<ol>
<li>增加Decimal128ToObjectConverter</li>
<li>自定义MappingMongoConverter</li>
</ol>
<p>排查后收获:</p>
<ol>
<li>除非上游改动代价太大,尽量在上游解决问题,不要抛给下游处理</li>
<li>mongo-spring对于Map等动态类型转换支持不完善,尽量定义准确类型数据结构接收结果,重写MappingMongoConverter不是上策</li>
<li>固定位数小数也可转换为整数存储,来绕过数字精度和类型转换问题</li>
</ol>
</div></article></div><div class="card"><div class="card-image"><a class="image is-7by3" href="/2022/11/15/2020/2020-02-23-IM%E7%B3%BB%E7%BB%9F%E5%85%B3%E9%94%AE%E7%82%B9%E6%A2%B3%E7%90%86/"><img class="fill" src="https://i.loli.net/2020/03/23/FwNYL9kIoCRDVT5.jpg" alt="IM系统关键点梳理"></a></div><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2022-11-15T06:10:59.442Z" title="2022/11/15 14:10:59">2022-11-15</time>发表</span><span class="level-item"><time dateTime="2022-11-15T06:10:59.442Z" title="2022/11/15 14:10:59">2022-11-15</time>更新</span><span class="level-item"><a class="link-muted" href="/categories/%E6%9E%B6%E6%9E%84/">架构</a></span><span class="level-item">几秒读完 (大约14个字)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2022/11/15/2020/2020-02-23-IM%E7%B3%BB%E7%BB%9F%E5%85%B3%E9%94%AE%E7%82%B9%E6%A2%B3%E7%90%86/">IM系统关键点梳理</a></h1><div class="content"><p><img src="https://i.loli.net/2020/03/23/s1q2CZDIrGJvubg.png" alt="IM系统基础.png"></p>
</div></article></div><nav class="pagination" role="navigation" aria-label="pagination"><div class="pagination-previous is-invisible is-hidden-mobile"><a href="/page/0/">上一页</a></div><div class="pagination-next"><a href="/page/2/">下一页</a></div><ul class="pagination-list is-hidden-mobile"><li><a class="pagination-link is-current" href="/">1</a></li><li><a class="pagination-link" href="/page/2/">2</a></li><li><span class="pagination-ellipsis">…</span></li><li><a class="pagination-link" href="/page/18/">18</a></li></ul></nav></div><div class="column column-left is-4-tablet is-4-desktop is-3-widescreen order-1"><div class="card widget" data-type="profile"><div class="card-content"><nav class="level"><div class="level-item has-text-centered flex-shrink-1"><div><figure class="image is-128x128 mx-auto mb-2"><img class="avatar" src="/img/avatar.png" alt="J.K.SAGE"></figure><p class="title is-size-4 is-block" style="line-height:inherit;">J.K.SAGE</p><p class="is-size-6 is-block">Take Your Time</p><p class="is-size-6 is-flex justify-content-center"><i class="fas fa-map-marker-alt mr-1"></i><span>China/Shanghai</span></p></div></div></nav><nav class="level is-mobile"><div class="level-item has-text-centered is-marginless"><div><p class="heading">文章</p><a href="/archives"><p class="title">54</p></a></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading">分类</p><a href="/categories"><p class="title">23</p></a></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading">标签</p><a href="/tags"><p class="title">22</p></a></div></div></nav><div class="level"><a class="level-item button is-primary is-rounded" href="http://github.com/sage417" target="_blank" rel="noopener">关注我</a></div><div class="level is-mobile is-multiline"><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="Github" href="http://github.com/sage417"><i class="fab fa-github"></i></a><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="Facebook" href="https://facebook.com"><i class="fab fa-facebook"></i></a><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="Twitter" href="https://twitter.com"><i class="fab fa-twitter"></i></a><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="Dribbble" href="https://dribbble.com"><i class="fab fa-dribbble"></i></a><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="RSS" href="/"><i class="fas fa-rss"></i></a></div></div></div><!--!--><div class="card widget" data-type="categories"><div class="card-content"><div class="menu"><h3 class="menu-label">分类</h3><ul class="menu-list"><li><a class="level is-mobile" href="/categories/DevKitPro/"><span class="level-start"><span class="level-item">DevKitPro</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/Java%E5%9F%BA%E7%A1%80/"><span class="level-start"><span class="level-item">Java基础</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/categories/Java%E5%B9%B6%E5%8F%91/"><span class="level-start"><span class="level-item">Java并发</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/Java%E6%A1%86%E6%9E%B6/"><span class="level-start"><span class="level-item">Java框架</span></span><span class="level-end"><span class="level-item tag">8</span></span></a><ul><li><a class="level is-mobile" href="/categories/Java%E6%A1%86%E6%9E%B6/Mybatis/"><span class="level-start"><span class="level-item">Mybatis</span></span><span class="level-end"><span class="level-item tag">5</span></span></a></li><li><a class="level is-mobile" href="/categories/Java%E6%A1%86%E6%9E%B6/Spring/"><span class="level-start"><span class="level-item">Spring</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li></ul></li><li><a class="level is-mobile" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/"><span class="level-start"><span class="level-item">中间件</span></span><span class="level-end"><span class="level-item tag">17</span></span></a><ul><li><a class="level is-mobile" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/mysql/"><span class="level-start"><span class="level-item">mysql</span></span><span class="level-end"><span class="level-item tag">5</span></span></a></li><li><a class="level is-mobile" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/nosql/"><span class="level-start"><span class="level-item">nosql</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/resilience4j/"><span class="level-start"><span class="level-item">resilience4j</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/"><span class="level-start"><span class="level-item">搜索引擎</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"><span class="level-start"><span class="level-item">消息队列</span></span><span class="level-end"><span class="level-item tag">4</span></span></a></li><li><a class="level is-mobile" href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/%E7%BC%93%E5%AD%98/"><span class="level-start"><span class="level-item">缓存</span></span><span class="level-end"><span class="level-item tag">5</span></span></a></li></ul></li><li><a class="level is-mobile" href="/categories/%E5%B7%A5%E4%BD%9C/"><span class="level-start"><span class="level-item">工作</span></span><span class="level-end"><span class="level-item tag">10</span></span></a><ul><li><a class="level is-mobile" href="/categories/%E5%B7%A5%E4%BD%9C/%E6%80%BB%E7%BB%93/"><span class="level-start"><span class="level-item">总结</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/categories/%E5%B7%A5%E4%BD%9C/%E6%8C%91%E6%88%98/"><span class="level-start"><span class="level-item">挑战</span></span><span class="level-end"><span class="level-item tag">8</span></span></a></li></ul></li><li><a class="level is-mobile" href="/categories/%E6%9E%B6%E6%9E%84/"><span class="level-start"><span class="level-item">架构</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/categories/%E7%AE%97%E6%B3%95/"><span class="level-start"><span class="level-item">算法</span></span><span class="level-end"><span class="level-item tag">7</span></span></a><ul><li><a class="level is-mobile" href="/categories/%E7%AE%97%E6%B3%95/hash/"><span class="level-start"><span class="level-item">hash</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/%E7%AE%97%E6%B3%95/%E4%BA%8C%E5%8F%89%E6%A0%91/"><span class="level-start"><span class="level-item">二叉树</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/categories/%E7%AE%97%E6%B3%95/%E5%AD%97%E7%AC%A6%E4%B8%B2/"><span class="level-start"><span class="level-item">字符串</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-mobile" href="/categories/%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F/"><span class="level-start"><span class="level-item">排序</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li></ul></li><li><a class="level is-mobile" href="/categories/%E8%AF%BB%E4%B9%A6/"><span class="level-start"><span class="level-item">读书</span></span><span class="level-end"><span class="level-item tag">6</span></span></a></li></ul></div></div></div><div class="card widget" data-type="links"><div class="card-content"><div class="menu"><h3 class="menu-label">链接</h3><ul class="menu-list"><li><a class="level is-mobile" href="https://hexo.io" target="_blank" rel="noopener"><span class="level-left"><span class="level-item">Hexo</span></span><span class="level-right"><span class="level-item tag">hexo.io</span></span></a></li><li><a class="level is-mobile" href="http://mysql.taobao.org" target="_blank" rel="noopener"><span class="level-left"><span class="level-item">taobaomysql</span></span><span class="level-right"><span class="level-item tag">mysql.taobao.org</span></span></a></li><li><a class="level is-mobile" href="http://luck-cheng.github.io" target="_blank" rel="noopener"><span class="level-left"><span class="level-item">luck-cheng</span></span><span class="level-right"><span class="level-item tag">luck-cheng.github.io</span></span></a></li><li><a class="level is-mobile" href="https://my.oschina.net/guangshan?tab=newest&catalogId=5744161" target="_blank" rel="noopener"><span class="level-left"><span class="level-item">guanshan</span></span><span class="level-right"><span class="level-item tag">my.oschina.net</span></span></a></li></ul></div></div></div><div class="column-right-shadow is-hidden-widescreen"></div></div><div class="column column-right is-4-tablet is-4-desktop is-3-widescreen is-hidden-touch is-hidden-desktop-only order-3"><div class="card widget" data-type="recent-posts"><div class="card-content"><h3 class="menu-label">最新文章</h3><article class="media"><div class="media-content"><p class="date"><time dateTime="2023-01-29T16:00:00.000Z">2023-01-30</time></p><p class="title"><a href="/2023/01/30/2023/2023-01-30_Sharding_JDBC%20%E5%85%A5%E9%97%A8/">Sharding-JDBC入门</a></p><p class="categories"><a href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/">中间件</a> / <a href="/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/mysql/">mysql</a></p></div></article><article class="media"><div class="media-content"><p class="date"><time dateTime="2022-11-29T16:00:00.000Z">2022-11-30</time></p><p class="title"><a href="/2022/11/30/2022/2022-11-30-Mongo%20Decimal128%20%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E8%A7%A3%E5%86%B3/">Mongo Decimal128 类型转换问题排查解决</a></p><p class="categories"><a href="/categories/%E5%B7%A5%E4%BD%9C/">工作</a> / <a href="/categories/%E5%B7%A5%E4%BD%9C/%E6%8C%91%E6%88%98/">挑战</a></p></div></article><article class="media"><div class="media-content"><p class="date"><time dateTime="2022-11-15T06:10:59.442Z">2022-11-15</time></p><p class="title"><a href="/2022/11/15/2020/2020-02-23-IM%E7%B3%BB%E7%BB%9F%E5%85%B3%E9%94%AE%E7%82%B9%E6%A2%B3%E7%90%86/">IM系统关键点梳理</a></p><p class="categories"><a href="/categories/%E6%9E%B6%E6%9E%84/">架构</a></p></div></article><article class="media"><div class="media-content"><p class="date"><time dateTime="2021-07-12T16:00:00.000Z">2021-07-13</time></p><p class="title"><a href="/2021/07/13/2021/2021-07-13-%E6%8E%92%E6%9F%A5JDK%E8%AF%A1%E5%BC%82%E5%A4%8F%E4%BB%A4%E6%97%B6%E9%97%AE%E9%A2%98/">排查诡异的夏令时问题</a></p><p class="categories"><a href="/categories/%E5%B7%A5%E4%BD%9C/">工作</a> / <a href="/categories/%E5%B7%A5%E4%BD%9C/%E6%8C%91%E6%88%98/">挑战</a></p></div></article><article class="media"><div class="media-content"><p class="date"><time dateTime="2021-03-30T02:00:00.000Z">2021-03-30</time></p><p class="title"><a href="/2021/03/30/2021/2021-03-30-Eureka%E5%8F%91%E7%8E%B0%E6%9C%BA%E5%88%B6/">Eureka服务发现机制</a></p><p class="categories"><a href="/categories/Java%E6%A1%86%E6%9E%B6/">Java框架</a> / <a href="/categories/Java%E6%A1%86%E6%9E%B6/Spring/">Spring</a></p></div></article></div></div><div class="card widget" data-type="archives"><div class="card-content"><div class="menu"><h3 class="menu-label">归档</h3><ul class="menu-list"><li><a class="level is-mobile" href="/archives/2023/01/"><span class="level-start"><span class="level-item">一月 2023</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2022/11/"><span class="level-start"><span class="level-item">十一月 2022</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2021/07/"><span class="level-start"><span class="level-item">七月 2021</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2021/03/"><span class="level-start"><span class="level-item">三月 2021</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2020/08/"><span class="level-start"><span class="level-item">八月 2020</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2020/07/"><span class="level-start"><span class="level-item">七月 2020</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2020/06/"><span class="level-start"><span class="level-item">六月 2020</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2020/05/"><span class="level-start"><span class="level-item">五月 2020</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2020/04/"><span class="level-start"><span class="level-item">四月 2020</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-mobile" href="/archives/2020/03/"><span class="level-start"><span class="level-item">三月 2020</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-mobile" href="/archives/2020/02/"><span class="level-start"><span class="level-item">二月 2020</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2020/01/"><span class="level-start"><span class="level-item">一月 2020</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/12/"><span class="level-start"><span class="level-item">十二月 2019</span></span><span class="level-end"><span class="level-item tag">9</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/11/"><span class="level-start"><span class="level-item">十一月 2019</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/09/"><span class="level-start"><span class="level-item">九月 2019</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/08/"><span class="level-start"><span class="level-item">八月 2019</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/07/"><span class="level-start"><span class="level-item">七月 2019</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/06/"><span class="level-start"><span class="level-item">六月 2019</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/05/"><span class="level-start"><span class="level-item">五月 2019</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/04/"><span class="level-start"><span class="level-item">四月 2019</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/03/"><span class="level-start"><span class="level-item">三月 2019</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/02/"><span class="level-start"><span class="level-item">二月 2019</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/01/"><span class="level-start"><span class="level-item">一月 2019</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2018/12/"><span class="level-start"><span class="level-item">十二月 2018</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2018/11/"><span class="level-start"><span class="level-item">十一月 2018</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-mobile" href="/archives/2018/09/"><span class="level-start"><span class="level-item">九月 2018</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2018/07/"><span class="level-start"><span class="level-item">七月 2018</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li></ul></div></div></div><div class="card widget" data-type="tags"><div class="card-content"><div class="menu"><h3 class="menu-label">标签</h3><div class="field is-grouped is-grouped-multiline"><div class="control"><a class="tags has-addons" href="/tags/IM%E7%B3%BB%E7%BB%9F/"><span class="tag">IM系统</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/aop/"><span class="tag">aop</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/java/"><span class="tag">java</span><span class="tag">14</span></a></div><div class="control"><a class="tags has-addons" href="/tags/linux/"><span class="tag">linux</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/mybatis/"><span class="tag">mybatis</span><span class="tag">5</span></a></div><div class="control"><a class="tags has-addons" href="/tags/redis/"><span class="tag">redis</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/spring/"><span class="tag">spring</span><span class="tag">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/"><span class="tag">中间件</span><span class="tag">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E4%BB%A3%E7%A0%81/"><span class="tag">代码</span><span class="tag">23</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"><span class="tag">分布式</span><span class="tag">7</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%9B%A2%E9%98%9F/"><span class="tag">团队</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%B7%A5%E4%BD%9C/"><span class="tag">工作</span><span class="tag">24</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%84%9F%E6%82%9F/"><span class="tag">感悟</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%90%9C%E7%B4%A2/"><span class="tag">搜索</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"><span class="tag">操作系统</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"><span class="tag">数据库</span><span class="tag">5</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%9E%B6%E6%9E%84/"><span class="tag">架构</span><span class="tag">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%B6%88%E6%81%AF/"><span class="tag">消息</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"><span class="tag">消息队列</span><span class="tag">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%AE%97%E6%B3%95/"><span class="tag">算法</span><span class="tag">7</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%BC%93%E5%AD%98/"><span class="tag">缓存</span><span class="tag">6</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E8%AF%BB%E4%B9%A6/"><span class="tag">读书</span><span class="tag">8</span></a></div></div></div></div></div></div></div></div></section><footer class="footer"><div class="container"><div class="level"><div class="level-start"><a class="footer-logo is-block mb-2" href="/"><img src="/img/logo.svg" alt="塑料内存" height="28"></a><p class="is-size-7"><span>© 2023 NightWish417</span> Powered by <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a> & <a href="https://github.com/ppoffice/hexo-theme-icarus" target="_blank" rel="noopener">Icarus</a></p></div><div class="level-end"><div class="field has-addons"><p class="control"><a class="button is-transparent is-large" target="_blank" rel="noopener" title="Creative Commons" href="https://creativecommons.org/"><i class="fab fa-creative-commons"></i></a></p><p class="control"><a class="button is-transparent is-large" target="_blank" rel="noopener" title="Attribution 4.0 International" href="https://creativecommons.org/licenses/by/4.0/"><i class="fab fa-creative-commons-by"></i></a></p><p class="control"><a class="button is-transparent is-large" target="_blank" rel="noopener" title="Download on GitHub" href="https://github.com/ppoffice/hexo-theme-icarus"><i class="fab fa-github"></i></a></p></div></div></div></div></footer><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/min/moment-with-locales.min.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js" defer></script><script>moment.locale("zh-CN");</script><script>var IcarusThemeSettings = {
article: {
highlight: {
clipboard: true,
fold: 'unfolded'
}
}
};</script><script src="/js/column.js"></script><script src="/js/animation.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/lightgallery.min.js" defer></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/jquery.justifiedGallery.min.js" defer></script><script>window.addEventListener("load", () => {
if (typeof $.fn.lightGallery === 'function') {
$('.article').lightGallery({ selector: '.gallery-item' });
}
if (typeof $.fn.justifiedGallery === 'function') {
if ($('.justified-gallery > p > .gallery-item').length) {
$('.justified-gallery > p > .gallery-item').unwrap();
}
$('.justified-gallery').justifiedGallery();
}
});</script><script type="text/x-mathjax-config">MathJax.Hub.Config({
'HTML-CSS': {
matchFontHeight: false
},
SVG: {
matchFontHeight: false
},
CommonHTML: {
matchFontHeight: false
},
tex2jax: {
inlineMath: [
['$','$'],
['\\(','\\)']
]
}
});</script><script src="https://cdn.jsdelivr.net/npm/[email protected]/unpacked/MathJax.js?config=TeX-MML-AM_CHTML" defer></script><!--!--><!--!--><div id="outdated"><h6>Your browser is out-of-date!</h6><p>Update your browser to view this website correctly.&npsb;<a id="btnUpdateBrowser" href="http://outdatedbrowser.com/">Update my browser now </a></p><p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">×</a></p></div><script src="https://cdn.jsdelivr.net/npm/[email protected]/outdatedbrowser/outdatedbrowser.min.js" defer></script><script>window.addEventListener("load", function () {
outdatedBrowser({
bgColor: '#f25648',
color: '#ffffff',
lowerThan: 'object-fit' // display on IE11 or below
});
});</script><a id="back-to-top" title="回到顶端" href="javascript:;"><i class="fas fa-chevron-up"></i></a><script src="/js/back_to_top.js" defer></script><!--!--><!--!--><script src="/js/main.js" defer></script><div class="searchbox"><div class="searchbox-container"><div class="searchbox-header"><div class="searchbox-input-container"><input class="searchbox-input" type="text" placeholder="想要查找什么..."></div><a class="searchbox-close" href="javascript:;">×</a></div><div class="searchbox-body"></div></div></div><script src="/js/insight.js" defer></script><script>document.addEventListener('DOMContentLoaded', function () {
loadInsight({"contentUrl":"/content.json"}, {"hint":"想要查找什么...","untitled":"(无标题)","posts":"文章","pages":"页面","categories":"分类","tags":"标签"});
});</script></body></html>