<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>k-on-forever&#39;s Blog</title>
  
  <subtitle>热爱技术，享受生活</subtitle>
  <link href="https://kon-forever.cloud/atom.xml" rel="self"/>
  
  <link href="https://kon-forever.cloud/"/>
  <updated>2026-09-28T02:39:05.446Z</updated>
  <id>https://kon-forever.cloud/</id>
  
  <author>
    <name>k-on-forever</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Redis学习笔记之高级篇</title>
    <link href="https://kon-forever.cloud/2026/04/04/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E9%AB%98%E7%BA%A7%E7%AF%87/"/>
    <id>https://kon-forever.cloud/2026/04/04/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E9%AB%98%E7%BA%A7%E7%AF%87/</id>
    <published>2026-04-04T02:00:00.000Z</published>
    <updated>2026-09-28T02:39:05.446Z</updated>
    
    <content type="html"><![CDATA[<h3 id="分布式缓存"><a href="#分布式缓存" class="headerlink" title="分布式缓存"></a>分布式缓存</h3><h4 id="Redis持久化"><a href="#Redis持久化" class="headerlink" title="Redis持久化"></a>Redis持久化</h4><h5 id="RDB持久化"><a href="#RDB持久化" class="headerlink" title="RDB持久化"></a>RDB持久化</h5><p>① 介绍</p><p>RDB全称Redis Database Backup file（Redis数据备份文件），也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后，从磁盘读取快照文件，恢复数据。快照文件称为RDB文件，默认是保存在当前运行目录。</p><p>② 执行时机</p><ol><li>save命令</li></ol><p>执行下面的命令，可以立即执行一次RDB：</p><img src="https://i-blog.csdnimg.cn/direct/e1bba7c51ed046ccb152b6f5fa43bb1f.png" alt="img" style="zoom:67%;" /><p>save命令会导致主进程执行RDB，这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。</p><ol start="2"><li>bgsave命令</li></ol><p>下面的命令可以异步执行RDB：</p><img src="https://i-blog.csdnimg.cn/direct/f65ff59710a54063a8e6efa020d77e0f.png" alt="img" style="zoom:67%;" /><p>这个命令执行后会开启独立进程完成RDB，主进程可以持续处理用户请求，不受影响。</p><ol start="3"><li>停机时</li></ol><p>Redis停机时会执行一次save命令，实现RDB持久化。</p><ol start="4"><li>触发RDB条件</li></ol><p>Redis内部有触发RDB的机制，可以在redis.conf文件中找到，格式如下：</p><figure class="highlight bash"><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="comment"># 900秒内，如果至少有1个key被修改，则执行bgsave ， 如果是save &quot;&quot; 则表示禁用RDB</span></span><br><span class="line">save 900 1  </span><br><span class="line">save 300 10  </span><br><span class="line">save 60 10000 </span><br></pre></td></tr></table></figure><p>RDB的其它配置也可以在redis.conf文件中设置：</p><figure class="highlight bash"><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="comment"># 是否压缩 ,建议不开启，压缩也会消耗cpu，磁盘的话不值钱</span></span><br><span class="line">rdbcompression <span class="built_in">yes</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># RDB文件名称</span></span><br><span class="line">dbfilename dump.rdb  </span><br><span class="line"></span><br><span class="line"><span class="comment"># 文件保存的路径目录</span></span><br><span class="line"><span class="built_in">dir</span> ./ </span><br></pre></td></tr></table></figure><p>③ RDB原理</p><ol><li>bgsave开始时会fork主进程得到子进程，子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。</li><li>fork采用的是copy-on-write技术：</li></ol><ul><li>当主进程执行读操作时，访问共享内存；</li><li>当主进程执行写操作时，则会拷贝一份数据，执行写操作。</li></ul><img src="https://i-blog.csdnimg.cn/direct/649c3d510b3e4d6390df5be25992217c.png" alt="img" style="zoom: 50%;" /><ol start="3"><li>当主进程要修改某块数据（比如图里的「数据 B」）时：</li></ol><ul><li>操作系统发现这块数据是只读的，先在物理内存里<strong>拷贝一份新副本</strong>（「数据 B 副本」）</li><li>主进程更新页表，让自己指向这个新副本，然后在副本上执行写操作</li><li>原来的「数据 B」保持不变，继续给子进程读（保证子进程看到的是 <code>fork</code> 那一刻的完整数据，不会被主进程的修改打乱）</li></ul><h5 id="AOF持久化"><a href="#AOF持久化" class="headerlink" title="AOF持久化"></a>AOF持久化</h5><p>① 介绍</p><p>AOF全称为Append Only File（追加文件）。Redis处理的每一个写命令都会记录在AOF文件，可以看做是命令日志文件。</p><img src="https://i-blog.csdnimg.cn/direct/aa3985dd2feb4b1aa1f8acdde927ac4c.png" alt="img" style="zoom:50%;" /><p>② AOF配置</p><ol><li>AOF默认是关闭的，需要修改redis.conf配置文件来开启AOF：</li></ol><figure class="highlight bash"><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="comment"># 是否开启AOF功能，默认是no</span></span><br><span class="line">appendonly <span class="built_in">yes</span></span><br><span class="line"><span class="comment"># AOF文件的名称</span></span><br><span class="line">appendfilename <span class="string">&quot;appendonly.aof&quot;</span></span><br></pre></td></tr></table></figure><ol start="2"><li>AOF的命令记录的频率也可以通过redis.conf文件来配：</li></ol><figure class="highlight bash"><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"><span class="comment"># 表示每执行一次写命令，立即记录到AOF文件</span></span><br><span class="line">appendfsync always </span><br><span class="line"><span class="comment"># 写命令执行完先放入AOF缓冲区，然后表示每隔1秒将缓冲区数据写到AOF文件，是默认方案</span></span><br><span class="line">appendfsync everysec </span><br><span class="line"><span class="comment"># 写命令执行完先放入AOF缓冲区，由操作系统决定何时将缓冲区内容写回磁盘</span></span><br><span class="line">appendfsync no</span><br></pre></td></tr></table></figure><img src="https://i-blog.csdnimg.cn/direct/b46e69925a994032aac9587b35b05c54.png" alt="img" style="zoom:67%;" /><h5 id="AOF文件重写"><a href="#AOF文件重写" class="headerlink" title="AOF文件重写"></a>AOF文件重写</h5><p>因为是记录命令，AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作，但只有最后一次写操作才有意义。通过执行bgrewriteaof命令，可以让AOF文件执行重写功能，用最少的命令达到相同效果。</p><img src="https://i-blog.csdnimg.cn/direct/b4f4710988db4fbebf0eee8f69e539c0.png" alt="img" style="zoom:67%;" /><p>如图，AOF原本有三个命令，但是<code>set num 123 和 set num 666</code>都是对num的操作，第二次会覆盖第一次的值，因此第一个命令记录下来没有意义。 所以重写命令后，AOF文件内容就是：<code>mset name jack num 666</code></p><h4 id="Redis主从"><a href="#Redis主从" class="headerlink" title="Redis主从"></a>Redis主从</h4><h5 id="主从数据同步原理"><a href="#主从数据同步原理" class="headerlink" title="主从数据同步原理"></a>主从数据同步原理</h5><p><strong>① 全量同步</strong></p><p>主从第一次建立连接时，会执行<strong>全量同步</strong>，将master节点的所有数据都拷贝给slave节点，流程如下：</p><ol><li>核心概念</li></ol><p>Replication ID（replid）</p><ul><li>每个 master 都有一个<strong>唯一的 replid</strong>，它是当前数据集的 “身份证”，代表这一套完整数据。</li><li>slave 会继承 master 的 replid，所以<strong>同一套数据集的 master 和 slave，replid 一定是一致的</strong>。</li></ul><p>offset（偏移量）</p><ul><li>master 每执行一条写命令，offset 就会递增，同时把命令记录到 <code>repl_backlog</code> 缓冲区。</li><li>slave 同步完成后，也会记录自己当前同步到的 offset。</li><li>如果 slave 的 offset &lt; master 的 offset，说明 slave 数据落后，需要补同步。</li><li>master 如何判断 slave 是「第一次连接」？</li></ul><p>判断逻辑非常简单：<strong>看 replid 是否一致</strong></p><p>当它第一次执行 <code>replicaof</code> 变成 slave，连接 master 时，会把<strong>自己原来的 replid 和 offset</strong> 发给 master。</p><p>master 检查：</p><ul><li>如果 slave 的 replid ≠ 自己的 replid → 判定这是<strong>全新的 slave（第一次连接）</strong>，需要做<strong>全量同步</strong>。</li><li>如果 slave 的 replid &#x3D; 自己的 replid → 判定这是<strong>重连的 slave</strong>，只需要做<strong>增量同步</strong>（补 offset 之后的命令）。</li><li>完整主从同步流程</li></ul><p>第一步：连接协商（psync 阶段）</p><ul><li>slave 执行 <code>replicaof</code>，和 master 建立连接，发送 <code>psync &lt;slave的replid&gt; &lt;slave的offset&gt;</code>。</li><li>master 发现 slave 的 replid 和自己不一致 → 拒绝增量同步，返回<strong>自己的 replid 和当前 offset</strong>。</li><li>slave 保存 master 的 replid 和 offset，之后它的 replid 就和 master 一致了。</li></ul><p>第二步：全量同步（RDB 阶段）</p><ul><li>master 后台执行 <code>bgsave</code>，生成 RDB 快照（把当前内存里的所有数据导出成文件）。</li><li>生成 RDB 的同时，master 会把这段时间收到的所有写命令，记录到 <code>repl_backlog</code> 缓冲区（避免这段时间的数据丢失）。</li><li>RDB 生成后，master 把文件发送给 slave。</li><li>slave 清空本地所有数据，加载 RDB 文件，恢复到 master 生成 RDB 时的状态。</li></ul><p>第三步：增量同步（命令回放阶段）</p><ul><li>master 把 <code>repl_backlog</code> 里记录的、RDB 生成期间的所有写命令，逐条发给 slave。</li><li>slave 接收并执行这些命令，把 RDB 之后的操作全部补上，最终和 master 数据完全一致。</li><li>之后，master 每收到一条写命令，都会实时同步给所有 slave，slave 执行命令，保持持续同步。</li></ul><img src="https://i-blog.csdnimg.cn/direct/b123e21bcafb48ebb841ef54b88245f9.png" alt="img" style="zoom:67%;" /><p>② repl_backlog原理</p><ol><li><code>repl_backlog</code> 是 master 节点上的一个<strong>固定大小的环形数组缓冲区</strong>：</li></ol><ul><li><strong>环形结构</strong>：数组下标走到末尾后，会从 0 重新开始读写，旧数据会被新数据覆盖。</li><li><strong>存储内容</strong>：保存 master 最近处理过的<strong>写命令日志</strong>，以及每条命令对应的 <code>offset</code>（偏移量）。</li><li><strong>核心作用</strong>：通过对比 master 和 slave 的 <code>offset</code>，精准定位两者的数据差异，实现高效增量同步。</li></ul><p><img src="https://i-blog.csdnimg.cn/direct/2409e88aa68844bc8e60936270d09f74.png" alt="img"></p><p><strong>2) 正常同步流程</strong></p><p><strong>offset 标记差异</strong></p><p>master 维护自己的当前 <code>offset</code>（红色标记），slave 维护自己已同步到的 <code>offset</code>（绿色标记）。两者之间的差值，就是 slave 缺失的、需要增量同步的命令数据。</p><p><strong>同步追赶</strong></p><p>随着 master 不断写入新数据，它的 <code>offset</code> 持续增大；slave 不断从 master 拉取命令并执行，自己的 <code>offset</code> 也在追赶 master 的 <code>offset</code>，绿色（已同步）区域逐渐扩大。</p><p><strong>安全覆盖</strong></p><p>当缓冲区被写满后，新写入的命令会覆盖最旧的数据：</p><ul><li>被覆盖的如果是 绿色（已同步到 slave）的数据，完全没有影响，因为这些数据已经不需要再同步了；</li><li>未同步的只有 红色（master 与 slave 之间）的部分，只要这部分还在缓冲区里，就能继续增量同步。</li></ul><p><strong>3) 异常场景：网络阻塞</strong></p><p>当 slave 出现网络阻塞、长时间无法同步时，流程会发生变化：</p><p><strong>差距拉大</strong></p><p>master 持续写入，<code>offset</code> 快速增长，而 slave 的 <code>offset</code> 停滞不前，两者的差距（红色未同步区域）越来越大。</p><p><strong>缓冲区覆盖危机</strong></p><p>棕色框中的红色部分，就是尚未同步，但是却已经被覆盖的数据。此时如果slave恢复，需要同步，却发现自己的offset都没有了，无法完成增量同步了。只能做全量同步。</p><p><img src="https://i-blog.csdnimg.cn/direct/e7972e47ed12426ebc3125ce4bc398e2.png" alt="img"></p><h5 id="主从同步优化"><a href="#主从同步优化" class="headerlink" title="主从同步优化"></a>主从同步优化</h5><p>可以从以下几个方面来优化Redis主从就集群：</p><ul><li>在master中配置repl-diskless-sync yes启用无磁盘复制，避免全量同步时的磁盘IO。</li><li>Redis单节点上的内存占用不要太大，减少RDB导致的过多磁盘IO</li><li>适当提高repl_baklog的大小，发现slave宕机时尽快实现故障恢复，尽可能避免全量同步</li><li>限制一个master上的slave节点数量，如果实在是太多slave，则可以采用主-从-从链式结构，减少master压力<img src="https://i-blog.csdnimg.cn/direct/738d436a431d4442a3a2e09ed37a57b7.png" alt="img" style="zoom:67%;" /></li></ul><h4 id="Redis哨兵"><a href="#Redis哨兵" class="headerlink" title="Redis哨兵"></a>Redis哨兵</h4><h5 id="哨兵原理"><a href="#哨兵原理" class="headerlink" title="哨兵原理"></a>哨兵原理</h5><p>① 集群结构和作用</p><img src="https://i-blog.csdnimg.cn/direct/1961ecc64b7a4a2da0698c65c0f2c0b8.png" alt="img" style="zoom:67%;" /><p>哨兵的作用如下：</p><ul><li><strong>监控</strong>：Sentinel 会不断检查您的master和slave是否按预期工作</li><li><strong>自动故障恢复</strong>：如果master故障，Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主</li><li><strong>通知</strong>：Sentinel充当Redis客户端的服务发现来源，当集群发生故障转移时，会将最新信息推送给Redis的客户端</li></ul><p>② 集群监控原理</p><p>Sentinel基于心跳机制监测服务状态，每隔1秒向集群的每个实例发送ping命令：</p><ul><li>主观下线：如果某sentinel节点发现某实例未在规定时间响应，则认为该实例<strong>主观下线</strong>。</li><li>客观下线：若超过指定数量（quorum）的sentinel都认为该实例主观下线，则该实例<strong>客观下线</strong>。quorum值最好超过Sentinel实例数量的一半。</li></ul><img src="https://i-blog.csdnimg.cn/direct/93c54e6d5b6f415eb945bea0b96ac0f4.png" alt="img" style="zoom:67%;" /><p>③ 集群故障恢复原理</p><p>一旦发现master故障，sentinel需要在salve中选择一个作为新的master，选择依据是这样的：</p><ul><li>首先会判断slave节点与master节点断开时间长短，如果超过指定值（down-after-milliseconds * 10）则会排除该slave节点</li><li>然后判断slave节点的slave-priority值，越小优先级越高，如果是0则永不参与选举</li><li>如果slave-prority一样，则判断slave节点的offset值，越大说明数据越新，优先级越高</li><li>最后是判断slave节点的运行id大小，越小优先级越高。</li></ul><p>④ 如何实现故障转移</p><ul><li>sentinel给备选的slave1节点发送slaveof no one命令，让该节点成为master</li><li>sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令，让这些slave成为新master的从节点，开始从新的master上同步数据。</li><li>最后，sentinel将故障节点标记为slave，当故障节点恢复后会自动成为新的master的slave节点</li></ul><h5 id="RedisTemplate"><a href="#RedisTemplate" class="headerlink" title="RedisTemplate"></a>RedisTemplate</h5><p>① 配置Redis地址</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">spring:</span><br><span class="line">  redis:</span><br><span class="line">    sentinel:</span><br><span class="line">      master: mymaster</span><br><span class="line">      nodes:</span><br><span class="line">        - <span class="number">192.168</span><span class="number">.150</span><span class="number">.101</span>:<span class="number">27001</span></span><br><span class="line">        - <span class="number">192.168</span><span class="number">.150</span><span class="number">.101</span>:<span class="number">27002</span></span><br><span class="line">        - <span class="number">192.168</span><span class="number">.150</span><span class="number">.101</span>:<span class="number">27003</span></span><br></pre></td></tr></table></figure><p>② 配置读写分离</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> LettuceClientConfigurationBuilderCustomizer <span class="title function_">clientConfigurationBuilderCustomizer</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> clientConfigurationBuilder -&gt; clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个bean中配置的就是读写策略，包括四种：</p><ul><li>MASTER：从主节点读取</li><li>MASTER_PREFERRED：优先从master节点读取，master不可用才读取replica</li><li>REPLICA：从slave（replica）节点读取</li><li>REPLICA _PREFERRED：优先从slave（replica）节点读取，所有的slave都不可用才读取master</li></ul><h4 id="Redis分片集群"><a href="#Redis分片集群" class="headerlink" title="Redis分片集群"></a>Redis分片集群</h4><h5 id="散列插槽"><a href="#散列插槽" class="headerlink" title="散列插槽"></a>散列插槽</h5><p>① 插槽的 “分配规则”：16384 个插槽分给所有 master 节点</p><p>Redis 集群总共有 <strong>0~16383</strong> 这 16384 个插槽（相当于 16384 个 “数据格子”），这些插槽会平均分给集群里的所有 master 节点。</p><p><img src="https://i-blog.csdnimg.cn/direct/2e16e54a9614426babbfe00172cb2b82.png" alt="img"></p><p>② 数据的 “寻址规则”：key→插槽号→节点</p><p>Redis 存数据时，会先给 key 算一个插槽号，再找这个插槽号对应的节点，步骤如下：</p><p>步骤 1：确定 key 的 “有效部分”（计算的依据）</p><ul><li>情况 1：key 里没有<code>{}</code> → 整个 key 都是有效部分（比如<code>num</code>、<code>a</code>，就用完整的<code>num</code>&#x2F;<code>a</code>计算）；</li><li>情况 2：key 里有<code>{}</code> → 只算<code>{}</code>里的内容（比如<code>{itcast}num</code>，只算<code>itcast</code>，不管外面的<code>num</code>）；</li></ul><p>步骤 2：计算插槽号（固定算法）</p><p>用「有效部分」通过 <strong>CRC16 算法</strong> 算出一个数字，再对 16384 取余数（余数范围 0~16383），结果就是插槽号。</p><h5 id="集群伸缩"><a href="#集群伸缩" class="headerlink" title="集群伸缩"></a>集群伸缩</h5><p>① 需求分析</p><p>需求：向集群中添加一个新的master节点，并向其中存储 num &#x3D; 10</p><ul><li>启动一个新的redis实例，端口为7004</li><li>添加7004到之前的集群，并作为一个master节点</li><li>给7004节点分配插槽，使得num这个key可以存储到7004实例</li></ul><p>② 创建新的redis实例</p><p>做什么：创建 7004 的工作目录、修改配置、启动实例</p><figure class="highlight bash"><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="comment"># 1. 创建7004目录</span></span><br><span class="line"><span class="built_in">mkdir</span> 7004</span><br><span class="line"><span class="comment"># 2. 复制通用配置文件到7004</span></span><br><span class="line"><span class="built_in">cp</span> redis.conf 7004/</span><br><span class="line"><span class="comment"># 3. 把配置里的6379全替换为7004（端口、目录、pid等）</span></span><br><span class="line">sed -i <span class="string">&#x27;s/6379/7004/g&#x27;</span> 7004/redis.conf</span><br><span class="line"><span class="comment"># 4. 启动7004实例（后台运行）</span></span><br><span class="line">redis-server 7004/redis.conf</span><br></pre></td></tr></table></figure><p>③ 添加新节点到redis</p><p>做什么：用<code>add-node</code>把 7004 接入集群，此时 7004 是 master，但<strong>0 个哈希槽，存不了数据</strong></p><figure class="highlight bash"><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"># 语法：redis-cli --cluster add-node 新节点IP:端口 集群任意已有节点IP:端口</span></span><br><span class="line">redis-cli --cluster add-node 192.168.238.138:7004 192.168.238.138:7001</span><br></pre></td></tr></table></figure><p>④ 添加新节点到redis</p><p>做什么：算出<code>num</code>这个 key 对应的槽位，确认它在哪个旧主节点的槽区间里</p><figure class="highlight bash"><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"># 计算num的哈希槽号</span></span><br><span class="line">redis-cli --cluster keyslot 192.168.238.138:7001 --key num</span><br></pre></td></tr></table></figure><p>✅ 结果：<code>num</code>对应槽位<code>2765</code>，属于 7001 的<code>0-5460</code>槽区间，因此要从 7001 移槽。</p><p>⑤ 转移插槽</p><p>做什么：交互式把<code>0-3000</code>的槽（包含 2765）从 7001 迁移到 7004，数据会跟着槽自动迁移</p><figure class="highlight bash"><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"># 启动reshard，指定集群任意节点</span></span><br><span class="line">redis-cli --cluster reshard 192.168.238.138:7001</span><br></pre></td></tr></table></figure><h5 id="故障转移"><a href="#故障转移" class="headerlink" title="故障转移"></a>故障转移</h5><p>① 自动故障转移</p><figure class="highlight bash"><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"># 1. 直接杀掉7002进程（模拟宕机）</span></span><br><span class="line">redis-cli -p 7002 shutdown</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 查看集群状态（会看到7002状态变为master,fail? 或 disconnected）</span></span><br><span class="line">redis-cli -p 7001 cluster nodes</span><br></pre></td></tr></table></figure><p>✅ 结果：过几秒后，7002 的从节点（比如 8002）会自动变成 Master。</p><p>② 手动故障转移</p><ol><li>利用cluster failover命令可以手动让集群中的某个master宕机，切换到执行cluster failover命令的这个slave节点，实现无感知的数据迁移。其流程如下：</li></ol><img src="https://i-blog.csdnimg.cn/direct/655a517278cf4147aa96ebe29e73e2ec.png" alt="img" style="zoom:67%;" /><ol start="2"><li>这种failover命令可以指定三种模式：</li></ol><ul><li>缺省：默认的流程，如图1~6歩</li><li>force：省略了对offset的一致性校验</li><li>takeover：直接执行第5歩，忽略数据一致性、忽略master状态和其它master的意见</li><li>实操步骤</li></ul><p>连接到 7002 的从节点（比如 8002）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-cli -p 8002</span><br></pre></td></tr></table></figure><p>执行故障转移命令</p><figure class="highlight bash"><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">127.0.0.1:8002&gt; CLUSTER FAILOVER</span><br><span class="line"><span class="comment"># 输出 OK</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Redis 高级特性：持久化机制 RDB/AOF、主从哨兵架构与分布式缓存方案</summary>
    
    
    
    <category term="Redis" scheme="https://kon-forever.cloud/categories/Redis/"/>
    
    
    <category term="Redis" scheme="https://kon-forever.cloud/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>Redis学习笔记之实战篇</title>
    <link href="https://kon-forever.cloud/2026/03/29/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E5%AE%9E%E6%88%98%E7%AF%87/"/>
    <id>https://kon-forever.cloud/2026/03/29/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E5%AE%9E%E6%88%98%E7%AF%87/</id>
    <published>2026-03-29T03:00:00.000Z</published>
    <updated>2026-09-28T02:48:16.549Z</updated>
    
    <content type="html"><![CDATA[<h2 id="实战篇"><a href="#实战篇" class="headerlink" title="实战篇"></a>实战篇</h2><h3 id="短信登录"><a href="#短信登录" class="headerlink" title="短信登录"></a>短信登录</h3><h4 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h4><p><strong>发送验证码：</strong></p><p>用户在提交手机号后，会校验手机号是否合法，如果不合法，则要求用户重新输入手机号。如果手机号合法，后台此时生成对应的验证码，同时将验证码进行保存，然后再通过短信的方式将验证码发送给用户。</p><p><strong>短信验证码登录、注册：</strong></p><p>用户将验证码和手机号进行输入，后台从session中拿到当前验证码，然后和用户输入的验证码进行校验，如果不一致，则无法通过校验，如果一致，则后台根据手机号查询用户，如果用户不存在，则为用户创建账号信息，保存到数据库，无论是否存在，都会将用户信息保存到session中，方便后续获得当前登录信息。</p><p><strong>校验登录状态：</strong></p><p>用户在请求时候，会从cookie中携带者JsessionId到后台，后台通过JsessionId从session中拿到用户信息，如果没有session信息，则进行拦截，如果有session信息，则将用户信息保存到threadLocal中，并且放行。</p><img src="https://i-blog.csdnimg.cn/direct/f772e3bfc10d4593b05301c90bfd1929.png" alt="img" style="zoom:67%;" /><h4 id="实现发送短信验证码功能"><a href="#实现发送短信验证码功能" class="headerlink" title="实现发送短信验证码功能"></a>实现发送短信验证码功能</h4><p>发送验证码</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="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">sendCode</span><span class="params">(String phone, HttpSession session)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.校验手机号</span></span><br><span class="line">    <span class="keyword">if</span> (RegexUtils.isPhoneInvalid(phone)) &#123;</span><br><span class="line">        <span class="comment">// 2.如果不符合，返回错误信息</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;手机号格式错误！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3.符合，生成验证码</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">code</span> <span class="operator">=</span> RandomUtil.randomNumbers(<span class="number">6</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 4.保存验证码到 session</span></span><br><span class="line">    session.setAttribute(<span class="string">&quot;code&quot;</span>,code);</span><br><span class="line">    <span class="comment">// 5.发送验证码</span></span><br><span class="line">    log.debug(<span class="string">&quot;发送短信验证码成功，验证码：&#123;&#125;&quot;</span>, code);</span><br><span class="line">    <span class="comment">// 返回ok</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① <code>session.setAttribute(&quot;code&quot;, code)</code>：核心是「保存校验凭证，实现用户绑定」</p><p>当用户输入手机号 + 验证码提交登录时，后端会执行 <code>session.getAttribute(&quot;code&quot;)</code>，取出这里存的验证码，和用户输入的对比 ——<strong>没有这行存储，后续登录就没有 “基准值” 可校验</strong>。</p><p>② <code>log.debug(&quot;发送短信验证码成功，验证码：{}&quot;, code)</code>：核心是「调试排查，记录运行状态」</p><p><code>{}</code>：SLF4J 的<strong>占位符</strong>，会自动把后面的 <code>code</code> 变量值替换到这个位置（比如最终打印：<code>发送短信验证码成功，验证码：123456</code>）。</p><p>登录</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">login</span><span class="params">(LoginFormDTO loginForm, HttpSession session)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.校验手机号</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">phone</span> <span class="operator">=</span> loginForm.getPhone();</span><br><span class="line">    <span class="keyword">if</span> (RegexUtils.isPhoneInvalid(phone)) &#123;</span><br><span class="line">        <span class="comment">// 2.如果不符合，返回错误信息</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;手机号格式错误！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3.校验验证码</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">cacheCode</span> <span class="operator">=</span> session.getAttribute(<span class="string">&quot;code&quot;</span>);</span><br><span class="line">    <span class="type">String</span> <span class="variable">code</span> <span class="operator">=</span> loginForm.getCode();</span><br><span class="line">    <span class="keyword">if</span>(cacheCode == <span class="literal">null</span> || !cacheCode.toString().equals(code))&#123;</span><br><span class="line">         <span class="comment">//3.不一致，报错</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;验证码错误&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//一致，根据手机号查询用户</span></span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> query().eq(<span class="string">&quot;phone&quot;</span>, phone).one();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//5.判断用户是否存在</span></span><br><span class="line">    <span class="keyword">if</span>(user == <span class="literal">null</span>)&#123;</span><br><span class="line">        <span class="comment">//不存在，则创建</span></span><br><span class="line">        user =  createUserWithPhone(phone);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//7.保存用户信息到session中</span></span><br><span class="line">    session.setAttribute(<span class="string">&quot;user&quot;</span>,user);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① 为什么校验的时候，要将cacheCode.toString()，不能直接比较？</p><ul><li><code>session.getAttribute(&quot;code&quot;)</code> 的返回值是 <code>Object</code> 类型（这是 <code>HttpSession</code> API 的设计），所以变量 <code>cacheCode</code> 被声明为 <code>Object</code>。</li><li><code>loginForm.getCode()</code> 返回的是 <code>String</code> 类型，变量 <code>code</code> 是 <code>String</code>。</li></ul><p>② User user &#x3D; query().eq(“phone”, phone).one(); query()的作用是什么？</p><ul><li>这是 MyBatis-Plus 提供的一个方法，通常在继承了 <code>ServiceImpl</code> 的 Service 层类中使用。</li><li>它的作用是：<strong>创建并返回一个 <code>QueryWrapper</code> 查询条件构造器</strong>。</li></ul><h4 id="实现登录拦截功能"><a href="#实现登录拦截功能" class="headerlink" title="实现登录拦截功能"></a>实现登录拦截功能</h4><p>拦截器代码</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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">       <span class="comment">//1.获取session</span></span><br><span class="line">        <span class="type">HttpSession</span> <span class="variable">session</span> <span class="operator">=</span> request.getSession();</span><br><span class="line">        <span class="comment">//2.获取session中的用户</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">user</span> <span class="operator">=</span> session.getAttribute(<span class="string">&quot;user&quot;</span>);</span><br><span class="line">        <span class="comment">//3.判断用户是否存在</span></span><br><span class="line">        <span class="keyword">if</span>(user == <span class="literal">null</span>)&#123;</span><br><span class="line">              <span class="comment">//4.不存在，拦截，返回401状态码</span></span><br><span class="line">              response.setStatus(<span class="number">401</span>);</span><br><span class="line">              <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//5.存在，保存用户信息到Threadlocal</span></span><br><span class="line">        UserHolder.saveUser((User)user);</span><br><span class="line">        <span class="comment">//6.放行</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>在 <code>LoginInterceptor</code> 的 <code>preHandle</code> 方法中，当判断用户已登录后，会调用 <code>UserHolder.saveUser(...)</code>，将用户信息存入<strong>当前处理请求的线程</strong>的 <code>ThreadLocal</code> 中。</p><p>让拦截器生效</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MvcConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">        <span class="comment">// 登录拦截器</span></span><br><span class="line">        registry.addInterceptor(<span class="keyword">new</span> <span class="title class_">LoginInterceptor</span>())</span><br><span class="line">                .excludePathPatterns(</span><br><span class="line">                        <span class="string">&quot;/shop/**&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;/voucher/**&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;/shop-type/**&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;/upload/**&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;/blog/hot&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;/user/code&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;/user/login&quot;</span></span><br><span class="line">                ).order(<span class="number">1</span>);</span><br><span class="line">        <span class="comment">// token刷新的拦截器</span></span><br><span class="line">        registry.addInterceptor(<span class="keyword">new</span> <span class="title class_">RefreshTokenInterceptor</span>(stringRedisTemplate)).addPathPatterns(<span class="string">&quot;/**&quot;</span>).order(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Redis代替session的业务流程"><a href="#Redis代替session的业务流程" class="headerlink" title="Redis代替session的业务流程"></a>Redis代替session的业务流程</h4><p>思路分析</p><p>当注册完成后，用户去登录会去校验用户提交的手机号和验证码，是否一致。如果一致，则根据手机号查询用户信息，不存在则新建，最后将用户数据保存到redis，并且生成token作为redis的key。当我们校验用户是否登录时，会去携带着token进行访问，从redis中取出token对应的value，判断是否存在这个数据，如果没有则拦截，如果存在则将其保存到threadLocal中，并且放行。</p><img src="https://i-blog.csdnimg.cn/direct/39c883f684cc481f90d53da2f2159e76.png" alt="img" style="zoom: 50%;" /><p>代码实现</p><p><strong>UserServiceImpl代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">login</span><span class="params">(LoginFormDTO loginForm, HttpSession session)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.校验手机号</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">phone</span> <span class="operator">=</span> loginForm.getPhone();</span><br><span class="line">    <span class="keyword">if</span> (RegexUtils.isPhoneInvalid(phone)) &#123;</span><br><span class="line">        <span class="comment">// 2.如果不符合，返回错误信息</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;手机号格式错误！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3.从redis获取验证码并校验</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">cacheCode</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);</span><br><span class="line">    <span class="type">String</span> <span class="variable">code</span> <span class="operator">=</span> loginForm.getCode();</span><br><span class="line">    <span class="keyword">if</span> (cacheCode == <span class="literal">null</span> || !cacheCode.equals(code)) &#123;</span><br><span class="line">        <span class="comment">// 不一致，报错</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;验证码错误&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 4.一致，根据手机号查询用户 select * from tb_user where phone = ?</span></span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> query().eq(<span class="string">&quot;phone&quot;</span>, phone).one();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 5.判断用户是否存在</span></span><br><span class="line">    <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 6.不存在，创建新用户并保存</span></span><br><span class="line">        user = createUserWithPhone(phone);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 7.保存用户信息到 redis中</span></span><br><span class="line">    <span class="comment">// 7.1.随机生成token，作为登录令牌</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> UUID.randomUUID().toString(<span class="literal">true</span>);</span><br><span class="line">    <span class="comment">// 7.2.将User对象转为HashMap存储</span></span><br><span class="line">    <span class="type">UserDTO</span> <span class="variable">userDTO</span> <span class="operator">=</span> BeanUtil.copyProperties(user, UserDTO.class);</span><br><span class="line">    Map&lt;String, Object&gt; userMap = BeanUtil.beanToMap(userDTO, <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(),</span><br><span class="line">            CopyOptions.create()</span><br><span class="line">                    .setIgnoreNullValue(<span class="literal">true</span>)</span><br><span class="line">                    .setFieldValueEditor((fieldName, fieldValue) -&gt; fieldValue.toString()));</span><br><span class="line">    <span class="comment">// 7.3.存储</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">tokenKey</span> <span class="operator">=</span> LOGIN_USER_KEY + token;</span><br><span class="line">    stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);</span><br><span class="line">    <span class="comment">// 7.4.设置token有效期</span></span><br><span class="line">    stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 8.返回token</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(token);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>1). UserDTO userDTO &#x3D; BeanUtil.copyProperties(user, UserDTO.class);</p><ul><li><strong>作用</strong>：把数据库查出来的 <code>User</code> 实体（包含密码等敏感字段），复制为 <code>UserDTO</code>（只保留 id、nickName、icon 等非敏感字段）。</li><li><strong>核心意义</strong>：避免敏感信息（比如密码）泄露到前端 &#x2F; Redis 中，保证数据安全。</li></ul><p>2). Map&lt;String, Object&gt; userMap &#x3D; BeanUtil.beanToMap(userDTO);</p><ul><li><strong>作用</strong>：把 <code>UserDTO</code> 对象转换成 <code>Map</code> 集合（键是字段名，值是字段值）。</li><li><strong>核心意义</strong>：Redis 的 Hash 结构只能接收「键值对」形式的数据，所以必须把对象转成 Map 才能存入。</li></ul><p>3). <code>setFieldValueEditor(...)</code>：强制把所有字段值转成字符串</p><ul><li>Redis 的 Hash 结构有个「死规矩」：<strong>键和值都必须是字符串类型</strong>，不支持 Long、Integer 等数字类型。</li><li>✅ 加了这个配置：<code>fieldValueEditor</code> 是个「编辑器」，它会遍历所有字段值，把不管是 Long、Integer 还是其他类型的值，都强制转成字符串 —— 比如 Long 的 <code>10086</code> 会变成字符串 <code>&quot;10086&quot;</code>，存入 Redis 后完全符合 Redis 的字符串要求，后续取值转换也不会报错。</li></ul><p>4), stringRedisTemplate.expire(…)</p><p><strong>给 Redis 中指定的 Key 设置「自动删除倒计时」，时间一到，Redis 就自动把这个 Key 和它对应的所有数据删掉</strong>。</p><h4 id="登录刷新问题"><a href="#登录刷新问题" class="headerlink" title="登录刷新问题"></a>登录刷新问题</h4><p>思路分析</p><p>之前的拦截器无法对不需要拦截的路径生效，那么我们可以添加一个拦截器，在第一个拦截器中拦截所有的路径，把第二个拦截器做的事情放入到第一个拦截器中，同时刷新令牌，因为第一个拦截器有了threadLocal的数据，所以此时第二个拦截器只需要判断拦截器中的user对象是否存在即可，完成整体刷新功能。</p><img src="https://i-blog.csdnimg.cn/direct/17a11503833e4801a998127d444b002f.png" alt="img" style="zoom: 50%;" /><p>代码实现</p><p>① RefreshTokenInterceptor：做 “数据准备 + Token 续期”（永远放行）</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(...)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 从请求头拿Token（比如前端传的authorization: xxx）</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;authorization&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isBlank(token)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 没Token也放行，交给LoginInterceptor判断</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 2. 去Redis查用户信息（根据TokenKey查Hash）</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> LOGIN_USER_KEY + token;</span><br><span class="line">    Map&lt;Object, Object&gt; userMap = stringRedisTemplate.opsForHash().entries(key);</span><br><span class="line">    <span class="comment">// 3. 没查到用户（Token过期/无效），放行（交给LoginInterceptor拦截）</span></span><br><span class="line">    <span class="keyword">if</span> (userMap.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 4. 查到用户，转成UserDTO存入ThreadLocal（给LoginInterceptor用）</span></span><br><span class="line">    <span class="type">UserDTO</span> <span class="variable">userDTO</span> <span class="operator">=</span> BeanUtil.fillBeanWithMap(userMap, <span class="keyword">new</span> <span class="title class_">UserDTO</span>(), <span class="literal">false</span>);</span><br><span class="line">    UserHolder.saveUser(userDTO);</span><br><span class="line">    <span class="comment">// 5. 关键：刷新Token有效期（用户发请求就续期，避免使用中过期）</span></span><br><span class="line">    stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);</span><br><span class="line">    <span class="comment">// 6. 永远放行（它只做准备，不拦人）</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterCompletion</span><span class="params">(...)</span> &#123;</span><br><span class="line">    UserHolder.removeUser(); <span class="comment">// 请求结束，清空ThreadLocal，防内存泄漏</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② LoginInterceptor：只做 “权限拦截”（只判断，不干活）</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="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(...)</span> &#123;</span><br><span class="line">    <span class="comment">// 只看ThreadLocal里有没有用户（RefreshTokenInterceptor存的）</span></span><br><span class="line">    <span class="keyword">if</span> (UserHolder.getUser() == <span class="literal">null</span>) &#123;</span><br><span class="line">        response.setStatus(<span class="number">401</span>); <span class="comment">// 没用户，返回401</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// 拦截</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 有用户，放行</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="商户查询缓存"><a href="#商户查询缓存" class="headerlink" title="商户查询缓存"></a>商户查询缓存</h3><h4 id="添加商户缓存"><a href="#添加商户缓存" class="headerlink" title="添加商户缓存"></a>添加商户缓存</h4><p>思路分析</p><p>标准的操作方式就是查询数据库之前先查询缓存，如果缓存数据存在，则直接从缓存中返回，如果缓存数据不存在，再查询数据库，然后将数据存入redis。</p><img src="https://i-blog.csdnimg.cn/direct/c984782a64eb4e2e932f68d0acda5d39.png" alt="img" style="zoom:50%;" /><p>代码实现</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;cache:shop:&quot;</span> + id;</span><br><span class="line">    <span class="comment">// 1. 从redis查询商铺缓存</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">shopJson</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="comment">// 2. 判断是否存在</span></span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">        <span class="comment">// 3. 存在，直接返回</span></span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">        <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 4. 不存在，根据id查询数据库</span></span><br><span class="line">    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> getById(id);</span><br><span class="line">    <span class="comment">// 5. 不存在，返回错误</span></span><br><span class="line">    <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺不存在！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 6. 存在，写入redis</span></span><br><span class="line">    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));</span><br><span class="line">    <span class="comment">// 7. 返回</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① <code>@Resource</code> 能成功注入 <code>StringRedisTemplate</code>，核心原因是：<strong>Spring 容器中已经存在一个类型为 <code>StringRedisTemplate</code> 的 Bean，而 <code>@Resource</code> 注解可以根据名称或类型来查找并注入这个 Bean</strong>。Spring Boot 有一套「自动配置」逻辑，当你在项目中引入 <code>spring-boot-starter-data-redis</code> 依赖后，Spring 会自动加载 <code>RedisAutoConfiguration</code> 类（这个类是 Spring 框架自带的，不是你写的），里面会<strong>自动创建 <code>StringRedisTemplate</code> 和 <code>RedisTemplate</code> 两个 Bean</strong>。</p><p>② Shop shop &#x3D; JSONUtil.toBean(shopJson, Shop.class); 为什么要转化类型？</p><ul><li><strong>Redis 的存储限制</strong>：Redis 是一个键值对数据库，它只能存储字符串、数字等基础类型，不能直接存储 Java 对象。所以你在把 <code>Shop</code> 存进 Redis 时，必须先用 <code>JSONUtil.toJsonStr(shop)</code> 把它转成 JSON 字符串（文本）才能存。</li><li><strong>Java 的业务需求</strong>：从 Redis 取出来后，必须把 JSON 字符串<strong>还原成 <code>Shop</code> 对象</strong>，才能在 Java 代码里正常使用。</li></ul><h4 id="缓存更新策略"><a href="#缓存更新策略" class="headerlink" title="缓存更新策略"></a>缓存更新策略</h4><p>三种策略</p><p>**① 内存淘汰：**redis自动进行，当redis内存达到咱们设定的max-memery的时候，会自动触发淘汰机制，淘汰掉一些不重要的数据(可以自己设置策略方式)</p><p>**② 超时剔除：**当我们给redis设置了过期时间ttl之后，redis会将超时的数据进行删除，方便咱们继续使用缓存</p><p>**③ 主动更新：**我们可以手动调用方法把缓存删掉，通常用于解决缓存和数据库不一致问题</p><img src="https://i-blog.csdnimg.cn/direct/75d7871a0ea74a8aacdca64d2b8b1570.png" alt="img" style="zoom: 50%;" /><p>先操作缓存还是先操作数据库？</p><p>答案：应该先操作数据库。</p><p>左边「先删缓存，再更数据库」的并发问题（旧值 10、新值 20）</p><p>① 初始状态</p><ul><li>数据库中值：10（旧值）</li><li>Redis 缓存中值：10（旧值）</li></ul><p>② 并发时序</p><table><thead><tr><th>时间顺序</th><th>线程 1（更新操作：把值改成 20）</th><th>线程 2（查询操作）</th></tr></thead><tbody><tr><td>1</td><td>删除 Redis 缓存（缓存变为空）</td><td>-</td></tr><tr><td>2</td><td>-</td><td>发起查询，缓存未命中</td></tr><tr><td>3</td><td>-</td><td>去数据库读取，拿到旧值 10</td></tr><tr><td>4</td><td>-</td><td>把旧值 10 写入 Redis 缓存</td></tr><tr><td>5</td><td>执行数据库更新，把值改成 20</td><td>-</td></tr></tbody></table><p>③ 最终结果</p><ul><li>数据库值：20（新值）</li><li>Redis 缓存值：10（旧值）</li><li>后续所有查询都会直接读缓存的 10，直到缓存过期 &#x2F; 被删除 —— 这就是<strong>缓存与数据库的数据不一致</strong>，本质是「缓存脏读」。</li></ul><img src="https://i-blog.csdnimg.cn/direct/66cecdc0432d4d92ab7df79b989e7fe4.png" alt="img" style="zoom: 50%;" /><p>实现商铺和缓存与数据库双写一致</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">update</span><span class="params">(Shop shop)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> shop.getId();</span><br><span class="line">    <span class="keyword">if</span> (id == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺id不能为空&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 1. 更新数据库</span></span><br><span class="line">    updateById(shop);</span><br><span class="line">    <span class="comment">// 2. 删除缓存</span></span><br><span class="line">    stringRedisTemplate.delete(CACHE_SHOP_KEY + id);</span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><code>@Transactional</code> 事务注解</p><ul><li><strong>作用</strong>：将整个方法包裹在一个数据库事务中，确保 “更新数据库” 和 “删除缓存” 这两个操作要么全部成功，要么全部失败（回滚）。</li></ul><p>缓存穿透问题的解决思路</p><p>① 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在，这样缓存永远不会生效，这些请求都会打到数据库。</p><p>② 常见的解决方案有两种：</p><p>1). 缓存空对象</p><p>当我们客户端访问不存在的数据时，先请求redis，但是此时redis中没有数据，此时会访问到数据库，但是数据库中也没有数据，这个数据穿透了缓存，直击数据库，我们都知道数据库能够承载的并发不如redis这么高，如果大量的请求同时过来访问这种不存在的数据，这些请求就都会访问到数据库，简单的解决方案就是哪怕这个数据在数据库中也不存在，我们也把这个数据存入到redis中去。<strong>这样，下次用户过来访问这个不存在的数据，那么在 redis 中也能找到这个数据，<strong>就不会进入到</strong>数据库</strong>了。</p><p>2). 布隆过滤</p><p>布隆过滤器其实采用的是哈希思想来解决这个问题，通过一个庞大的二进制数组，走哈希思想去判断当前这个要查询的这个数据是否存在，如果布隆过滤器判断存在，则放行，这个请求会去访问redis，哪怕此时redis中的数据过期了，但是数据库中一定存在这个数据，在数据库中查询出来这个数据后，再将其放入到redis中。</p><p>缺点：不存在一定不存在，存在可能不存在</p><img src="https://i-blog.csdnimg.cn/direct/6ed22a2df1f6443a9a6565665382401b.png" alt="img" style="zoom:50%;" /><p>编码解决商品查询的缓存穿透问题</p><p>重点代码</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (shopJson != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺信息不存在&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① 先明确两个前置条件：</p><ul><li><code>StrUtil.isNotBlank(shopJson)</code>：判断字符串<strong>非空且非空白</strong>（即 <code>shopJson</code> 不是 <code>null</code>、不是 <code>&quot;&quot;</code>、不是全空格）；</li><li>前一行已经判定 <code>StrUtil.isNotBlank(shopJson) == false</code>，说明 <code>shopJson</code> 只有两种可能：<code>null</code> 或 <code>&quot;&quot;</code>（空字符串）。</li></ul><p>② 这行 <code>if (shopJson != null)</code>：说明 <code>shopJson</code> 是 <code>&quot;&quot;</code>（空字符串）—— 这个 <code>&quot;&quot;</code> 是你之前主动存入 Redis 的「空对象标记」（对应代码里 <code>stringRedisTemplate.opsForValue().set(key, &quot;&quot;, ...)</code>）。</p><p>③ 逻辑意义：</p><ul><li>如果 Redis 里存的是 <code>&quot;&quot;</code>，说明<strong>之前已经查过这个 ID 的店铺，数据库里根本没有</strong>；</li><li>此时直接返回 “店铺不存在”，不用再查数据库，避免恶意请求（比如传不存在的 ID）反复穿透到数据库，这就是「缓存空对象防穿透」的核心逻辑。</li></ul><p>缓存雪崩问题及解决思路</p><p>① 缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机，导致大量请求到达数据库，带来巨大压力。</p><p>② 解决方案：</p><ul><li>给不同的Key的TTL添加随机值</li><li>利用Redis集群提高服务的可用性</li><li>给缓存业务添加降级限流策略</li><li>给业务添加多级缓存</li></ul><img src="https://i-blog.csdnimg.cn/direct/68f4a24eab1f4f20ba1440e13a68238a.png" alt="img" style="zoom:50%;" /><p>缓存击穿问题及解决思路</p><p>① 缓存击穿问题也叫热点Key问题，就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了，无数的请求访问会在瞬间给数据库带来巨大的冲击。</p><p>逻辑分析：假设线程1在查询缓存之后，本来应该去查询数据库，然后把这个数据重新加载到缓存的，此时只要线程1走完这个逻辑，其他线程就都能从缓存中加载这些数据了，但是假设在线程1没有走完的时候，后续的线程2，线程3，线程4同时过来访问当前这个方法， 那么这些线程都不能从缓存中查询到数据，那么他们就会同一时刻来访问查询缓存，都没查到，接着同一时间去访问数据库，同时的去执行数据库代码，对数据库访问压力过大。</p><img src="https://i-blog.csdnimg.cn/direct/0415ca1defe54e5cbe4d695f2dd244e9.png" alt="img" style="zoom:50%;" /><p>② 解决方案：</p><p>1). 互斥锁</p><p>假设现在线程1过来访问，它查询缓存没有命中，但是此时它获得到了锁的资源，那么线程1就会一个人去执行逻辑，假设现在线程2过来，线程2在执行过程中，并没有获得到锁，那么线程2就可以进行到休眠，直到线程1把锁释放后，线程2获得到锁，然后再来执行逻辑，此时就能够从缓存中拿到数据了。</p><img src="https://i-blog.csdnimg.cn/direct/c34c0f2a50c5474a898660823f50ece4.png" alt="img" style="zoom: 67%;" /><p>2). 逻辑过期</p><p>我们把过期时间设置在redis的value中，注意：这个过期时间并不会直接作用于redis，而是我们后续通过逻辑去处理。假设线程1去查询缓存，然后从value中判断出来当前的数据已经过期了，此时线程1去获得互斥锁，那么其他线程会进行阻塞，获得了锁的线程他会开启一个线程去进行以前的重构数据的逻辑，直到新开的线程完成这个逻辑后，才释放锁， 而线程1直接进行返回，假设现在线程3过来访问，由于线程线程2持有着锁，所以线程3无法获得锁，线程3也直接返回数据，只有等到新开的线程2把重建数据构建完后，其他线程才能走返回正确的数据。</p><p>这种方案巧妙在于，异步的构建缓存，缺点在于在构建完缓存之前，返回的都是脏数据。</p><p>利用互斥锁解决缓存击穿问题</p><p>① 思路分析</p><p>核心思路就是利用redis的setnx方法来表示获取锁，该方法含义是redis中如果没有这个key，则插入成功，返回1，在stringRedisTemplate中返回true，如果有这个key则插入失败，则返回0，在stringRedisTemplate返回false，我们可以通过true，或者是false，来表示是否有线程成功插入key，成功插入的key的线程我们认为他就是获得到锁的线程。</p><p>② 代码实现</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="keyword">private</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(String key)</span> &#123;</span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">flag</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().setIfAbsent(key, <span class="string">&quot;1&quot;</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">    <span class="keyword">return</span> BooleanUtil.isTrue(flag);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">(String key)</span> &#123;</span><br><span class="line">    stringRedisTemplate.delete(key);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① <code>setIfAbsent(key, &quot;1&quot;, 10, TimeUnit.SECONDS)</code>：Redis 的<code>SETNX</code>命令（不存在则设置），实现分布式锁：</p><ul><li>如果 key 不存在，设置 key&#x3D;1，过期时间 10 秒，返回<code>true</code>（加锁成功）；</li><li>如果 key 已存在，返回<code>false</code>（加锁失败）；</li><li>注意：返回值是<code>Boolean</code>（包装类），<strong>可能为 null</strong>（比如 Redis 连接超时 &#x2F; 异常）；</li></ul><p>② 为什么不用直接 return flag？</p><p>直接 return 可能出现<code>NullPointerException</code>（比如 flag&#x3D;null 时），这个工具类帮你做了空值兜底，保证返回的是 boolean（基本类型），更安全。</p><p>利用逻辑过期解决缓存击穿问题</p><p>① 思路分析</p><p>当用户开始查询redis时，判断是否命中，如果没有命中则直接返回空数据，不查询数据库，而一旦命中后，将value取出，判断value中的过期时间是否满足，如果没有过期，则直接返回redis中的数据，如果过期，则在开启独立线程后直接返回之前的数据，独立线程去重构数据，重构完成后释放互斥锁。</p><img src="https://i-blog.csdnimg.cn/direct/4c55ff18fdc148ea826c62ef90c38e75.png" alt="img" style="zoom:50%;" /><p>② 代码实现</p><p>1). 新建一个实体类</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisData</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> LocalDateTime expireTime;</span><br><span class="line">    <span class="keyword">private</span> Object data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2). ShopServiceImpl</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ExecutorService</span> <span class="variable">CACHE_REBUILD_EXECUTOR</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"><span class="keyword">public</span> Shop <span class="title function_">queryWithLogicalExpire</span><span class="params">( Long id )</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">    <span class="comment">// 1.从redis查询商铺缓存</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="comment">// 2.判断是否存在</span></span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isBlank(json)) &#123;</span><br><span class="line">        <span class="comment">// 3.存在，直接返回</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 4.命中，需要先把json反序列化为对象</span></span><br><span class="line">    <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> JSONUtil.toBean(json, RedisData.class);</span><br><span class="line">    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);</span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">expireTime</span> <span class="operator">=</span> redisData.getExpireTime();</span><br><span class="line">    <span class="comment">// 5.判断是否过期</span></span><br><span class="line">    <span class="keyword">if</span>(expireTime.isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="comment">// 5.1.未过期，直接返回店铺信息</span></span><br><span class="line">        <span class="keyword">return</span> shop;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 5.2.已过期，需要缓存重建</span></span><br><span class="line">    <span class="comment">// 6.缓存重建</span></span><br><span class="line">    <span class="comment">// 6.1.获取互斥锁</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_SHOP_KEY + id;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> tryLock(lockKey);</span><br><span class="line">    <span class="comment">// 6.2.判断是否获取锁成功</span></span><br><span class="line">    <span class="keyword">if</span> (isLock)&#123;</span><br><span class="line">        CACHE_REBUILD_EXECUTOR.submit( ()-&gt;&#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span>&#123;</span><br><span class="line">                <span class="comment">//重建缓存</span></span><br><span class="line">                <span class="built_in">this</span>.saveShop2Redis(id,<span class="number">20L</span>);</span><br><span class="line">            &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                unlock(lockKey);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 6.4.返回过期的商铺信息</span></span><br><span class="line">    <span class="keyword">return</span> shop;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① RedisData redisData &#x3D; JSONUtil.toBean(shopJson, RedisData.class);</p><p><strong>核心前提</strong>：<code>RedisData</code> 是一个<strong>自定义的封装类</strong>（你项目里肯定有这个类），作用是把 “业务数据（Shop）” 和 “逻辑过期时间” 打包存到 Redis 里。</p><p>② JSONObject data &#x3D; (JSONObject) redisData.getData();</p><p><code>redisData.getData()</code> 拿到的是 RedisData 里的 <code>data</code> 字段（类型是 Object），但这个字段实际存的是 Shop 对象的 JSON 格式数据，所以需要强转成 <code>JSONObject</code>（Hutool 工具类的 JSON 对象，也可以理解成 Map）。</p><p>封装Redis工具类</p><p>通用的缓存穿透解决方案</p><p>遵循 “先查缓存 → 缓存无则查数据库 → 数据库无则缓存空值 → 数据库有则缓存数据” 的逻辑，且通过泛型实现了 “通用化”</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;R, ID&gt; R <span class="title function_">queryWithPassThrough</span><span class="params">(String keyPrefix , ID id, Class&lt;R&gt; type, Function&lt;ID, R&gt; dbFallback,</span></span><br><span class="line"><span class="params">                                      Long time, TimeUnit unit)</span> &#123;  <span class="comment">//不太理解</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> keyPrefix + id;</span><br><span class="line"></span><br><span class="line">    <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isNotBlank(json)) &#123;</span><br><span class="line">        <span class="keyword">return</span> JSONUtil.toBean(json, type);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;  <span class="comment">//不太理解</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">R</span> <span class="variable">r</span> <span class="operator">=</span> dbFallback.apply(id);</span><br><span class="line">    <span class="keyword">if</span> (r == <span class="literal">null</span>) &#123;</span><br><span class="line">        stringRedisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, CACHE_NULL_TTL, TimeUnit.MINUTES);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">this</span>.set(key, r, time, unit);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> r;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, <span class="built_in">this</span>::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES); <span class="comment">//不太理解</span></span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>1). 先拆解方法签名</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="comment">// 泛型定义：&lt;R, ID&gt; （和上一版一致）</span></span><br><span class="line"><span class="comment">// R = 返回值类型（如Shop、User、Goods），ID = 主键类型（如Long、Integer）</span></span><br><span class="line"><span class="keyword">public</span> &lt;R, ID&gt; R <span class="title function_">queryWithPassThrough</span><span class="params">(</span></span><br><span class="line"><span class="params">    String keyPrefix,       // 缓存key前缀（如<span class="string">&quot;cache:shop:&quot;</span>、<span class="string">&quot;cache:goods:&quot;</span>）</span></span><br><span class="line"><span class="params">    ID id,                  // 要查询的主键（如店铺id=<span class="number">1</span>、商品id=<span class="number">2</span>）</span></span><br><span class="line"><span class="params">    Class&lt;R&gt; type,          // 返回值的类类型（如Shop.class、Goods.class）</span></span><br><span class="line"><span class="params">    Function&lt;ID, R&gt; dbFallback,  // 数据库查询回调（传入id，返回业务数据）</span></span><br><span class="line"><span class="params">    Long time,              // 新增：缓存有效数据的过期时间（数值）</span></span><br><span class="line"><span class="params">    TimeUnit unit           // 新增：过期时间的单位（如分钟、小时，和time配合）</span></span><br><span class="line"><span class="params">)</span></span><br></pre></td></tr></table></figure><ul><li><code>R</code>：代表<strong>返回值类型</strong>（可以是 Shop、User、Goods 等任意业务类）；</li><li><code>ID</code>：代表<strong>主键类型</strong>（可以是 Long、Integer、String 等）；</li></ul><p>2). this::getById</p><p><strong>语法含义</strong>：等价于一个 Lambda 表达式 <code>(Long id) -&gt; this.getById(id)</code>，本质是 “把查询数据库的方法作为参数传给通用方法”；</p><p>通用化、适配所有业务的缓存击穿解决方案（逻辑过期策略）</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 拼接通用缓存key（比如keyPrefix=cache:shop:，id=1 → key=cache:shop:1）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> keyPrefix + id;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 查Redis缓存：逻辑过期的前提是缓存里有数据（只是过期），如果缓存为空，直接返回null</span></span><br><span class="line"><span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (StrUtil.isBlank(json)) &#123; <span class="comment">// 缓存为空（没存过/被删了），不是热点key，直接返回</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 反序列化：把Redis里的JSON转成封装了“数据+过期时间”的RedisData对象（核心！）</span></span><br><span class="line"><span class="comment">// Redis里存的不是单纯的Shop/User，而是RedisData的JSON（包含业务数据+逻辑过期时间）</span></span><br><span class="line"><span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> JSONUtil.toBean(json, RedisData.class);</span><br><span class="line"><span class="comment">// 从RedisData中拆解出业务数据（比如Shop的JSON），转成JSONObject</span></span><br><span class="line"><span class="type">JSONObject</span> <span class="variable">data</span> <span class="operator">=</span> (JSONObject) redisData.getData();</span><br><span class="line"><span class="comment">// 把JSONObject转成泛型R（比如Shop、User）</span></span><br><span class="line"><span class="type">R</span> <span class="variable">r</span> <span class="operator">=</span> JSONUtil.toBean(data, type);</span><br><span class="line"><span class="comment">// 从RedisData中拆解出逻辑过期时间（不是Redis的TTL）</span></span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">expireTime</span> <span class="operator">=</span> redisData.getExpireTime();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. 判断是否逻辑过期：没过期→直接返回缓存数据，不走数据库</span></span><br><span class="line"><span class="keyword">if</span> (expireTime.isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">    <span class="keyword">return</span> r;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5. 已过期→尝试获取分布式锁（保证只有1个线程更新缓存）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_SHOP_KEY + id; <span class="comment">// 锁key（比如lock:shop:1）</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> tryLock(lockKey);   <span class="comment">// 加锁（setIfAbsent）</span></span><br><span class="line"><span class="keyword">if</span> (isLock) &#123; <span class="comment">// 只有1个线程能拿到锁</span></span><br><span class="line">    <span class="comment">// 6. 异步重建缓存：用线程池执行，不阻塞当前请求</span></span><br><span class="line">    CACHE_REBUILD_EXECUTOR.submit(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 6.1 查数据库（调用传入的dbFallback，比如this::getById）</span></span><br><span class="line">            <span class="type">R</span> <span class="variable">r1</span> <span class="operator">=</span> dbFallback.apply(id);</span><br><span class="line">            <span class="comment">// 6.2 把新数据封装成RedisData，存入Redis（设置新的逻辑过期时间）</span></span><br><span class="line">            <span class="built_in">this</span>.setWithLogicalExpire(key, r1, time, unit);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;重建缓存失败&quot;</span>, e); <span class="comment">// 异常只打日志，不影响主线程</span></span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            unlock(lockKey); <span class="comment">// 最终释放锁，避免死锁</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 7. 不管有没有拿到锁，都返回旧数据（核心！解决缓存击穿的关键）</span></span><br><span class="line"><span class="keyword">return</span> r;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><strong>1). <code>dbFallback.apply(id)</code> 是什么？</strong></p><p><code>dbFallback</code>是你调用方法时传入的 “数据库查询逻辑”，比如：</p><figure class="highlight java"><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">// 调用时传入this::getById（根据id查店铺）</span></span><br><span class="line">cacheClient.queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, <span class="built_in">this</span>::getById, <span class="number">20L</span>, TimeUnit.MINUTES);</span><br></pre></td></tr></table></figure><p><code>dbFallback.apply(id)</code> 就是执行<code>this.getById(id)</code>，查数据库获取最新的店铺数据 —— 泛型 + 函数式接口让这个方法适配所有业务的数据库查询。</p><h3 id="优惠券秒杀"><a href="#优惠券秒杀" class="headerlink" title="优惠券秒杀"></a>优惠券秒杀</h3><h4 id="全局唯一ID"><a href="#全局唯一ID" class="headerlink" title="全局唯一ID"></a>全局唯一ID</h4><p>为了增加ID的安全性，我们不直接使用Redis自增的数值，而是拼接一些其它信息：</p><p><img src="https://i-blog.csdnimg.cn/direct/15fcbfff7fdf483391d5c58a62274222.png" alt="img"></p><p>ID的组成部分：符号位：1bit，永远为0</p><p>时间戳：31bit，以秒为单位，可以使用69年</p><p>序列号：32bit，秒内的计数器，支持每秒产生2^32个不同ID</p><h4 id="Redis实现全局唯一Id"><a href="#Redis实现全局唯一Id" class="headerlink" title="Redis实现全局唯一Id"></a>Redis实现全局唯一Id</h4><p>RedisIdWorker</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisIdWorker</span> &#123;</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="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">BEGIN_TIMESTAMP</span> <span class="operator">=</span> <span class="number">1640995200L</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="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">COUNT_BITS</span> <span class="operator">=</span> <span class="number">32</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RedisIdWorker</span><span class="params">(StringRedisTemplate stringRedisTemplate)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.stringRedisTemplate = stringRedisTemplate;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">nextId</span><span class="params">(String keyPrefix)</span> &#123;</span><br><span class="line">        <span class="comment">// 1.生成时间戳</span></span><br><span class="line">        <span class="type">LocalDateTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line">        <span class="type">long</span> <span class="variable">nowSecond</span> <span class="operator">=</span> now.toEpochSecond(ZoneOffset.UTC);</span><br><span class="line">        <span class="type">long</span> <span class="variable">timestamp</span> <span class="operator">=</span> nowSecond - BEGIN_TIMESTAMP;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2.生成序列号</span></span><br><span class="line">        <span class="comment">// 2.1.获取当前日期，精确到天</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">date</span> <span class="operator">=</span> now.format(DateTimeFormatter.ofPattern(<span class="string">&quot;yyyy:MM:dd&quot;</span>));</span><br><span class="line">        <span class="comment">// 2.2.自增长</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">count</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().increment(<span class="string">&quot;icr:&quot;</span> + keyPrefix + <span class="string">&quot;:&quot;</span> + date);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3.拼接并返回</span></span><br><span class="line">        <span class="keyword">return</span> timestamp &lt;&lt; COUNT_BITS | count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① String date &#x3D; now.format(DateTimeFormatter.ofPattern(“yyyy:MM:dd”));</p><ul><li>按<strong>日期拆分 Redis 计数器的 Key</strong>，避免单个 Key 的计数器值无限增大（比如累计到几十亿，既占内存又不方便管理）；</li><li>让每个日期的计数器从<code>1</code>开始，减少 ID 的长度（如果全局一个计数器，数值会快速变大）；</li><li>方便按日期统计 &#x2F; 清理数据（比如删除 30 天前的计数器 Key）。</li></ul><p>举个例子：订单业务的 Key，2026-03-11 是<code>icr:order:2026:03:11</code>，2026-03-12 就变成<code>icr:order:2026:03:12</code>，每天的计数器独立。</p><p>② return timestamp &lt;&lt; COUNT_BITS | count.longValue();</p><p>这是<strong>核心的位运算逻辑</strong>，目的是把「相对时间戳」和「每日计数器」拼接成一个完整的 ID。</p><ul><li><code>timestamp &lt;&lt; COUNT_BITS</code>：把<code>timestamp</code>（相对时间戳）<strong>左移 32 位</strong>，放到 64 位<code>long</code>的高位区域；比如<code>timestamp=100</code>，左移 32 位后变成<code>100 * 2^32</code>（二进制就是 100 后面跟 32 个 0）；</li><li><code>count.longValue()</code>：把 Redis 返回的计数器值转为<code>long</code>（因为返回的是<code>Long</code>包装类）；</li><li><code>|</code>（按位或）：把高位的时间戳和低位的计数器拼接成一个数（因为两部分的二进制位不重叠，按位或等价于拼接）。</li></ul><p>测试类</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">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testIdWorker</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="type">CountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">300</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">Runnable</span> <span class="variable">task</span> <span class="operator">=</span> () -&gt; &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">            <span class="type">long</span> <span class="variable">id</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;id = &quot;</span> + id);</span><br><span class="line">        &#125;</span><br><span class="line">        latch.countDown();</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="type">long</span> <span class="variable">begin</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">300</span>; i++) &#123;</span><br><span class="line">        es.submit(task);</span><br><span class="line">    &#125;</span><br><span class="line">    latch.await();</span><br><span class="line">    <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">&quot;time = &quot;</span> + (end - begin));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>CountDownLatch 的工作原理：</p><ul><li>你（主线程）要统计 300 个工人（子线程）完成 “每人生产 100 个零件（生成 100 个 ID）” 的总耗时；</li><li>你先给工人发了一个 “倒计时牌”（CountDownLatch (300)），初始数字是 300；</li><li>每个工人完成自己的 100 个零件后，就把倒计时牌数字减 1（<code>latch.countDown()</code>）；</li><li>你站在旁边等（<code>latch.await()</code>），直到倒计时牌数字变成 0（所有工人都完成），才开始计算从开工到收工的总时间；</li><li>如果没有这个倒计时牌，你可能刚安排完工人就看表，此时工人还在干活，统计的时间毫无意义。</li></ul><h4 id="实现秒杀下单"><a href="#实现秒杀下单" class="headerlink" title="实现秒杀下单"></a>实现秒杀下单</h4><p>思路分析</p><ul><li>秒杀是否开始或结束，如果尚未开始或已经结束则无法下单</li><li>库存是否充足，不足则无法下单</li></ul><p>① 当用户开始进行下单，我们应当去查询优惠卷信息，查询到优惠卷信息，判断是否满足秒杀条件。</p><p>② 比如时间是否充足，如果时间充足，则进一步判断库存是否足够，如果两者都满足，则扣减库存，创建订单，然后返回订单id，如果有一个条件不满足则直接结束。</p><img src="https://i-blog.csdnimg.cn/direct/34b96b6c992e42a9b3321b5df2f08f96.png" alt="img" style="zoom:67%;" /><p>代码实现</p><p><strong>VoucherOrderServiceImpl</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.查询优惠券</span></span><br><span class="line">    <span class="type">SeckillVoucher</span> <span class="variable">voucher</span> <span class="operator">=</span> seckillVoucherService.getById(voucherId);</span><br><span class="line">    <span class="comment">// 2.判断秒杀是否开始</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getBeginTime().isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="comment">// 尚未开始</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀尚未开始！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3.判断秒杀是否已经结束</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getEndTime().isBefore(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="comment">// 尚未开始</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀已经结束！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 4.判断库存是否充足</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getStock() &lt; <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="comment">// 库存不足</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//5，扣减库存</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">            .setSql(<span class="string">&quot;stock= stock -1&quot;</span>)</span><br><span class="line">            .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).update();</span><br><span class="line">    <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">        <span class="comment">//扣减库存</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//6.创建订单</span></span><br><span class="line">    <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">    <span class="comment">// 6.1.订单id</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">    voucherOrder.setId(orderId);</span><br><span class="line">    <span class="comment">// 6.2.用户id</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    voucherOrder.setUserId(userId);</span><br><span class="line">    <span class="comment">// 6.3.代金券id</span></span><br><span class="line">    voucherOrder.setVoucherId(voucherId);</span><br><span class="line">    save(voucherOrder);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① Resource private ISeckillVoucherService seckillVoucherService;</p><p><strong>核心解释</strong>：这是 Spring 的<strong>依赖注入</strong>，目的是引入操作「秒杀优惠券表（SeckillVoucher）」的服务类。</p><ul><li><code>ISeckillVoucherService</code>：MyBatis-Plus 自动生成的服务接口，专门用于操作<code>SeckillVoucher</code>（秒杀优惠券）表，包含查询、修改、删除等方法；</li><li><strong>为什么需要它</strong>：秒杀逻辑的第一步是查询优惠券的秒杀时间、库存等信息（这些存在<code>SeckillVoucher</code>表），所以必须注入这个服务来操作该表。</li></ul><p>② 扣减库存的 update 链式调用</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></pre></td><td class="code"><pre><span class="line">seckillVoucherService.update()</span><br><span class="line">        .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>)</span><br><span class="line">        .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId)</span><br><span class="line">        .update();</span><br></pre></td></tr></table></figure><p>这是<strong>MyBatis-Plus 的链式更新语法</strong>，目的是扣减秒杀优惠券的库存，拆解每一步：</p><table><thead><tr><th>代码片段</th><th>作用</th></tr></thead><tbody><tr><td><code>seckillVoucherService.update()</code></td><td>初始化 MyBatis-Plus 的更新构造器（UpdateWrapper）</td></tr><tr><td><code>.setSql(&quot;stock = stock - 1&quot;)</code></td><td>设置要执行的 SQL 片段：将库存字段减 1（直接写 SQL 片段，而非 set (“stock”, 具体值)，避免多线程下覆盖）</td></tr><tr><td><code>.eq(&quot;voucher_id&quot;, voucherId)</code></td><td>添加更新条件：只更新<code>voucher_id</code>等于传入的优惠券 ID 的记录</td></tr><tr><td><code>.update()</code></td><td>执行最终的更新操作，对应 SQL：<code>UPDATE seckill_voucher SET stock = stock - 1 WHERE voucher_id = ?</code></td></tr></tbody></table><h4 id="库存超卖问题分析"><a href="#库存超卖问题分析" class="headerlink" title="库存超卖问题分析"></a>库存超卖问题分析</h4><p>存在的问题</p><p>假设线程1过来查询库存，判断出来库存大于1，正准备去扣减库存，但是还没有来得及去扣减，此时线程2过来，线程2也去查询库存，发现这个数量一定也大于1，那么这两个线程都会去扣减库存，最终多个线程相当于一起去扣减库存，此时就会出现库存的超卖问题。</p><img src="https://i-blog.csdnimg.cn/direct/805226df173f4d9689e90499f4b21479.png" alt="img" style="zoom: 50%;" /><p>解决方案（我们采用乐观锁）</p><img src="https://i-blog.csdnimg.cn/direct/3fe97320577d4fc099380950e290e6c3.png" alt="img" style="zoom: 67%;" /><p>① 乐观锁：会有一个版本号，每次操作数据会对版本号+1，再提交回数据时，会去校验是否比之前的版本大1 ，如果大1 ，则进行操作成功。这套机制的核心逻辑在于：如果在操作过程中，版本号只比原来大1 ，那么就意味着操作过程中没有人对它进行过修改，它的操作就是安全的，如果不大1，则数据被修改过，乐观锁还有一些变种的处理方式比如cas</p><p>② 代码实现</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></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">        .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>)</span><br><span class="line">        .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId)</span><br><span class="line">        .gt(<span class="string">&quot;stock&quot;</span>,voucher.getStock())</span><br><span class="line">        .update(); </span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><code>.gt(&quot;stock&quot;, voucher.getStock())</code> ✅ 核心防超卖逻辑</p><ul><li><code>gt</code> 是 <code>greater than</code> 的缩写，意为<strong>大于</strong>。</li><li>这是<strong>乐观锁 &#x2F; 库存校验的关键</strong>：要求<strong>数据库中当前的库存值</strong>，必须<strong>大于</strong>代码中拿到的<strong>预期库存</strong>（<code>voucher.getStock()</code>）。</li><li>为什么能防超卖？多线程并发请求时，只有当数据库库存确实大于预期值时，才会执行扣减；如果某线程执行时库存已不足，该条件会直接失败，避免 <code>stock</code> 变成负数。</li></ul><h4 id="一人一单"><a href="#一人一单" class="headerlink" title="一人一单"></a>一人一单</h4><p>版本1.0</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> Result <span class="title function_">createVoucherOrder</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">         <span class="comment">// 5.1.查询订单</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).count();</span><br><span class="line">        <span class="comment">// 5.2.判断是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 用户已经购买过了</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;用户已经购买过一次！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6.扣减库存</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">                .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>) <span class="comment">// set stock = stock - 1</span></span><br><span class="line">                .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).gt(<span class="string">&quot;stock&quot;</span>, <span class="number">0</span>) <span class="comment">// where id = ? and stock &gt; 0</span></span><br><span class="line">                .update();</span><br><span class="line">        <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">            <span class="comment">// 扣减失败</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7.创建订单</span></span><br><span class="line">        <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">        <span class="comment">// 7.1.订单id</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">        voucherOrder.setId(orderId);</span><br><span class="line">        <span class="comment">// 7.2.用户id</span></span><br><span class="line">        voucherOrder.setUserId(userId);</span><br><span class="line">        <span class="comment">// 7.3.代金券id</span></span><br><span class="line">        voucherOrder.setVoucherId(voucherId);</span><br><span class="line">        save(voucherOrder);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7.返回订单id</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><code>synchronized</code> 修饰整个方法 → 全局串行，性能崩溃</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">public</span> <span class="keyword">synchronized</span> Result <span class="title function_">createVoucherOrder</span><span class="params">(Long voucherId)</span> &#123;</span><br></pre></td></tr></table></figure><p>❌ <strong>所有用户的秒杀请求都要排队执行</strong>（比如 1000 个用户下单，只能一个接一个处理）；</p><p>❌ 本来锁的目标是 “一人一单”（只锁单个用户），结果锁了所有用户，完全违背并发设计初衷。</p><p>版本2.0</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">@Transactional</span></span><br><span class="line"><span class="keyword">public</span>  Result <span class="title function_">createVoucherOrder</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line"><span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line"><span class="keyword">synchronized</span>(userId.toString().intern())&#123;</span><br><span class="line">         <span class="comment">// 5.1.查询订单</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).count();</span><br><span class="line">        <span class="comment">// 5.2.判断是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 用户已经购买过了</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;用户已经购买过一次！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6.扣减库存</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">                .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>) <span class="comment">// set stock = stock - 1</span></span><br><span class="line">                .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).gt(<span class="string">&quot;stock&quot;</span>, <span class="number">0</span>) <span class="comment">// where id = ? and stock &gt; 0</span></span><br><span class="line">                .update();</span><br><span class="line">        <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">            <span class="comment">// 扣减失败</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7.创建订单</span></span><br><span class="line">        <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">        <span class="comment">// 7.1.订单id</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">        voucherOrder.setId(orderId);</span><br><span class="line">        <span class="comment">// 7.2.用户id</span></span><br><span class="line">        voucherOrder.setUserId(userId);</span><br><span class="line">        <span class="comment">// 7.3.代金券id</span></span><br><span class="line">        voucherOrder.setVoucherId(voucherId);</span><br><span class="line">        save(voucherOrder);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7.返回订单id</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>锁在事务内 → 一人一单规则仍失效，<strong>锁的释放时机早于事务提交时机</strong>，导致 “一人一单” 规则被突破。</p><p>① <code>@Transactional</code> 注解的方法，其事务提交时机是「整个方法执行完毕、返回结果后」，而 <code>synchronized</code> 锁的释放时机是「代码块执行完毕」。</p><p>② 真实执行时序（以同一个用户的两个并发请求为例）：</p><figure class="highlight bash"><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">线程A（用户1）执行步骤：</span><br><span class="line">1. 获取用户1的锁 → 2. 查订单（count=0）→ 3. 扣库存 → 4. 创订单 → 5. 释放锁（synchronized代码块执行完）→ 6. Spring提交事务（订单真正入库）</span><br><span class="line"></span><br><span class="line">线程B（用户1，线程A释放锁后立即执行）：</span><br><span class="line">1. 获取用户1的锁 → 2. 查订单（此时线程A的事务还没提交，数据库中无订单，count=0）→ 3. 重复下单 → 4. 释放锁 → 5. Spring提交事务</span><br></pre></td></tr></table></figure><p>最终结果：用户 1 会创建两个订单，“一人一单” 的核心规则完全失效。</p><p>版本3.0</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">synchronized</span> (userId.toString().intern()) &#123;</span><br><span class="line">    <span class="comment">// 1. 获取 Spring 的代理对象</span></span><br><span class="line">    <span class="type">IVoucherOrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> (IVoucherOrderService) AopContext.currentProxy();</span><br><span class="line">    <span class="comment">// 2. 通过代理对象调用</span></span><br><span class="line">    <span class="keyword">return</span> proxy.createVoucherOrder(voucherId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li><strong>保证事务生效</strong>：通过 <code>proxy</code> 调用，Spring 代理对象会拦截方法，开启事务、提交 &#x2F; 回滚，<strong>数据一致性</strong>得到保证。</li><li><strong>保证一人一单</strong>：锁还在外层，释放锁前事务一定提交，<strong>并发安全性</strong>得到保证。</li></ul><h4 id="集群环境下的并发问题"><a href="#集群环境下的并发问题" class="headerlink" title="集群环境下的并发问题"></a>集群环境下的并发问题</h4><p>同一个用户（比如用户 100），同时发送请求到 <strong>服务器 A</strong> 和 <strong>服务器 B</strong>。</p><ul><li><strong>服务器 A</strong> 拿到锁，正在查库存 → 库存充足 → 准备扣减。</li><li><strong>服务器 B</strong> 拿到锁，正在查库存 → 库存充足 → 准备扣减。</li><li><strong>原因</strong>：服务器 A 的锁只认识服务器 A 里的线程，它根本不知道服务器 B 里还有个线程在抢同一个资源。</li><li><strong>结果</strong>：<strong>两个服务器同时执行了扣减逻辑</strong>，导致了 <strong>超卖</strong>，且同一个用户买了 <strong>两份订单</strong>（一人多单）。</li></ul><h3 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h3><h4 id="Redis-实现分布式锁版统一"><a href="#Redis-实现分布式锁版统一" class="headerlink" title="Redis 实现分布式锁版统一"></a>Redis 实现分布式锁版统一</h4><p><strong>SimpleRedisLock</strong></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="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_PREFIX=<span class="string">&quot;lock:&quot;</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> timeoutSec)</span> &#123;</span><br><span class="line">    <span class="comment">// 获取线程标示</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">threadId</span> <span class="operator">=</span> Thread.currentThread().getId()</span><br><span class="line">    <span class="comment">// 获取锁</span></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">success</span> <span class="operator">=</span> stringRedisTemplate.opsForValue()</span><br><span class="line">            .setIfAbsent(KEY_PREFIX + name, threadId + <span class="string">&quot;&quot;</span>, timeoutSec, TimeUnit.SECONDS);</span><br><span class="line">    <span class="keyword">return</span> Boolean.TRUE.equals(success);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① .setIfAbsent(KEY_PREFIX + name, threadId + “”, timeoutSec, TimeUnit.SECONDS);</p><p>1). <code>setIfAbsent</code> 对应 Redis 的 <code>SETNX</code> 命令，核心逻辑是：<strong>如果 key 不存在，则设置值并返回 true；如果 key 已存在，则直接返回 false</strong>—— 这是实现分布式锁 “互斥性” 的核心（保证同一时间只有一个线程能拿到锁）。</p><p>2). <code>threadId + &quot;&quot;</code> 的小细节，<code>Thread.currentThread().getId()</code> 返回的是 <strong><code>long</code> 原始类型</strong>，而 Redis 操作的 value 必须是 <code>String</code> 类型。拼接空字符串是最简洁的语法糖，能将 <code>long</code> 强制转为 <code>String</code>，满足 <code>setIfAbsent</code> 的参数类型要求。<em>等价效果：<code>String.valueOf(threadId)</code>，但写法更简洁。</em></p><p>② 为什么自动拆箱有风险？<code>Boolean.TRUE.equals(success)</code> 好在哪？</p><p>1). 代码中 <code>Boolean success = ...</code> 定义的是<strong>包装类 Boolean 对象</strong>，如果直接写 <code>return success;</code>，会触发 <strong>自动拆箱</strong>（将 <code>Boolean</code> 转为 <code>boolean</code> 原始类型）。</p><ul><li>当 Redis 操作异常 &#x2F; 返回 <code>null</code> 时，<code>success</code> 会是 <strong><code>null</code></strong>；</li><li>此时拆箱过程会尝试把 <code>null</code> 转为 <code>boolean</code>，直接抛出 <strong><code>NullPointerException</code></strong>，导致程序崩溃。</li></ul><p>2). <code>Boolean.TRUE.equals(success)</code> 的核心优势</p><ul><li><code>Boolean.TRUE</code> 是 Boolean 类的单例常量，调用其 <code>equals()</code> 方法时，内部自带 <strong>null 校验逻辑</strong>；</li><li>无论 <code>success</code> 是 <code>null</code>、<code>Boolean.FALSE</code> 还是 <code>Boolean.TRUE</code>，都能安全返回结果：</li></ul><table><thead><tr><th><code>success</code> 取值</th><th><code>return success;</code>（直接拆箱）</th><th><code>return Boolean.TRUE.equals(success);</code></th></tr></thead><tbody><tr><td><code>null</code></td><td>抛 <code>NullPointerException</code></td><td>返回 <code>false</code></td></tr><tr><td><code>Boolean.FALSE</code></td><td>抛 <code>NullPointerException</code></td><td>返回 <code>false</code></td></tr><tr><td><code>Boolean.TRUE</code></td><td>正常返回 <code>true</code></td><td>返回 <code>true</code></td></tr></tbody></table><p>修改业务代码</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">@Override</span></span><br><span class="line">  <span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">      <span class="comment">// 1.查询优惠券</span></span><br><span class="line">      <span class="type">SeckillVoucher</span> <span class="variable">voucher</span> <span class="operator">=</span> seckillVoucherService.getById(voucherId);</span><br><span class="line">      <span class="comment">// 2.判断秒杀是否开始</span></span><br><span class="line">      <span class="keyword">if</span> (voucher.getBeginTime().isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">          <span class="comment">// 尚未开始</span></span><br><span class="line">          <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀尚未开始！&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 3.判断秒杀是否已经结束</span></span><br><span class="line">      <span class="keyword">if</span> (voucher.getEndTime().isBefore(LocalDateTime.now())) &#123;</span><br><span class="line">          <span class="comment">// 尚未开始</span></span><br><span class="line">          <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀已经结束！&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 4.判断库存是否充足</span></span><br><span class="line">      <span class="keyword">if</span> (voucher.getStock() &lt; <span class="number">1</span>) &#123;</span><br><span class="line">          <span class="comment">// 库存不足</span></span><br><span class="line">          <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">      <span class="comment">//创建锁对象(新增代码)</span></span><br><span class="line">      <span class="type">SimpleRedisLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleRedisLock</span>(<span class="string">&quot;order:&quot;</span> + userId, stringRedisTemplate);</span><br><span class="line">      <span class="comment">//获取锁对象</span></span><br><span class="line">      <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> lock.tryLock(<span class="number">1200</span>);</span><br><span class="line"><span class="comment">//加锁失败</span></span><br><span class="line">      <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">          <span class="keyword">return</span> Result.fail(<span class="string">&quot;不允许重复下单&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">          <span class="comment">//获取代理对象(事务)</span></span><br><span class="line">          <span class="type">IVoucherOrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> (IVoucherOrderService) AopContext.currentProxy();</span><br><span class="line">          <span class="keyword">return</span> proxy.createVoucherOrder(voucherId);</span><br><span class="line">      &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">          <span class="comment">//释放锁</span></span><br><span class="line">          lock.unlock();</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h4 id="Redis分布式锁误删情况说明"><a href="#Redis分布式锁误删情况说明" class="headerlink" title="Redis分布式锁误删情况说明"></a>Redis分布式锁误删情况说明</h4><p>存在的问题</p><ul><li><strong>线程 1</strong>成功获取锁，开始执行业务</li><li>线程 1 遇到<strong>阻塞</strong>（比如 JVM GC 垃圾回收、CPU 繁忙），导致业务执行超时 → Redis 锁<strong>自动过期释放</strong>。</li><li><strong>线程 2</strong>刚好来抢锁，<strong>成功获取锁</strong>，开始执行自己的业务。</li><li>线程 1<strong>恢复执行</strong>，业务做完了，它不知道锁已经过期被线程 2 拿走了，直接执行「删除锁」逻辑 → 误删线程 2 的锁。</li></ul><img src="https://i-blog.csdnimg.cn/direct/5a3bfdfb12d4427cbe4967a0fcef2b05.png" alt="img" style="zoom:50%;" /><p>解决方案</p><p>① 核心逻辑：在存入锁时，放入自己线程的标识。在删除锁时，判断当前这把锁的标识是不是自己存入的，如果是，则进行删除，如果不是，则不进行删除。</p><p>② 代码实现</p><p>加锁</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="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ID_PREFIX</span> <span class="operator">=</span> UUID.randomUUID().toString(<span class="literal">true</span>) + <span class="string">&quot;-&quot;</span>;</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> timeoutSec)</span> &#123;</span><br><span class="line">   <span class="comment">// 获取线程标示</span></span><br><span class="line">   <span class="type">String</span> <span class="variable">threadId</span> <span class="operator">=</span> ID_PREFIX + Thread.currentThread().getId();</span><br><span class="line">   <span class="comment">// 获取锁</span></span><br><span class="line">   <span class="type">Boolean</span> <span class="variable">success</span> <span class="operator">=</span> stringRedisTemplate.opsForValue()</span><br><span class="line">                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);</span><br><span class="line">   <span class="keyword">return</span> Boolean.TRUE.equals(success);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>释放锁</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 获取线程标示</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">threadId</span> <span class="operator">=</span> ID_PREFIX + Thread.currentThread().getId();</span><br><span class="line">    <span class="comment">// 获取锁中的标示</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">id</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);</span><br><span class="line">    <span class="comment">// 判断标示是否一致</span></span><br><span class="line">    <span class="keyword">if</span>(threadId.equals(id)) &#123;</span><br><span class="line">        <span class="comment">// 释放锁</span></span><br><span class="line">        stringRedisTemplate.delete(KEY_PREFIX + name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="分布式锁的原子性问题"><a href="#分布式锁的原子性问题" class="headerlink" title="分布式锁的原子性问题"></a>分布式锁的原子性问题</h4><p>我们结合线程 1、线程 2 的动作，一步步拆解：</p><table><thead><tr><th>时间节点</th><th>线程 1 动作</th><th>线程 2 动作</th><th>Redis 锁状态</th><th>关键变化</th></tr></thead><tbody><tr><td>1</td><td>持有锁（value &#x3D; 线程 1ID），执行业务</td><td>阻塞等待锁</td><td>存在（属于线程 1）</td><td>线程 1 正常拿锁</td></tr><tr><td>2</td><td>执行到解锁步骤，先<code>get</code>锁，发现 value 是自己的 ID，进入<code>if</code>判断</td><td>等待</td><td>存在（属于线程 1）</td><td>线程 1 完成 “验证”，还没执行 delete</td></tr><tr><td>3</td><td><strong>线程 1 突然卡顿 &#x2F; GC</strong>（CPU 切换）</td><td>无</td><td><strong>锁过期自动释放</strong></td><td>致命点！锁因为超时被 Redis 回收，线程 1 还没删</td></tr><tr><td>4</td><td>卡顿中</td><td>尝试抢锁，成功获取锁（value &#x3D; 线程 2ID），开始执行业务</td><td>存在（属于线程 2）</td><td>线程 2 拿到了锁，业务正在跑</td></tr><tr><td>5</td><td>线程 1 恢复执行</td><td>执行业务中</td><td>存在（属于线程 2）</td><td>线程 1 从<code>if</code>里继续往下走，直接执行<code>delete</code></td></tr><tr><td>6</td><td>完成删除</td><td>业务中</td><td>被删空</td><td>线程 1 把线程 2 的锁删了！线程 2 的业务还没做完，锁没了</td></tr></tbody></table><p><strong>核心后果</strong>：线程 1 明明做了 “验证归属” 的判断，却依然误删了线程 2 的锁 —— 因为<strong>验证和删除中间被打断了。</strong></p><h4 id="利用Java代码调用Lua脚本改造分布式锁"><a href="#利用Java代码调用Lua脚本改造分布式锁" class="headerlink" title="利用Java代码调用Lua脚本改造分布式锁"></a>利用Java代码调用Lua脚本改造分布式锁</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> DefaultRedisScript&lt;Long&gt; UNLOCK_SCRIPT;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        UNLOCK_SCRIPT = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;();</span><br><span class="line">        UNLOCK_SCRIPT.setLocation(<span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;unlock.lua&quot;</span>));</span><br><span class="line">        UNLOCK_SCRIPT.setResultType(Long.class);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 调用lua脚本</span></span><br><span class="line">    stringRedisTemplate.execute(</span><br><span class="line">            UNLOCK_SCRIPT,</span><br><span class="line">            Collections.singletonList(KEY_PREFIX + name),</span><br><span class="line">            ID_PREFIX + Thread.currentThread().getId());</span><br><span class="line">&#125;</span><br><span class="line">经过以上代码改造后，我们就能够实现 拿锁比锁删锁的原子性动作了~</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><strong>① 静态代码块：加载 Lua 脚本</strong></p><ul><li><code>static final</code>：脚本只需要加载一次，类加载时初始化，高效复用</li><li><code>DefaultRedisScript</code>：Spring Data Redis 提供的封装类，用来和 Redis 模板配合执行 Lua 脚本</li><li>脚本位置：<code>unlock.lua</code> 放在项目 <code>resources</code> 文件夹下，里面就是 “判断 + 删除” 的逻辑</li></ul><p><strong>② unlock () 方法：执行 Lua 脚本释放锁</strong></p><ul><li>参数 1：<code>UNLOCK_SCRIPT</code> → 刚才加载好的脚本对象</li><li>参数 2：<code>Collections.singletonList(KEY_PREFIX + name)</code> → 脚本里的 <code>KEYS[1]</code>，就是锁的 key（比如 <code>lock:order</code>）</li><li>参数 3：<code>ID_PREFIX + Thread.currentThread().getId()</code> → 脚本里的 <code>ARGV[1]</code>，就是当前线程的唯一标识（用来和锁里存的 value 对比）</li></ul><h3 id="分布式锁-redission"><a href="#分布式锁-redission" class="headerlink" title="分布式锁-redission"></a>分布式锁-redission</h3><h4 id="存在的问题"><a href="#存在的问题" class="headerlink" title="存在的问题"></a>存在的问题</h4><p>不可重入：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方法A加了分布式锁</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">    lock(); <span class="comment">// 线程拿到锁</span></span><br><span class="line">    methodB(); <span class="comment">// 方法B也加了同一个分布式锁</span></span><br><span class="line">    unlock();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法B也加了同一个分布式锁</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodB</span><span class="params">()</span> &#123;</span><br><span class="line">    lock(); <span class="comment">// 同一个线程再次申请锁，被拒绝 → 死锁</span></span><br><span class="line">    <span class="comment">// ...业务</span></span><br><span class="line">    unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>线程执行<code>methodA</code>拿到锁后，调用<code>methodB</code>时，再次尝试拿同一个锁，这时候分布式锁会认为 “锁已经被别人占了”，导致线程自己阻塞自己，形成<strong>死锁</strong>。</p><p>不可重试：</p><p>现在的<code>setnx</code>实现，线程尝试拿锁一次，如果失败（返回<code>false</code>），就直接结束了，没有 “再试一次” 的机制。但实际业务里（比如秒杀、订单创建），锁竞争往往是短暂的，线程应该可以重试几次，提高拿到锁的成功率。</p><p>超时释放：</p><p>我们给锁加了过期时间（比如 30 秒），本来是为了防止 “服务挂了锁不释放” 导致死锁，但带来了新问题：如果业务执行时间<strong>超过了锁的过期时间</strong>，锁会自动释放，这时候其他线程就能拿到锁，操作同一个资源，导致<strong>数据不一致</strong>。</p><p>主从一致性：</p><p><strong>① Redis 主从集群的原理</strong></p><ul><li>主节点（Master）：负责写操作（加锁、解锁）</li><li>从节点（Slave）：负责读操作，主节点的数据会<strong>异步同步</strong>到从节点</li><li>如果主节点挂了，集群会把一个从节点升级为新的主节点</li></ul><p><strong>② 问题场景</strong></p><ul><li>线程 A 向主节点加锁成功，主节点还没把这个锁数据同步到从节点</li><li>主节点突然宕机了</li><li>集群选举一个从节点成为新主节点，但这个新主节点<strong>没有刚才的锁数据</strong>，认为锁不存在</li><li>线程 B 来拿锁，直接成功，这时候就出现了<strong>两个线程同时持有同一个锁</strong>的情况，锁失效，并发安全问题爆发。</li></ul><h4 id="Redission快速入门"><a href="#Redission快速入门" class="headerlink" title="Redission快速入门"></a>Redission快速入门</h4><p>配置Redisson客户端</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedissonConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedissonClient <span class="title function_">redissonClient</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 配置</span></span><br><span class="line">        <span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config.useSingleServer().setAddress(<span class="string">&quot;redis://192.168.150.101:6379&quot;</span>)</span><br><span class="line">            .setPassword(<span class="string">&quot;123321&quot;</span>);</span><br><span class="line">        <span class="comment">// 创建RedissonClient对象</span></span><br><span class="line">        <span class="keyword">return</span> Redisson.create(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如何使用Redission的分布式锁</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="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> RedissionClient redissonClient;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testRedisson</span><span class="params">()</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">    <span class="comment">//获取锁(可重入)，指定锁的名称</span></span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;anyLock&quot;</span>);</span><br><span class="line">    <span class="comment">//尝试获取锁，参数分别是：获取锁的最大等待时间(期间会重试)，锁自动释放时间，时间单位</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> lock.tryLock(<span class="number">1</span>,<span class="number">10</span>,TimeUnit.SECONDS);</span><br><span class="line">    <span class="comment">//判断获取锁成功</span></span><br><span class="line">    <span class="keyword">if</span>(isLock)&#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;执行业务&quot;</span>);          </span><br><span class="line">        &#125;<span class="keyword">finally</span>&#123;</span><br><span class="line">            <span class="comment">//释放锁</span></span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>VoucherOrderServiceImpl</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">        <span class="comment">// 1.查询优惠券</span></span><br><span class="line">        <span class="type">SeckillVoucher</span> <span class="variable">voucher</span> <span class="operator">=</span> seckillVoucherService.getById(voucherId);</span><br><span class="line">        <span class="comment">// 2.判断秒杀是否开始</span></span><br><span class="line">        <span class="keyword">if</span> (voucher.getBeginTime().isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">            <span class="comment">// 尚未开始</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀尚未开始！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 3.判断秒杀是否已经结束</span></span><br><span class="line">        <span class="keyword">if</span> (voucher.getEndTime().isBefore(LocalDateTime.now())) &#123;</span><br><span class="line">            <span class="comment">// 尚未开始</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀已经结束！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 4.判断库存是否充足</span></span><br><span class="line">        <span class="keyword">if</span> (voucher.getStock() &lt; <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="comment">// 库存不足</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">        <span class="comment">//创建锁对象 这个代码不用了，因为我们现在要使用分布式锁</span></span><br><span class="line">        <span class="comment">//SimpleRedisLock lock = new SimpleRedisLock(&quot;order:&quot; + userId, stringRedisTemplate);</span></span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock:order:&quot;</span> + userId);</span><br><span class="line">        <span class="comment">//获取锁对象</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> lock.tryLock();</span><br><span class="line">       </span><br><span class="line"><span class="comment">//加锁失败</span></span><br><span class="line">        <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;不允许重复下单&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//获取代理对象(事务)</span></span><br><span class="line">            <span class="type">IVoucherOrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> (IVoucherOrderService) AopContext.currentProxy();</span><br><span class="line">            <span class="keyword">return</span> proxy.createVoucherOrder(voucherId);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">//释放锁</span></span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><h4 id="redission可重入锁原理"><a href="#redission可重入锁原理" class="headerlink" title="redission可重入锁原理"></a>redission可重入锁原理</h4><p>3 个核心参数</p><table><thead><tr><th>参数</th><th>含义</th><th>作用</th></tr></thead><tbody><tr><td><code>KEYS[1]</code></td><td>锁的<strong>大 key</strong>（锁名称）</td><td>代表这把锁的整体，用来判断锁是否存在</td></tr><tr><td><code>ARGV[1]</code></td><td>锁的<strong>过期时间</strong>（毫秒）</td><td>防止锁死锁，即使客户端宕机也会自动释放</td></tr><tr><td><code>ARGV[2]</code></td><td>锁的<strong>小 key</strong>（持有者标识）</td><td>格式：<code>客户端ID + &quot;:&quot; + 线程ID</code>，用来判断锁是否属于当前线程</td></tr></tbody></table><p>脚本的核心逻辑</p><figure class="highlight lua"><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">-- 步骤1：锁不存在 → 直接加锁</span></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;exists&#x27;</span>, KEYS[<span class="number">1</span>]) == <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hset&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>);  <span class="comment">-- 新建hash锁，小key对应值=1（第一次持有）</span></span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]); <span class="comment">-- 给锁设置过期时间</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>; <span class="comment">-- 返回nil = 加锁成功</span></span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 步骤2：锁存在，但属于当前线程 → 可重入</span></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;hexists&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>]) == <span class="number">1</span>) <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>); <span class="comment">-- 重入次数+1</span></span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]);    <span class="comment">-- 刷新过期时间</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>; <span class="comment">-- 返回nil = 重入成功</span></span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 步骤3：锁存在且不属于当前线程 → 抢锁失败</span></span><br><span class="line"><span class="keyword">return</span> redis.call(<span class="string">&#x27;pttl&#x27;</span>, KEYS[<span class="number">1</span>]); <span class="comment">-- 返回锁的剩余过期时间</span></span><br></pre></td></tr></table></figure><h4 id="redission锁重试和WatchDog机制"><a href="#redission锁重试和WatchDog机制" class="headerlink" title="redission锁重试和WatchDog机制"></a>redission锁重试和WatchDog机制</h4><p>Lua 抢锁逻辑</p><table><thead><tr><th>条件</th><th>操作</th><th>返回值</th><th>含义</th></tr></thead><tbody><tr><td>锁不存在</td><td>插入锁（Hash 结构），设置过期时间</td><td><code>null</code></td><td>抢锁成功</td></tr><tr><td>锁存在且属于当前线程</td><td>重入次数 + 1，刷新过期时间</td><td><code>null</code></td><td>可重入成功</td></tr><tr><td>锁存在且不属于当前线程</td><td>无操作</td><td>锁的剩余过期时间（ttl）</td><td>抢锁失败</td></tr></tbody></table><p><code>lock()</code> 核心抢锁流程</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></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">threadId</span> <span class="operator">=</span> Thread.currentThread().getId();</span><br><span class="line"><span class="type">Long</span> <span class="variable">ttl</span> <span class="operator">=</span> tryAcquire(-<span class="number">1</span>, leaseTime, unit, threadId);</span><br><span class="line"><span class="comment">// lock acquired</span></span><br><span class="line"><span class="keyword">if</span> (ttl == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① Long ttl &#x3D; tryAcquire(-1, leaseTime, unit, threadId);</p><ul><li><code>-1</code>：是 <code>waitTime</code> 的默认值（表示无限等待，直到抢到锁）；</li><li><code>leaseTime</code>：锁的过期时间（无参 <code>lock()</code> 时默认 <code>-1</code>，带参 <code>lock(10, TimeUnit.SECONDS)</code> 时为 10）；</li><li><code>unit</code>：时间单位（如 <code>TimeUnit.MILLISECONDS</code>）；</li><li><code>threadId</code>：当前线程 ID。</li></ul><p>② 返回值 <code>ttl</code>：</p><ul><li><code>null</code> → 抢锁 &#x2F; 可重入成功；</li><li>非 null 数字 → 锁被其他线程持有，返回锁的剩余过期时间（比如返回 20000 代表锁还有 20 秒过期）。</li></ul><p>WatchDog（看门狗）续约机制</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></pre></td><td class="code"><pre><span class="line">RFuture&lt;Long&gt; ttlRemainingFuture = tryLockInnerAsync(waitTime,</span><br><span class="line">                                        commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),</span><br><span class="line">                                        TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);</span><br><span class="line">ttlRemainingFuture.onComplete((ttlRemaining, e) -&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (e != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// lock acquired</span></span><br><span class="line">    <span class="keyword">if</span> (ttlRemaining == <span class="literal">null</span>) &#123;</span><br><span class="line">        scheduleExpirationRenewal(threadId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="keyword">return</span> ttlRemainingFuture;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① <code>tryLockInnerAsync(...)</code> 的第二个参数：<code>commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout()</code></p><ul><li>含义：获取 Redisson 配置的「看门狗默认超时时间」，默认值是 <strong>30 秒</strong>（30000 毫秒）；</li><li>作用：把锁的初始过期时间设为 30 秒（替代用户传入的 <code>leaseTime</code>）。</li></ul><p>② if (ttlRemaining &#x3D;&#x3D; null) { scheduleExpirationRenewal(threadId); }</p><ul><li>逻辑：只有抢锁成功（<code>ttlRemaining=null</code>），才调用 <code>scheduleExpirationRenewal(threadId)</code>；</li><li><code>scheduleExpirationRenewal</code>：核心作用是「启动看门狗续约线程」，是连接抢锁和续约的关键方法。</li></ul><h4 id="redission锁的MutiLock原理"><a href="#redission锁的MutiLock原理" class="headerlink" title="redission锁的MutiLock原理"></a>redission锁的MutiLock原理</h4><p>存在的问题</p><p>我们去写命令，写在主机上， 主机会将数据同步给从机，但是假设在主机还没有来得及把数据写入到从机去的时候，此时主机宕机，哨兵会发现主机宕机，并且选举一个slave变成master，而此时新的master中实际上并没有锁信息，此时锁信息就已经丢掉了。</p><img src="https://i-blog.csdnimg.cn/direct/35cd85875c7c48c7ba2e81e8d3f8fc89.png" alt="img" style="zoom: 50%;" /><p>解决方案</p><p>为了解决这个问题，redission提出来了MutiLock锁，使用这把锁就不使用主从了，每个节点的地位都是一样的， 这把锁加锁的逻辑需要写入到每一个主丛节点上，只有所有的服务器都写入成功，此时才是加锁成功。假设现在某个节点挂了，那么它去获得锁的时候，只要有一个节点拿不到，都不能算是加锁成功，就保证了加锁的可靠性。</p><img src="https://i-blog.csdnimg.cn/direct/a16918acda84463cadf64531da7fb488.png" alt="img" style="zoom: 50%;" /><h3 id="秒杀优化"><a href="#秒杀优化" class="headerlink" title="秒杀优化"></a>秒杀优化</h3><h4 id="异步秒杀思路"><a href="#异步秒杀思路" class="headerlink" title="异步秒杀思路"></a>异步秒杀思路</h4><p>(1) 当用户下单之后，判断库存是否充足只需要到redis中去根据key找对应的value是否大于0即可。如果不充足，则直接结束，如果充足，继续在redis中判断用户是否可以下单，如果set集合中没有这条数据，说明它可以下单，如果set集合中没有这条记录，则将userId和优惠卷存入到redis中，并且返回0，整个过程需要保证是原子性的，我们可以使用lua来操作。</p><img src="https://i-blog.csdnimg.cn/direct/7d7850c854d64fe1b6c3bb31ae3db31b.png" alt="img" style="zoom:50%;" /><p>(2) 校验通过后，无需等待完整下单流程完成，直接给用户返回 “下单受理成功”（附带订单 ID），同时将下单任务丢入异步队列；后台单独线程消费异步队列中的任务，慢慢执行完整的数据库下单逻辑（创建订单、扣减库存等）；前端通过返回的订单 ID，查询异步下单的最终结果（成功 &#x2F; 失败）。</p><h4 id="Redis完成秒杀资格判断"><a href="#Redis完成秒杀资格判断" class="headerlink" title="Redis完成秒杀资格判断"></a>Redis完成秒杀资格判断</h4><p>需求</p><ul><li>新增秒杀优惠券的同时，将优惠券信息保存到Redis中</li><li>基于Lua脚本，判断秒杀库存、一人一单，决定用户是否抢购成功</li><li>如果抢购成功，将优惠券id和用户id封装后存入阻塞队列</li><li>开启线程任务，不断从阻塞队列中获取信息，实现异步下单功能</li></ul><p>代码实现</p><p><strong>完整lua表达式</strong></p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 1.参数列表</span></span><br><span class="line"><span class="comment">-- 1.1.优惠券id</span></span><br><span class="line"><span class="keyword">local</span> voucherId = ARGV[<span class="number">1</span>]</span><br><span class="line"><span class="comment">-- 1.2.用户id</span></span><br><span class="line"><span class="keyword">local</span> userId = ARGV[<span class="number">2</span>]</span><br><span class="line"><span class="comment">-- 1.3.订单id</span></span><br><span class="line"><span class="keyword">local</span> orderId = ARGV[<span class="number">3</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2.数据key</span></span><br><span class="line"><span class="comment">-- 2.1.库存key</span></span><br><span class="line"><span class="keyword">local</span> stockKey = <span class="string">&#x27;seckill:stock:&#x27;</span> .. voucherId</span><br><span class="line"><span class="comment">-- 2.2.订单key</span></span><br><span class="line"><span class="keyword">local</span> orderKey = <span class="string">&#x27;seckill:order:&#x27;</span> .. voucherId</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3.脚本业务</span></span><br><span class="line"><span class="comment">-- 3.1.判断库存是否充足 get stockKey</span></span><br><span class="line"><span class="keyword">if</span>(<span class="built_in">tonumber</span>(redis.call(<span class="string">&#x27;get&#x27;</span>, stockKey)) &lt;= <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    <span class="comment">-- 3.2.库存不足，返回1</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment">-- 3.2.判断用户是否下单 SISMEMBER orderKey userId</span></span><br><span class="line"><span class="keyword">if</span>(redis.call(<span class="string">&#x27;sismember&#x27;</span>, orderKey, userId) == <span class="number">1</span>) <span class="keyword">then</span></span><br><span class="line">    <span class="comment">-- 3.3.存在，说明是重复下单，返回2</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">2</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment">-- 3.4.扣库存 incrby stockKey -1</span></span><br><span class="line">redis.call(<span class="string">&#x27;incrby&#x27;</span>, stockKey, <span class="number">-1</span>)</span><br><span class="line"><span class="comment">-- 3.5.下单（保存用户）sadd orderKey userId</span></span><br><span class="line">redis.call(<span class="string">&#x27;sadd&#x27;</span>, orderKey, userId)</span><br><span class="line"><span class="comment">-- 3.6.发送消息到队列中， XADD stream.orders * k1 v1 k2 v2 ...</span></span><br><span class="line">redis.call(<span class="string">&#x27;xadd&#x27;</span>, <span class="string">&#x27;stream.orders&#x27;</span>, <span class="string">&#x27;*&#x27;</span>, <span class="string">&#x27;userId&#x27;</span>, userId, <span class="string">&#x27;voucherId&#x27;</span>, voucherId, <span class="string">&#x27;id&#x27;</span>, orderId)</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① Lua 里的 <code>..</code> 是什么</p><p><code>..</code> 是 <strong>Lua 语言的字符串拼接运算符</strong>，作用和 Java 里用 <code>+</code> 拼接字符串（比如 <code>&quot;a&quot; + &quot;b&quot;</code>）完全一样，只是语法不同。</p><p>② if(tonumber(redis.call(‘get’, stockKey)) &lt;&#x3D; 0) then return 1 end</p><p><code>tonumber()</code> 是 Lua 的内置函数，作用是把<strong>字符串类型的数字</strong>转成<strong>数值类型</strong>；</p><p><strong>VoucherOrderServiceImpl</strong></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="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="comment">//获取用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">    <span class="comment">// 1.执行lua脚本</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> stringRedisTemplate.execute(</span><br><span class="line">            SECKILL_SCRIPT,</span><br><span class="line">            Collections.emptyList(),</span><br><span class="line">            voucherId.toString(), userId.toString(), String.valueOf(orderId)</span><br><span class="line">    );</span><br><span class="line">    <span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> result.intValue();</span><br><span class="line">    <span class="comment">// 2.判断结果是否为0</span></span><br><span class="line">    <span class="keyword">if</span> (r != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 2.1.不为0 ，代表没有购买资格</span></span><br><span class="line">        <span class="keyword">return</span> Result.fail(r == <span class="number">1</span> ? <span class="string">&quot;库存不足&quot;</span> : <span class="string">&quot;不能重复下单&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//TODO 保存阻塞队列</span></span><br><span class="line">    <span class="comment">// 3.返回订单id</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="基于阻塞队列实现秒杀优化"><a href="#基于阻塞队列实现秒杀优化" class="headerlink" title="基于阻塞队列实现秒杀优化"></a>基于阻塞队列实现秒杀优化</h4><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><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//异步处理线程池</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ExecutorService</span> <span class="variable">SECKILL_ORDER_EXECUTOR</span> <span class="operator">=</span> Executors.newSingleThreadExecutor();</span><br><span class="line"></span><br><span class="line"><span class="comment">//在类初始化之后执行，因为当这个类初始化好了之后，随时都是有可能要执行的</span></span><br><span class="line"><span class="meta">@PostConstruct</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">   SECKILL_ORDER_EXECUTOR.submit(<span class="keyword">new</span> <span class="title class_">VoucherOrderHandler</span>());</span><br><span class="line">&#125;</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="keyword">private</span> <span class="keyword">class</span> <span class="title class_">VoucherOrderHandler</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>)&#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 1.获取队列中的订单信息</span></span><br><span class="line">                    <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> orderTasks.take();</span><br><span class="line">                    <span class="comment">// 2.创建订单</span></span><br><span class="line">                    handleVoucherOrder(voucherOrder);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    log.error(<span class="string">&quot;处理订单异常&quot;</span>, e);</span><br><span class="line">                &#125;</span><br><span class="line">           &#125;</span><br><span class="line">        &#125;</span><br><span class="line">     </span><br><span class="line">       <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleVoucherOrder</span><span class="params">(VoucherOrder voucherOrder)</span> &#123;</span><br><span class="line">            <span class="comment">//1.获取用户</span></span><br><span class="line">            <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> voucherOrder.getUserId();</span><br><span class="line">            <span class="comment">// 2.创建锁对象</span></span><br><span class="line">            <span class="type">RLock</span> <span class="variable">redisLock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock:order:&quot;</span> + userId);</span><br><span class="line">            <span class="comment">// 3.尝试获取锁</span></span><br><span class="line">            <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> redisLock.lock();</span><br><span class="line">            <span class="comment">// 4.判断是否获得锁成功</span></span><br><span class="line">            <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">                <span class="comment">// 获取锁失败，直接返回失败或者重试</span></span><br><span class="line">                log.error(<span class="string">&quot;不允许重复下单！&quot;</span>);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line"><span class="comment">//注意：由于是spring的事务是放在threadLocal中，此时的是多线程，事务会失效</span></span><br><span class="line">                proxy.createVoucherOrder(voucherOrder);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="comment">// 释放锁</span></span><br><span class="line">                redisLock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">     <span class="comment">//a</span></span><br><span class="line"><span class="keyword">private</span> BlockingQueue&lt;VoucherOrder&gt; orderTasks =<span class="keyword">new</span>  <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">1024</span> * <span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">        <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">        <span class="comment">// 1.执行lua脚本</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> stringRedisTemplate.execute(</span><br><span class="line">                SECKILL_SCRIPT,</span><br><span class="line">                Collections.emptyList(),</span><br><span class="line">                voucherId.toString(), userId.toString(), String.valueOf(orderId)</span><br><span class="line">        );</span><br><span class="line">        <span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> result.intValue();</span><br><span class="line">        <span class="comment">// 2.判断结果是否为0</span></span><br><span class="line">        <span class="keyword">if</span> (r != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 2.1.不为0 ，代表没有购买资格</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(r == <span class="number">1</span> ? <span class="string">&quot;库存不足&quot;</span> : <span class="string">&quot;不能重复下单&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">        <span class="comment">// 2.3.订单id</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">        voucherOrder.setId(orderId);</span><br><span class="line">        <span class="comment">// 2.4.用户id</span></span><br><span class="line">        voucherOrder.setUserId(userId);</span><br><span class="line">        <span class="comment">// 2.5.代金券id</span></span><br><span class="line">        voucherOrder.setVoucherId(voucherId);</span><br><span class="line">        <span class="comment">// 2.6.放入阻塞队列</span></span><br><span class="line">        orderTasks.add(voucherOrder);</span><br><span class="line">        <span class="comment">//3.获取代理对象</span></span><br><span class="line">         proxy = (IVoucherOrderService)AopContext.currentProxy();</span><br><span class="line">        <span class="comment">//4.返回订单id</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">    &#125;</span><br><span class="line">     </span><br><span class="line">      <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span>  <span class="keyword">void</span> <span class="title function_">createVoucherOrder</span><span class="params">(VoucherOrder voucherOrder)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> voucherOrder.getUserId();</span><br><span class="line">        <span class="comment">// 5.1.查询订单</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;voucher_id&quot;</span>, voucherOrder.getVoucherId()).count();</span><br><span class="line">        <span class="comment">// 5.2.判断是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 用户已经购买过了</span></span><br><span class="line">           log.error(<span class="string">&quot;用户已经购买过了&quot;</span>);</span><br><span class="line">           <span class="keyword">return</span> ;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6.扣减库存</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">                .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>) <span class="comment">// set stock = stock - 1</span></span><br><span class="line">                .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherOrder.getVoucherId()).gt(<span class="string">&quot;stock&quot;</span>, <span class="number">0</span>) <span class="comment">// where id = ? and stock &gt; 0</span></span><br><span class="line">                .update();</span><br><span class="line">        <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">            <span class="comment">// 扣减失败</span></span><br><span class="line">            log.error(<span class="string">&quot;库存不足&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> ;</span><br><span class="line">        &#125;</span><br><span class="line">        save(voucherOrder);</span><br><span class="line"> </span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① private static final ExecutorService SECKILL_ORDER_EXECUTOR &#x3D; Executors.newSingleThreadExecutor();</p><p>这是 Java 的<strong>线程池</strong>，<code>Executors.newSingleThreadExecutor()</code> 会创建一个「只有 1 个工作线程」的线程池 —— 所有任务都由这 1 个线程按顺序处理。</p><p>② @PostConstruct private void init() { SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler()); }</p><p><code>@PostConstruct</code>：Spring 注解，作用是「当前类被 Spring 初始化完成后（项目启动时），立即执行这个方法」；</p><p>③ private class VoucherOrderHandler implements Runnable { … }</p><p>这是一个<strong>内部线程任务类</strong>，实现了 <code>Runnable</code> 接口（Java 中 “可被线程执行的任务” 都要实现这个接口），里面的 <code>run()</code> 方法是线程要执行的核心逻辑。</p><h3 id="Postman测试方法"><a href="#Postman测试方法" class="headerlink" title="Postman测试方法"></a>Postman测试方法</h3><h4 id="发送验证码（不需要登录）"><a href="#发送验证码（不需要登录）" class="headerlink" title="发送验证码（不需要登录）"></a>发送验证码（不需要登录）</h4><p>(1) 请求方式：POST</p><p>(2) 直连后端：<a href="http://localhost:8082/user/code?phone=13100000000">http://localhost:8082/user/code?phone=13100000000</a></p><p>(3) 预期响应</p><figure class="highlight bash"><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">&#123;</span><br><span class="line">  <span class="string">&quot;success&quot;</span>: <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="登录获取-token"><a href="#登录获取-token" class="headerlink" title="登录获取 token"></a>登录获取 token</h4><p>(1) 请求方式：POST</p><p>(2) 直连后端：<a href="http://localhost:8082/user/login">http://localhost:8082/user/login</a></p><p>(3) Body（选择 raw，格式 JSON）：</p><figure class="highlight bash"><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">&#123;</span><br><span class="line">  <span class="string">&quot;phone&quot;</span>: <span class="string">&quot;13100000000&quot;</span>,</span><br><span class="line">  <span class="string">&quot;code&quot;</span>: <span class="string">&quot;518208&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意：code 是第一步发送验证码后，在控制台日志里看到的 6 位数字</p><p>(4) 预期响应</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;1c3e03a8a5734dd1a8a5285c0d49a63c&quot;</span></span><br></pre></td></tr></table></figure><p>这个字符串就是 token，复制保存下来</p><h4 id="测试登录状态（验证-token-是否有效）"><a href="#测试登录状态（验证-token-是否有效）" class="headerlink" title="测试登录状态（验证 token 是否有效）"></a>测试登录状态（验证 token 是否有效）</h4><p>(1) 请求方式：GET</p><p>(2) 直连后端：<a href="http://localhost:8082/user/me">http://localhost:8082/user/me</a></p><p>(3) Headers：</p><p>authorization: 1c3e03a8a5734dd1a8a5285c0d49a63c</p><p>(4) 预期响应</p><figure class="highlight bash"><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">&#123;</span><br><span class="line">  <span class="string">&quot;success&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="string">&quot;data&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;id&quot;</span>: 1,</span><br><span class="line">    <span class="string">&quot;nickName&quot;</span>: <span class="string">&quot;user_xxx&quot;</span>,</span><br><span class="line">    <span class="string">&quot;icon&quot;</span>: <span class="string">&quot;...&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="秒杀下单（需要登录）"><a href="#秒杀下单（需要登录）" class="headerlink" title="秒杀下单（需要登录）"></a>秒杀下单（需要登录）</h4><p>(1) 请求方式：POST</p><p>(2) 直连后端：<a href="http://localhost:8082/voucher-order/seckill/10">http://localhost:8082/voucher-order/seckill/10</a></p><p>(3) Headers：</p><p>authorization: 1c3e03a8a5734dd1a8a5285c0d49a63c</p><p>(4) Body：不需要</p><p>(5) 可能响应：</p><figure class="highlight bash"><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">&#123;</span><br><span class="line">  <span class="string">&quot;success&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="string">&quot;data&quot;</span>: 1234567890</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="JMeter使用方法"><a href="#JMeter使用方法" class="headerlink" title="JMeter使用方法"></a>JMeter使用方法</h3><h4 id="添加线程组"><a href="#添加线程组" class="headerlink" title="添加线程组"></a>添加线程组</h4><p>(1) 右键 “测试计划” → 添加 → 线程(用户) → 线程组</p><p>(2) 配置</p><ul><li><p>线程数(用户数)：并发用户数（如 100）</p></li><li><p>Ramp-Up时间(秒)：启动时间（如 10）</p></li><li><p>循环次数：每个线程执行次数（如 1）</p></li></ul><img src="https://i-blog.csdnimg.cn/direct/6c46a765b36a4d479fa28be8c4693697.png" alt="img" style="zoom: 33%;" /><h4 id="添加-HTTP-请求"><a href="#添加-HTTP-请求" class="headerlink" title="添加 HTTP 请求"></a>添加 HTTP 请求</h4><p>(1) 右键 “线程组” → 添加 → 取样器 → HTTP 请求</p><p>(2) 配置：</p><ul><li>名称：秒杀下单（自定义）</li><li>服务器名称或IP：localhost（或后端服务器IP）</li><li>端口号：8081（或你的后端端口）</li><li>方法：POST</li><li>路径：&#x2F;voucher-order&#x2F;seckill&#x2F;10（你的秒杀接口路径）</li></ul><img src="https://i-blog.csdnimg.cn/direct/ecf3ba3200114a0c85f4ab9126e80b81.png" alt="img" style="zoom:33%;" /><h4 id="添加-HTTP-信息头管理器"><a href="#添加-HTTP-信息头管理器" class="headerlink" title="添加 HTTP 信息头管理器"></a>添加 HTTP 信息头管理器</h4><p>(1) 右键 “线程组” → 添加 → 配置元件 → HTTP 信息头管理器</p><p>(2) 配置：</p><ul><li>名称：authorization</li><li>值：你的登录token（例如：1c3e03a8a5734dd1a8a5285c0d49a63c）</li></ul><h4 id="添加监听器（查看结果）"><a href="#添加监听器（查看结果）" class="headerlink" title="添加监听器（查看结果）"></a>添加监听器（查看结果）</h4><p>右键 “线程组” → 添加 → 监听器 → 选择：</p><ul><li><p>察看结果树：查看每个请求的详细响应（调试用）</p></li><li><p>汇总报告：查看统计信息（吞吐量、错误率等）</p></li><li><p>聚合报告：更详细的性能报告</p></li></ul><h4 id="运行测试"><a href="#运行测试" class="headerlink" title="运行测试"></a>运行测试</h4><p>点击顶部工具栏的绿色“启动”按钮</p><h3 id="消息队列"><a href="#消息队列" class="headerlink" title="消息队列"></a>消息队列</h3><h4 id="为什么使用消息队列"><a href="#为什么使用消息队列" class="headerlink" title="为什么使用消息队列"></a>为什么使用消息队列</h4><p>阻塞队列是 <strong>JVM 内存内的本地队列</strong>，只适合「单机、简单场景」，实际项目中处处受限：</p><p>只能单机使用，不支持分布式</p><p>阻塞队列存储在单个 JVM 进程内存中，<strong>多机集群部署时队列无法共享</strong>：不同服务器的阻塞队列相互隔离，任务无法在集群间负载均衡，导致部分机器任务堆积、部分机器空闲，资源利用率极低。</p><p>消息无持久化，宕机就丢</p><p>阻塞队列的任务仅存在内存中，<strong>服务重启、宕机或 OOM 时，未处理的消息会全部丢失</strong>：在秒杀等核心业务中，这会导致用户已成功下单但订单数据丢失，引发资损和客诉。</p><h4 id="认识消息队列"><a href="#认识消息队列" class="headerlink" title="认识消息队列"></a>认识消息队列</h4><ul><li>消息队列：存储和管理消息，也被称为消息代理（Message Broker）</li><li>生产者：发送消息到消息队列</li><li>消费者：从消息队列获取消息并处理消息</li></ul><h4 id="基于List实现消息队列（了解即可）"><a href="#基于List实现消息队列（了解即可）" class="headerlink" title="基于List实现消息队列（了解即可）"></a>基于List实现消息队列（了解即可）</h4><p>实现原理</p><p>队列是入口和出口不在一边，因此我们可以利用：LPUSH 结合 RPOP、或者 RPUSH 结合 LPOP来实现。当队列中没有消息时，RPOP或LPOP操作会返回null，并不像JVM的阻塞队列那样会阻塞并等待消息。因此这里应该使用BRPOP或者BLPOP来实现阻塞效果。</p><p><img src="https://i-blog.csdnimg.cn/direct/61e7d19d580c480da05025c32822a8df.png" alt="img"></p><p>优缺点</p><p>① 优点：</p><ul><li>利用Redis存储，不受限于JVM内存上限</li><li>基于Redis的持久化机制，数据安全性有保证</li><li>可以满足消息有序性</li></ul><p>② 缺点：</p><ul><li>无法避免消息丢失</li><li>只支持单消费者</li></ul><h4 id="基于PubSub的消息队列（了解即可）"><a href="#基于PubSub的消息队列（了解即可）" class="headerlink" title="基于PubSub的消息队列（了解即可）"></a>基于PubSub的消息队列（了解即可）</h4><p>实现原理</p><p>SUBSCRIBE channel [channel] ：订阅一个或多个频道</p><p>PUBLISH channel msg ：向一个频道发送消息</p><p>PSUBSCRIBE pattern[pattern] ：订阅与pattern格式匹配的所有频道</p><img src="https://i-blog.csdnimg.cn/direct/7352b329919946c7bebfd3c9f7db6fe2.png" alt="img" style="zoom: 67%;" /><p>优缺点</p><p>① 优点：</p><ul><li>采用发布订阅模型，支持多生产、多消费</li></ul><p>② 缺点：</p><ul><li>不支持数据持久化</li><li>无法避免消息丢失</li><li>消息堆积有上限，超出时数据丢失</li></ul><h4 id="基于Stream的消息队列（推荐）"><a href="#基于Stream的消息队列（推荐）" class="headerlink" title="基于Stream的消息队列（推荐）"></a>基于Stream的消息队列（推荐）</h4><p>发送消息：XADD 命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">XADD key [NOMKSTREAM] [MAXLEN|MINID [=|~] threshold [LIMIT count]] *|ID field value [field value ...]</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li><code>key</code>：消息队列名称，队列不存在时<strong>默认自动创建</strong>（<code>NOMKSTREAM</code> 可禁止自动创建）。</li><li><code>MAXLEN|MINID</code>：用于限制队列的<strong>最大消息数量</strong>。</li><li><code>*</code>：让 Redis <strong>自动生成消息 ID</strong>（格式：<code>时间戳-递增数字</code>，如 <code>1644805700523-0</code>）；也可手动指定 ID。</li><li>消息内容：由多个 <code>field value</code> 键值对组成（称为 Entry）。</li></ul><p><strong>示例：</strong></p><figure class="highlight bash"><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"># 创建名为 users 的队列，发送消息 &#123;name: jack, age: 21&#125;，自动生成 ID</span></span><br><span class="line">XADD <span class="built_in">users</span> * name jack age 21</span><br></pre></td></tr></table></figure><p>读取消息：<code>XREAD</code> 命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><code>① COUNT count</code>：每次读取的<strong>最大消息数量</strong>。</p><p><code>② BLOCK milliseconds</code>：阻塞模式，无消息时阻塞等待，超时后返回 <code>(nil)</code>。</p><p><code>③ STREAMS key</code>：指定要读取的队列名称。</p><p><code>④ ID</code>：起始读取 ID，仅返回<strong>大于该 ID</strong> 的消息：</p><ul><li><code>0</code>：从队列<strong>第一个消息</strong>开始读取。</li><li><code>$</code>：从队列<strong>最新消息</strong>开始读取。</li></ul><p><strong>示例：</strong></p><p>① 读取历史消息：指定起始 ID 为 <code>0</code>，从队列第一条消息开始读取。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">XREAD COUNT 1 STREAMS <span class="built_in">users</span> 0</span><br></pre></td></tr></table></figure><p>② 阻塞读取最新消息：指定起始 ID 为 <code>$</code>，配合 <code>BLOCK</code> 实现阻塞等待，无消息时超时返回 <code>(nil)</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">XREAD COUNT 1 BLOCK 1000 STREAMS <span class="built_in">users</span> $</span><br></pre></td></tr></table></figure><p>关键注意点</p><p>使用 <code>$</code> 读取最新消息时存在<strong>漏读风险</strong>：若处理当前消息期间，队列新增了多条消息，下次调用只会获取到<strong>最新的一条</strong>，中间新增的消息会被跳过。</p><h4 id="基于Stream的消息队列-消费者组"><a href="#基于Stream的消息队列-消费者组" class="headerlink" title="基于Stream的消息队列-消费者组"></a>基于Stream的消息队列-消费者组</h4><p>创建消费者组</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">XGROUP CREATE key groupName ID [MKSTREAM]</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li>key：队列名称</li><li>groupName：消费者组名称</li><li>ID：起始ID标示，$代表队列中最后一个消息，0则代表队列中第一个消息</li><li>MKSTREAM：队列不存在时自动创建队列</li></ul><p><strong>删除指定的消费者组</strong></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">XGROUP DESTORY key groupName</span><br></pre></td></tr></table></figure><p><strong>给指定的消费者组添加消费者</strong></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">XGROUP CREATECONSUMER key groupname consumername</span><br></pre></td></tr></table></figure><p><strong>从消费者组读取消息：</strong></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">XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li>group：消费组名称</li><li>consumer：消费者名称，如果消费者不存在，会自动创建一个消费者</li><li>count：本次查询的最大数量</li><li>BLOCK milliseconds：当没有消息时最长等待时间</li><li>NOACK：无需手动ACK，获取到消息后自动确认（一般不要添加这个）</li><li>STREAMS key：指定队列名称</li><li>ID：获取消息的起始ID：<ul><li>“&gt;”：从下一个未消费的消息开始</li><li>其它：根据指定id从pending-list中获取已消费但未确认的消息，例如0，是从pending-list中的第一个消息开始</li></ul></li></ul><p><strong>Redis Stream 消费者组可靠消费</strong> 的核心逻辑</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="keyword">while</span>(<span class="literal">true</span>)&#123;</span><br><span class="line">    <span class="comment">// 1. 阻塞读取消费者组g1中消费者c1的新消息（队列s1，阻塞2000ms，每次读1条）</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">msg</span> <span class="operator">=</span> redis.call(<span class="string">&quot;XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 &gt;&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span>(msg == <span class="literal">null</span>)&#123;</span><br><span class="line">        <span class="keyword">continue</span>; <span class="comment">// 无新消息，继续循环监听</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 2. 执行业务逻辑处理消息</span></span><br><span class="line">        handleMessage(msg);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 【图片中省略但必须加】处理成功后调用XACK确认</span></span><br><span class="line">        <span class="comment">// redis.call(&quot;XACK s1 g1 &quot; + msgId);</span></span><br><span class="line">        </span><br><span class="line">    &#125; <span class="keyword">catch</span>(Exception e) &#123;</span><br><span class="line">        <span class="comment">// 3. 处理失败时，循环重试pending-list中的未确认消息</span></span><br><span class="line">        <span class="keyword">while</span>(<span class="literal">true</span>)&#123;</span><br><span class="line">            <span class="comment">// 读取pending-list中已分配但未确认的消息（从ID=0开始）</span></span><br><span class="line">            <span class="type">Object</span> <span class="variable">msg</span> <span class="operator">=</span> redis.call(<span class="string">&quot;XREADGROUP GROUP g1 c1 COUNT 1 STREAMS s1 0&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span>(msg == <span class="literal">null</span>)&#123;</span><br><span class="line">                <span class="keyword">break</span>; <span class="comment">// 无未确认消息，退出重试循环</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 重新处理异常消息</span></span><br><span class="line">                handleMessage(msg);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 【图片中省略但必须加】重试成功后ACK确认</span></span><br><span class="line">                <span class="comment">// redis.call(&quot;XACK s1 g1 &quot; + msgId);</span></span><br><span class="line">                </span><br><span class="line">            &#125; <span class="keyword">catch</span>(Exception e) &#123;</span><br><span class="line">                <span class="comment">// 再次失败则继续重试（也可加重试次数限制）</span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① <strong>必须 ACK</strong>：处理成功后，要调用 <code>XACK</code> 命令告诉 Redis “这条消息我处理完了”，消息会从 <code>pending-list</code>（未确认消息列表）中移除。</p><p>② Object msg &#x3D; redis.call(“XREADGROUP GROUP g1 c1 COUNT 1 STREAMS s1 0”);</p><ul><li><code>0</code> 代表从 <code>pending-list</code> 中读取<strong>已分配但未确认</strong>的消息，也就是之前处理失败的那条。</li><li>这是为了<strong>不丢失消息</strong>，保证失败的消息会被反复重试，直到处理成功。</li></ul><h4 id="基于Redis的Stream结构作为消息队列，实现异步秒杀下单"><a href="#基于Redis的Stream结构作为消息队列，实现异步秒杀下单" class="headerlink" title="基于Redis的Stream结构作为消息队列，实现异步秒杀下单"></a>基于Redis的Stream结构作为消息队列，实现异步秒杀下单</h4><p>需求</p><ul><li>创建一个Stream类型的消息队列，名为stream.orders</li><li>修改之前的秒杀下单Lua脚本，在认定有抢购资格后，直接向stream.orders中添加消息，内容包含voucherId、userId、orderId</li><li>项目启动时，开启一个线程任务，尝试获取stream.orders中的消息，完成下单</li></ul><p>VoucherOrderServiceImpl</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><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">VoucherOrderHandler</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 &gt;</span></span><br><span class="line">                List&lt;MapRecord&lt;String, Object, Object&gt;&gt; list = stringRedisTemplate.opsForStream().read(</span><br><span class="line">                    Consumer.from(<span class="string">&quot;g1&quot;</span>, <span class="string">&quot;c1&quot;</span>),</span><br><span class="line">                    StreamReadOptions.empty().count(<span class="number">1</span>).block(Duration.ofSeconds(<span class="number">2</span>)),</span><br><span class="line">                    StreamOffset.create(<span class="string">&quot;stream.orders&quot;</span>, ReadOffset.lastConsumed())</span><br><span class="line">                );</span><br><span class="line">                <span class="comment">// 2.判断订单信息是否为空</span></span><br><span class="line">                <span class="keyword">if</span> (list == <span class="literal">null</span> || list.isEmpty()) &#123;</span><br><span class="line">                    <span class="comment">// 如果为null，说明没有消息，继续下一次循环</span></span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 解析数据</span></span><br><span class="line">                MapRecord&lt;String, Object, Object&gt; record = list.get(<span class="number">0</span>);</span><br><span class="line">                Map&lt;Object, Object&gt; value = record.getValue();</span><br><span class="line">                <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> BeanUtil.fillBeanWithMap(value, <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>(), <span class="literal">true</span>);</span><br><span class="line">                <span class="comment">// 3.创建订单</span></span><br><span class="line">                createVoucherOrder(voucherOrder);</span><br><span class="line">                <span class="comment">// 4.确认消息 XACK</span></span><br><span class="line">                stringRedisTemplate.opsForStream().acknowledge(<span class="string">&quot;s1&quot;</span>, <span class="string">&quot;g1&quot;</span>, record.getId());</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;处理订单异常&quot;</span>, e);</span><br><span class="line">                <span class="comment">//处理异常消息</span></span><br><span class="line">                handlePendingList();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handlePendingList</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0</span></span><br><span class="line">                List&lt;MapRecord&lt;String, Object, Object&gt;&gt; list = stringRedisTemplate.opsForStream().read(</span><br><span class="line">                    Consumer.from(<span class="string">&quot;g1&quot;</span>, <span class="string">&quot;c1&quot;</span>),</span><br><span class="line">                    StreamReadOptions.empty().count(<span class="number">1</span>),</span><br><span class="line">                    StreamOffset.create(<span class="string">&quot;stream.orders&quot;</span>, ReadOffset.from(<span class="string">&quot;0&quot;</span>))</span><br><span class="line">                );</span><br><span class="line">                <span class="comment">// 2.判断订单信息是否为空</span></span><br><span class="line">                <span class="keyword">if</span> (list == <span class="literal">null</span> || list.isEmpty()) &#123;</span><br><span class="line">                    <span class="comment">// 如果为null，说明没有异常消息，结束循环</span></span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 解析数据</span></span><br><span class="line">                MapRecord&lt;String, Object, Object&gt; record = list.get(<span class="number">0</span>);</span><br><span class="line">                Map&lt;Object, Object&gt; value = record.getValue();</span><br><span class="line">                <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> BeanUtil.fillBeanWithMap(value, <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>(), <span class="literal">true</span>);</span><br><span class="line">                <span class="comment">// 3.创建订单</span></span><br><span class="line">                createVoucherOrder(voucherOrder);</span><br><span class="line">                <span class="comment">// 4.确认消息 XACK</span></span><br><span class="line">                stringRedisTemplate.opsForStream().acknowledge(<span class="string">&quot;s1&quot;</span>, <span class="string">&quot;g1&quot;</span>, record.getId());</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;处理pendding订单异常&quot;</span>, e);</span><br><span class="line">                <span class="keyword">try</span>&#123;</span><br><span class="line">                    Thread.sleep(<span class="number">20</span>);</span><br><span class="line">                &#125;<span class="keyword">catch</span>(Exception e)&#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 核心：从消息队列读取消息</span></span><br><span class="line">List&lt;MapRecord&lt;String, Object, Object&gt;&gt; list = stringRedisTemplate.opsForStream().read(</span><br><span class="line">    Consumer.from(<span class="string">&quot;g1&quot;</span>, <span class="string">&quot;c1&quot;</span>),  <span class="comment">// 🔴 消息队列-消费组配置：组名g1，消费者名c1</span></span><br><span class="line">    StreamReadOptions.empty().count(<span class="number">1</span>).block(Duration.ofSeconds(<span class="number">2</span>)),  <span class="comment">// 🔴 消息队列-读取规则：每次读1条，没消息则阻塞等2秒</span></span><br><span class="line">    StreamOffset.create(<span class="string">&quot;stream.orders&quot;</span>, ReadOffset.lastConsumed())  <span class="comment">// 🔴 消息队列-指定队列名+读取位置：队列名stream.orders，读未消费的新消息</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><ul><li><code>stringRedisTemplate.opsForStream()</code>：拿到操作 Redis 消息队列的 “工具”，所有消息队列的读写都靠它。</li><li>消费组（g1）+ 消费者（c1）：可以理解为 “客服组 g1 里的客服 c1”，多个消费者能分摊处理队列消息，避免单线程扛不住。</li><li><code>stream.orders</code>：是你的消息队列名称。</li><li><code>block(2秒)</code>：队列没消息时不做空循环，等 2 秒再查，节省资源。</li></ul><p>② 为什么 <code>list 为空就 break</code>？</p><ul><li><code>list</code> 是从 <code>Pending List</code> 读取失败消息的结果，代码里设置了 <code>count(1)</code>（每次只读 1 条）；</li><li>如果 <code>list == null/空</code>，说明 <strong>Pending List 里已经没有待重试的失败消息了</strong>（所有失败订单都处理完了）；</li><li>此时再继续死循环没有意义，所以 <code>break</code> 跳出循环，结束本次失败消息的处理流程（等下次有新的失败消息时，这个方法会再次被调用）。</li></ul><p>③ 为什么要 <code>list.get(0)</code>？</p><ul><li>代码里 <code>StreamReadOptions.empty().count(1)</code> 明确指定了「每次只读取 1 条消息」，所以 <code>list</code> 里最多只有 1 个元素；</li><li><code>list.get(0)</code> 就是取出这<strong>唯一的一条失败消息</strong>，只有拿到这条消息，才能解析里面的订单数据（用户 ID、优惠券 ID），进而重新处理订单。</li></ul><p>④ 为什么要调用 <code>acknowledge</code> 确认消息？</p><ul><li>这是 Redis Stream 的「消息确认机制」，作用是告诉 Redis：<strong>这条失败的订单消息我已经重新处理成功了，请把它从 Pending List 中移除</strong>；</li><li>如果不确认（ACK）：Redis 会一直把这条消息留在 Pending List 里，后续这个方法会反复读取这条消息，导致「同一个失败订单被重复处理」（比如用户一个订单被多次扣库存）；</li></ul><p>⑤ 这个方法的执行流程</p><figure class="highlight bash"><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">1. 进入死循环，准备捞取失败订单；</span><br><span class="line">2. 从Pending List里读1条失败的订单消息；</span><br><span class="line">3. 如果没读到（list空），说明失败订单都处理完了，跳出循环；</span><br><span class="line">4. 如果读到了，解析成订单对象，重新尝试处理（加锁+扣库存+存订单）；</span><br><span class="line">5. 处理成功后，告诉Redis“这条消息搞定了，你可以删了”；</span><br><span class="line">6. 重复步骤2-5，直到所有失败消息都处理完。</span><br></pre></td></tr></table></figure><h3 id="达人探店"><a href="#达人探店" class="headerlink" title="达人探店"></a>达人探店</h3><h4 id="查看探店笔记"><a href="#查看探店笔记" class="headerlink" title="查看探店笔记"></a>查看探店笔记</h4><p>BlogServiceImpl</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="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.查询blog</span></span><br><span class="line">    <span class="type">Blog</span> <span class="variable">blog</span> <span class="operator">=</span> getById(id);</span><br><span class="line">    <span class="keyword">if</span> (blog == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;笔记不存在！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 2.查询blog有关的用户</span></span><br><span class="line">    queryBlogUser(blog);</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">return</span> Result.ok(blog);</span><br><span class="line">&#125;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">queryBlogUser</span><span class="params">(Blog blog)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> blog.getUserId();</span><br><span class="line">    com.hmdp.entity.<span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userService.getById(userId);</span><br><span class="line">    blog.setName(user.getNickName());</span><br><span class="line">    blog.setIcon(user.getIcon());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>这个私有方法的核心作用是：<strong>根据博客（Blog）中的用户 ID，查询对应的用户信息，并把用户的「昵称」和「头像」两个核心字段填充到博客对象中</strong>，最终让博客对象包含发布者的展示信息（而非完整的用户对象），适配前端详情页只需要昵称 &#x2F; 头像的展示需求。</p><h4 id="点赞功能"><a href="#点赞功能" class="headerlink" title="点赞功能"></a>点赞功能</h4><p>需求</p><ul><li>同一个用户只能点赞一次，再次点击则取消点赞</li><li>如果当前用户已经点赞，则点赞按钮高亮显示（前端已实现，判断字段Blog类的isLike属性）</li></ul><p>实现步骤</p><ul><li>给Blog类中添加一个isLike字段，标示是否被当前用户点赞</li><li>修改点赞功能，利用Redis的set集合判断是否点赞过，未点赞过则点赞数+1，已点赞过则点赞数-1</li><li>修改根据id查询Blog的业务，判断当前登录用户是否点赞过，赋值给isLike字段</li><li>修改分页查询Blog业务，判断当前登录用户是否点赞过，赋值给isLike字段</li></ul><p>代码实现</p><p>① 在Blog 添加一个字段</p><figure class="highlight java"><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="meta">@TableField(exist = false)</span></span><br><span class="line"><span class="keyword">private</span> Boolean isLike;</span><br></pre></td></tr></table></figure><p>② 修改代码</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line">   <span class="keyword">public</span> Result <span class="title function_">likeBlog</span><span class="params">(Long id)</span>&#123;</span><br><span class="line">       <span class="comment">// 1.获取登录用户</span></span><br><span class="line">       <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">       <span class="comment">// 2.判断当前登录用户是否已经点赞</span></span><br><span class="line">       <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> BLOG_LIKED_KEY + id;</span><br><span class="line">       <span class="type">Boolean</span> <span class="variable">isMember</span> <span class="operator">=</span> stringRedisTemplate.opsForSet().isMember(key, userId.toString());</span><br><span class="line">       <span class="keyword">if</span>(BooleanUtil.isFalse(isMember))&#123;</span><br><span class="line">            <span class="comment">//3.如果未点赞，可以点赞</span></span><br><span class="line">           <span class="comment">//3.1 数据库点赞数+1</span></span><br><span class="line">           <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> update().setSql(<span class="string">&quot;liked = liked + 1&quot;</span>).eq(<span class="string">&quot;id&quot;</span>, id).update();</span><br><span class="line">           <span class="comment">//3.2 保存用户到Redis的set集合</span></span><br><span class="line">           <span class="keyword">if</span>(isSuccess)&#123;</span><br><span class="line">               stringRedisTemplate.opsForSet().add(key,userId.toString());</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="comment">//4.如果已点赞，取消点赞</span></span><br><span class="line">           <span class="comment">//4.1 数据库点赞数-1</span></span><br><span class="line">           <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> update().setSql(<span class="string">&quot;liked = liked - 1&quot;</span>).eq(<span class="string">&quot;id&quot;</span>, id).update();</span><br><span class="line">           <span class="comment">//4.2 把用户从Redis的set集合移除</span></span><br><span class="line">           <span class="keyword">if</span>(isSuccess)&#123;</span><br><span class="line">               stringRedisTemplate.opsForSet().remove(key,userId.toString());</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br></pre></td></tr></table></figure><h4 id="点赞排行榜"><a href="#点赞排行榜" class="headerlink" title="点赞排行榜"></a>点赞排行榜</h4><p>BlogServiceImpl</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Result <span class="title function_">likeBlog</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">     <span class="comment">// 1.获取登录用户</span></span><br><span class="line">     <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">     <span class="comment">// 2.判断当前登录用户是否已经点赞</span></span><br><span class="line">     <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> BLOG_LIKED_KEY + id;</span><br><span class="line">     <span class="type">Double</span> <span class="variable">score</span> <span class="operator">=</span> stringRedisTemplate.opsForZSet().score(key, userId.toString());</span><br><span class="line">     <span class="keyword">if</span> (score == <span class="literal">null</span>) &#123;</span><br><span class="line">         <span class="comment">// 3.如果未点赞，可以点赞</span></span><br><span class="line">         <span class="comment">// 3.1.数据库点赞数 + 1</span></span><br><span class="line">         <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> update().setSql(<span class="string">&quot;liked = liked + 1&quot;</span>).eq(<span class="string">&quot;id&quot;</span>, id).update();</span><br><span class="line">         <span class="comment">// 3.2.保存用户到Redis的set集合  zadd key value score</span></span><br><span class="line">         <span class="keyword">if</span> (isSuccess) &#123;</span><br><span class="line">             stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());</span><br><span class="line">         &#125;</span><br><span class="line">     &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">         <span class="comment">// 4.如果已点赞，取消点赞</span></span><br><span class="line">         <span class="comment">// 4.1.数据库点赞数 -1</span></span><br><span class="line">         <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> update().setSql(<span class="string">&quot;liked = liked - 1&quot;</span>).eq(<span class="string">&quot;id&quot;</span>, id).update();</span><br><span class="line">         <span class="comment">// 4.2.把用户从Redis的set集合移除</span></span><br><span class="line">         <span class="keyword">if</span> (isSuccess) &#123;</span><br><span class="line">             stringRedisTemplate.opsForZSet().remove(key, userId.toString());</span><br><span class="line">         &#125;</span><br><span class="line">     &#125;</span><br><span class="line">     <span class="keyword">return</span> Result.ok();</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">isBlogLiked</span><span class="params">(Blog blog)</span> &#123;</span><br><span class="line">     <span class="comment">// 1.获取登录用户</span></span><br><span class="line">     <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> UserHolder.getUser();</span><br><span class="line">     <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">         <span class="comment">// 用户未登录，无需查询是否点赞</span></span><br><span class="line">         <span class="keyword">return</span>;</span><br><span class="line">     &#125;</span><br><span class="line">     <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> user.getId();</span><br><span class="line">     <span class="comment">// 2.判断当前登录用户是否已经点赞</span></span><br><span class="line">     <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;blog:liked:&quot;</span> + blog.getId();</span><br><span class="line">     <span class="type">Double</span> <span class="variable">score</span> <span class="operator">=</span> stringRedisTemplate.opsForZSet().score(key, userId.toString());</span><br><span class="line">     blog.setIsLike(score != <span class="literal">null</span>);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① Double score &#x3D; stringRedisTemplate.opsForZSet().score(key, userId.toString());</p><p>作用：查询 <strong>指定博客的点赞集合</strong> 中，<strong>当前用户</strong> 是否存在，并返回它的分数。</p><p><strong>业务判断：</strong></p><ul><li><code>score == null</code> → 用户<strong>没点赞</strong></li><li><code>score != null</code> → 用户<strong>已点赞</strong></li></ul><p>② stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());</p><p>作用：向 ZSet 中<strong>添加一个点赞用户</strong>，三个参数：</p><ul><li>博客点赞的 key</li><li>点赞用户 ID</li><li>分数（当前时间戳）</li></ul><p><strong>关键点：</strong></p><ul><li>ZSet 元素唯一 → 同一个用户重复点赞会被自动去重（完美适配「一人只能点一次赞」）</li><li>用<strong>时间戳当分数</strong> → 后续可以直接按时间倒序，展示「最新点赞的用户列表」（普通 Set 做不到排序，这就是用 ZSet 的原因！）</li></ul><p>BlogController</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/likes/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogLikes</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> blogService.queryBlogLikes(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>BlogService</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="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogLikes</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> BLOG_LIKED_KEY + id;</span><br><span class="line">    <span class="comment">// 1.查询top5的点赞用户 zrange key 0 4</span></span><br><span class="line">    Set&lt;String&gt; top5 = stringRedisTemplate.opsForZSet().range(key, <span class="number">0</span>, <span class="number">4</span>);</span><br><span class="line">    <span class="keyword">if</span> (top5 == <span class="literal">null</span> || top5.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.ok(Collections.emptyList());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 2.解析出其中的用户id</span></span><br><span class="line">    List&lt;Long&gt; ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());</span><br><span class="line">    <span class="type">String</span> <span class="variable">idStr</span> <span class="operator">=</span> StrUtil.join(<span class="string">&quot;,&quot;</span>, ids);</span><br><span class="line">    <span class="comment">// 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)</span></span><br><span class="line">    List&lt;UserDTO&gt; userDTOS = userService.query()</span><br><span class="line">            .in(<span class="string">&quot;id&quot;</span>, ids).last(<span class="string">&quot;ORDER BY FIELD(id,&quot;</span> + idStr + <span class="string">&quot;)&quot;</span>).list()</span><br><span class="line">            .stream()</span><br><span class="line">            .map(user -&gt; BeanUtil.copyProperties(user, UserDTO.class))</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">    <span class="comment">// 4.返回</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(userDTOS);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① List<Long> ids &#x3D; top5.stream().map(Long::valueOf).collect(Collectors.toList());</p><p><code>top5</code> 是你从 Redis 取出来的 <strong>Set<String></strong>，里面是：<code>[&quot;1001&quot;, &quot;1002&quot;, &quot;1003&quot;]</code>（字符串格式的用户 ID），数据库查询必须用数字类型（Long），所以要转换。</p><p><strong>map(Long::valueOf)</strong></p><p><code>map</code> &#x3D; 格式转换</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Long::valueOf` = 把字符串 `&quot;1001&quot;` 转成数字 `1001</span><br></pre></td></tr></table></figure><p>② List<UserDTO> userDTOS &#x3D; userService.query() .in(“id”, ids) .last(“ORDER BY FIELD(id,” + idStr + “)”) .list() .stream() .map(user -&gt; BeanUtil.copyProperties(user, UserDTO.class)) .collect(Collectors.toList());</p><p>1). userService.query()</p><p>这是 <strong>MyBatis-Plus（MP）的链式查询</strong></p><ul><li>作用：开启一个查询构造器，不用写复杂的 SQL，用代码拼接查询条件</li><li>等价于：<code>SELECT * FROM user</code></li></ul><p>2). .in(“id”, ids)</p><p>MP 的批量查询条件</p><ul><li>含义：<code>id 在 ids 集合中</code></li><li>对应 SQL：<code>WHERE id IN (1001,1002,1003,1004,1005)</code></li><li>作用：只查我们需要的<strong>前 5 个点赞用户</strong>，不查全表</li></ul><p>3). .last (“ORDER BY FIELD (id,” + idStr + “)”)</p><p>先讲背景：</p><p>数据库用 <code>IN</code> 查询时，<strong>返回的用户是乱序的</strong>！比如你查 <code>IN (1,2,3)</code>，数据库可能返回 <code>3,1,2</code>，会打乱 Redis 里<strong>点赞的先后顺序</strong>。</p><p>两个关键点：</p><ul><li>**<code>last()</code>**MP 的方法：<strong>手动在 SQL 最后追加一段代码</strong>（这里用来加排序）</li><li>**<code>ORDER BY FIELD(id, 1,2,3,4,5)</code>**MySQL 专属函数，作用：<strong>强制让结果按照我指定的 ID 顺序排序</strong></li></ul><p>举个例子：</p><ul><li><code>idStr = &quot;1001,1002,1003,1004,1005&quot;</code></li><li>拼接后 SQL：<code>ORDER BY FIELD(id,1001,1002,1003,1004,1005)</code></li><li>结果：<strong>用户必须按 1001→1002→1003→1004→1005 的顺序返回</strong>完美对应 Redis 里的<strong>点赞先后顺序</strong>！</li></ul><p>4). String idStr &#x3D; StrUtil.join(“,”, ids);</p><p><code>StrUtil.join</code> 是 <strong>hutool 工具类的拼接方法</strong>，作用：把一个集合 &#x2F; 数组，用指定符号拼接成一个字符串</p><p>举个最直观的例子：</p><ul><li>输入：<code>ids = [101, 102, 103]</code></li><li>拼接符号：<code>,</code></li><li>输出：<code>idStr = &quot;101,102,103&quot;</code></li></ul><p>为什么要拼这个字符串？</p><p>为了给后面的 <strong>SQL 排序</strong> 用！数据库需要这种<strong>逗号分隔的格式</strong>。</p><h3 id="好友关注"><a href="#好友关注" class="headerlink" title="好友关注"></a>好友关注</h3><h4 id="关注和取消关注"><a href="#关注和取消关注" class="headerlink" title="关注和取消关注"></a>关注和取消关注</h4><p>FollowController</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="comment">//关注</span></span><br><span class="line"><span class="meta">@PutMapping(&quot;/&#123;id&#125;/&#123;isFollow&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">follow</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long followUserId, <span class="meta">@PathVariable(&quot;isFollow&quot;)</span> Boolean isFollow)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> followService.follow(followUserId, isFollow);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//取消关注</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/or/not/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">isFollow</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long followUserId)</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> followService.isFollow(followUserId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>FollowService</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></pre></td><td class="code"><pre><span class="line">取消关注service</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">isFollow</span><span class="params">(Long followUserId)</span> &#123;</span><br><span class="line">        <span class="comment">// 1.获取登录用户</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">        <span class="comment">// 2.查询是否关注 select count(*) from tb_follow where user_id = ? and follow_user_id = ?</span></span><br><span class="line">        <span class="type">Integer</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;follow_user_id&quot;</span>, followUserId).count();</span><br><span class="line">        <span class="comment">// 3.判断</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(count &gt; <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"> 关注service</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">follow</span><span class="params">(Long followUserId, Boolean isFollow)</span> &#123;</span><br><span class="line">        <span class="comment">// 1.获取登录用户</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;follows:&quot;</span> + userId;</span><br><span class="line">        <span class="comment">// 1.判断到底是关注还是取关</span></span><br><span class="line">        <span class="keyword">if</span> (isFollow) &#123;</span><br><span class="line">            <span class="comment">// 2.关注，新增数据</span></span><br><span class="line">            <span class="type">Follow</span> <span class="variable">follow</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Follow</span>();</span><br><span class="line">            follow.setUserId(userId);</span><br><span class="line">            follow.setFollowUserId(followUserId);</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> save(follow);</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 3.取关，删除 delete from tb_follow where user_id = ? and follow_user_id = ?</span></span><br><span class="line">            remove(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;Follow&gt;()</span><br><span class="line">                    .eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;follow_user_id&quot;</span>, followUserId));</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> Result.ok();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><table><thead><tr><th>字段</th><th>含义</th></tr></thead><tbody><tr><td><code>userId</code></td><td><strong>当前登录用户</strong>（你自己）</td></tr><tr><td><code>followUserId</code></td><td><strong>要关注 &#x2F; 取关的用户</strong>（博主）</td></tr><tr><td><code>tb_follow</code> 表</td><td>专门存<strong>关注关系</strong>的表</td></tr><tr><td><code>isFollow</code></td><td><code>true</code>&#x3D; 关注，<code>false</code>&#x3D; 取关</td></tr></tbody></table><p>① <code>if (isFollow)</code> → <strong>关注（新增数据）</strong></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="comment">// 新建一个【关注关系】对象</span></span><br><span class="line"><span class="type">Follow</span> <span class="variable">follow</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Follow</span>();</span><br><span class="line"><span class="comment">// 设置：关注人是「我」（当前登录用户）</span></span><br><span class="line">follow.setUserId(userId);</span><br><span class="line"><span class="comment">// 设置：被关注的人是「对方」（博主）</span></span><br><span class="line">follow.setFollowUserId(followUserId);</span><br><span class="line"><span class="comment">// 把这条关注记录 保存到数据库</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> save(follow);</span><br></pre></td></tr></table></figure><ul><li>**<code>Follow follow = new Follow();</code>**数据库里有一张表叫 <code>tb_follow</code>（关注表），我们要往表里加一条数据，就得先创建一个对应的对象。</li><li>**<code>setUserId(userId)</code>**填数据：<strong>关注的人是我</strong>（我是主动关注的一方）。</li><li>**<code>setFollowUserId(followUserId)</code>**填数据：<strong>我关注的人是他</strong>（对方是被关注的博主）。</li><li>**<code>save(follow)</code>**MyBatis-Plus 提供的方法，<strong>直接把这条关注数据插入数据库</strong>。</li></ul><p>② <code>else</code> → <strong>取关（删除数据）</strong></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></pre></td><td class="code"><pre><span class="line">remove(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;Follow&gt;()</span><br><span class="line">    .eq(<span class="string">&quot;user_id&quot;</span>, userId)          <span class="comment">// 条件1：关注人是我</span></span><br><span class="line">    .eq(<span class="string">&quot;follow_user_id&quot;</span>, followUserId) <span class="comment">// 条件2：被关注人是他</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>new QueryWrapper<Follow>()</p><p>→ <strong>删除条件</strong></p><p>翻译：我不能乱删，必须告诉我<strong>删哪一行</strong></p><h4 id="共同关注"><a href="#共同关注" class="headerlink" title="共同关注"></a>共同关注</h4><p>代码实现</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserController 根据id查询用户</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryUserById</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long userId)</span>&#123;</span><br><span class="line"><span class="comment">// 查询详情</span></span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userService.getById(userId);</span><br><span class="line"><span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">UserDTO</span> <span class="variable">userDTO</span> <span class="operator">=</span> BeanUtil.copyProperties(user, UserDTO.class);</span><br><span class="line"><span class="comment">// 返回</span></span><br><span class="line"><span class="keyword">return</span> Result.ok(userDTO);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// BlogController  根据id查询博主的探店笔记</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/of/user&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogByUserId</span><span class="params">(</span></span><br><span class="line"><span class="params"><span class="meta">@RequestParam(value = &quot;current&quot;, defaultValue = &quot;1&quot;)</span> Integer current,</span></span><br><span class="line"><span class="params"><span class="meta">@RequestParam(&quot;id&quot;)</span> Long id)</span> &#123;</span><br><span class="line"><span class="comment">// 根据用户查询</span></span><br><span class="line">Page&lt;Blog&gt; page = blogService.query()</span><br><span class="line">.eq(<span class="string">&quot;user_id&quot;</span>, id).page(<span class="keyword">new</span> <span class="title class_">Page</span>&lt;&gt;(current, SystemConstants.MAX_PAGE_SIZE));</span><br><span class="line"><span class="comment">// 获取当前页数据</span></span><br><span class="line">List&lt;Blog&gt; records = page.getRecords();</span><br><span class="line"><span class="keyword">return</span> Result.ok(records);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><strong>① @PathVariable 和 @RequestParam 的区别</strong></p><p> <code>@PathVariable</code>（路径参数，写在 URL <code>/</code> 里的）， <code>@RequestParam</code> 是 <strong>查询参数</strong>，写在 URL <code>?</code> 后面的。</p><table><thead><tr><th>注解</th><th>位置</th><th>例子</th></tr></thead><tbody><tr><td><code>@PathVariable</code></td><td>URL 路径 <code>/</code> 里</td><td><code>/100/true</code></td></tr><tr><td><code>@RequestParam</code></td><td>URL 问号 <code>?</code> 后面</td><td><code>?current=1&amp;id=100</code></td></tr></tbody></table><p>② blogService.query()</p><p>→ 翻译：<strong>我要查 blog 表（博客表）的数据</strong></p><p>→ 等价 SQL：<code>SELECT * FROM blog</code></p><p>③ .page(分页参数)</p><p>.page(new Page&lt;&gt;(current, SystemConstants.MAX_PAGE_SIZE));</p><p>这是<strong>MyBatis-Plus 分页方法</strong>，两个参数：</p><ul><li><strong><code>current</code></strong>：当前页码（比如 1 &#x3D; 第一页，2 &#x3D; 第二页）</li><li><strong><code>SystemConstants.MAX_PAGE_SIZE</code></strong>：固定值，比如 <strong>10</strong> → 一页显示 10 条博客</li></ul><p>④ page.getRecords()</p><p>→ 翻译：<strong>从分页结果里，把「当前页的博客列表」拿出来</strong></p><p>→ 结果：<code>[博客1, 博客2, ..., 博客10]</code></p><p>修改FollowServiceImpl</p><p>改造原因：我们需要在用户关注了某位用户后，需要将数据放入到set集合中，方便后续进行共同关注，同时当取消关注时，也需要从set集合中进行删除。set集合可以实现交集、并集、补集的功能。</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="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">followCommons</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取当前用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;follows:&quot;</span> + userId;</span><br><span class="line">    <span class="comment">// 2.求交集</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key2</span> <span class="operator">=</span> <span class="string">&quot;follows:&quot;</span> + id;</span><br><span class="line">    Set&lt;String&gt; intersect = stringRedisTemplate.opsForSet().intersect(key, key2);</span><br><span class="line">    <span class="keyword">if</span> (intersect == <span class="literal">null</span> || intersect.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// 无交集</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(Collections.emptyList());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3.解析id集合</span></span><br><span class="line">    List&lt;Long&gt; ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());</span><br><span class="line">    <span class="comment">// 4.查询用户</span></span><br><span class="line">    List&lt;UserDTO&gt; users = userService.listByIds(ids)</span><br><span class="line">            .stream()</span><br><span class="line">            .map(user -&gt; BeanUtil.copyProperties(user, UserDTO.class))</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">    <span class="keyword">return</span> Result.ok(users);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>.collect(Collectors.toList())</p><p>把转换好的数字 ID，打包成一个<strong>列表</strong> </p><h4 id="Feed流实现方案"><a href="#Feed流实现方案" class="headerlink" title="Feed流实现方案"></a>Feed流实现方案</h4><p>针对好友的操作，采用的是Timeline的方式，只需要拿到我们关注用户的信息，然后按照时间排序即可，因此采用Timeline的模式。该模式的实现方案有三种：</p><ul><li>拉模式</li><li>推模式</li><li>推拉结合</li></ul><p>拉模式</p><p>① 核心逻辑：</p><p>当张三、李四和王五发了消息后，都会保存在自己的邮箱中。假设赵六要读取信息，那么它会从读取它自己的收件箱，此时系统会从它关注的人群中，把它关注人的信息全部都进行拉取，然后在进行排序。</p><img src="https://i-blog.csdnimg.cn/direct/e51d080b70f84f81b864597f8ee3a58a.png" alt="img" style="zoom:50%;" /><p>② 优点：比较节约空间，因为赵六在读信息时，并没有重复读取，而且读取完之后可以把它的收件箱进行清楚。</p><p>③ 缺点：比较延迟，当用户读取数据时才去关注的人里边去读取数据，假设用户关注了大量的用户，那么此时就会拉取海量的内容，对服务器压力巨大。</p><p>推模式</p><p>① 核心逻辑：</p><p>推模式是没有写邮箱的，当张三写了一个内容，此时会主动的把张三写的内容发送到它的粉丝收件箱中去，假设此时李四再来读取，就不用再去临时拉取了。</p><img src="https://i-blog.csdnimg.cn/direct/0c469091fc34406d9c2a2cb79fb2356a.png" alt="img" style="zoom:50%;" /><p>② 优点：时效快，不用临时拉取。</p><p>③ 缺点：内存压力大，假设一个大V写信息，很多人关注它， 就会写很多分数据到粉丝那边去</p><p>推拉模式</p><p>推拉模式是一个折中的方案。</p><p>① 站在发件人这一端，如果是个普通的人，那么我们采用写扩散的方式，直接把数据写入到它的粉丝中去，因为普通的人它的粉丝关注量比较小，所以这样做没有压力。如果是大V，那么它是直接将数据先写入到一份到发件箱里边去，然后再直接写一份到活跃粉丝收件箱里边去。</p><p>② 现在站在收件人这端来看，如果是活跃粉丝，那么大V和普通的人发的都会直接写入到自己收件箱里边来，而如果是普通的粉丝，由于它们上线不是很频繁，所以等它们上线时，再从发件箱里边去拉信息。</p><img src="https://i-blog.csdnimg.cn/direct/420ed1e92f2844f5b8abfbfab50ff300.png" alt="img" style="zoom: 50%;" /><h4 id="推送到粉丝收件箱"><a href="#推送到粉丝收件箱" class="headerlink" title="推送到粉丝收件箱"></a>推送到粉丝收件箱</h4><h5 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h5><ul><li>修改新增探店笔记的业务，在保存blog到数据库的同时，推送到粉丝的收件箱</li><li>收件箱满足可以根据时间戳排序，必须用Redis的数据结构实现</li><li>查询收件箱数据时，可以实现分页查询</li></ul><h5 id="Feed流的分页问题"><a href="#Feed流的分页问题" class="headerlink" title="Feed流的分页问题"></a>Feed流的分页问题</h5><p>① 传统分页</p><ul><li><strong>t1 时刻</strong>：取第 1 页（<code>page=1, size=5</code>），拿到<code>10,9,8,7,6</code>。</li><li><strong>t2 时刻</strong>：新增一条内容<code>11</code>，数据整体后移。</li><li><strong>t3 时刻</strong>：取第 2 页（<code>page=2, size=5</code>），按传统逻辑从第 6 条开始取，拿到<code>6,5,4,3,2</code>。</li><li><strong>结果</strong>：<code>6</code>在第 1 页和第 2 页都出现了，<strong>数据重复</strong>。</li></ul><img src="https://i-blog.csdnimg.cn/direct/e68b3d137cf944d2a367d75ff4f8b249.png" alt="img" style="zoom: 50%;" /><p>② 滚动分页</p><ul><li><strong>t1 时刻</strong>：首次加载，<code>lastId = ∞</code>（无前置数据），取前 5 条<code>10,9,8,7,6</code>，记录<code>lastId = 6</code>。</li><li><strong>t2 时刻</strong>：新增内容<code>11</code>，插入到最顶部，不影响已记录的<code>6</code>。</li><li><strong>t3 时刻</strong>：加载下一页，传入<code>lastId = 6</code>，从<code>6</code>之后的<code>5</code>开始取，拿到<code>5,4,3,2,1</code>，无重复。</li></ul><img src="https://i-blog.csdnimg.cn/direct/7cc3552352c54df9b1b78c5cca38ffe7.png" alt="img" style="zoom: 50%;" /><h5 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">saveBlog</span><span class="params">(Blog blog)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取登录用户</span></span><br><span class="line">    <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> UserHolder.getUser();</span><br><span class="line">    blog.setUserId(user.getId());</span><br><span class="line">    <span class="comment">// 2.保存探店笔记</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> save(blog);</span><br><span class="line">    <span class="keyword">if</span>(!isSuccess)&#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;新增笔记失败!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3.查询笔记作者的所有粉丝 select * from tb_follow where follow_user_id = ?</span></span><br><span class="line">    List&lt;Follow&gt; follows = followService.query().eq(<span class="string">&quot;follow_user_id&quot;</span>, user.getId()).list();</span><br><span class="line">    <span class="comment">// 4.推送笔记id给所有粉丝</span></span><br><span class="line">    <span class="keyword">for</span> (Follow follow : follows) &#123;</span><br><span class="line">        <span class="comment">// 4.1.获取粉丝id</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> follow.getUserId();</span><br><span class="line">        <span class="comment">// 4.2.推送</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> FEED_KEY + userId;</span><br><span class="line">        stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 5.返回id</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(blog.getId());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="实现分页查询收邮箱"><a href="#实现分页查询收邮箱" class="headerlink" title="实现分页查询收邮箱"></a>实现分页查询收邮箱</h4><p>需求</p><p>① 每次查询完成后，我们要分析出查询出数据的最小时间戳，这个值会作为下一次查询的条件</p><p>② 我们需要找到与上一次查询相同的查询个数作为偏移量，下次查询时，跳过这些查询过的数据，拿到我们需要的数据</p><table><thead><tr><th>参数</th><th>含义</th><th>核心目的</th></tr></thead><tbody><tr><td><code>lastId</code></td><td>上一次查询结果里的<strong>最小时间戳</strong></td><td>限定本次查询只加载「比这个时间更早」的 Blog，保证数据按时间倒序加载，避免重复获取</td></tr><tr><td><code>offset</code></td><td>上一次查询返回的<strong>Blog 数量</strong></td><td>用来跳过已经加载过的数据，实现 “下一页” 的翻页效果</td></tr></tbody></table><p>代码实现</p><p>① 定义出来具体的返回值实体类</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScrollResult</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;?&gt; list;</span><br><span class="line">    <span class="keyword">private</span> Long minTime;</span><br><span class="line">    <span class="keyword">private</span> Integer offset;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② BlogController</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/of/follow&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogOfFollow</span><span class="params">(</span></span><br><span class="line"><span class="params">    <span class="meta">@RequestParam(&quot;lastId&quot;)</span> Long max, <span class="meta">@RequestParam(value = &quot;offset&quot;, defaultValue = &quot;0&quot;)</span> Integer offset)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> blogService.queryBlogOfFollow(max, offset);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>③ BlogServiceImpl</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogOfFollow</span><span class="params">(Long max, Integer offset)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取当前用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">// 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> FEED_KEY + userId;</span><br><span class="line">    Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; typedTuples = stringRedisTemplate.opsForZSet()</span><br><span class="line">        .reverseRangeByScoreWithScores(key, <span class="number">0</span>, max, offset, <span class="number">2</span>);</span><br><span class="line">    <span class="comment">// 3.非空判断</span></span><br><span class="line">    <span class="keyword">if</span> (typedTuples == <span class="literal">null</span> || typedTuples.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.ok();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 4.解析数据：blogId、minTime（时间戳）、offset</span></span><br><span class="line">    List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(typedTuples.size());</span><br><span class="line">    <span class="type">long</span> <span class="variable">minTime</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">// 2</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">os</span> <span class="operator">=</span> <span class="number">1</span>; <span class="comment">// 2</span></span><br><span class="line">    <span class="keyword">for</span> (ZSetOperations.TypedTuple&lt;String&gt; tuple : typedTuples) &#123; <span class="comment">// 5 4 4 2 2</span></span><br><span class="line">        <span class="comment">// 4.1.获取id</span></span><br><span class="line">        ids.add(Long.valueOf(tuple.getValue()));</span><br><span class="line">        <span class="comment">// 4.2.获取分数(时间戳）</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">time</span> <span class="operator">=</span> tuple.getScore().longValue();</span><br><span class="line">        <span class="keyword">if</span>(time == minTime)&#123;</span><br><span class="line">            os++;</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            minTime = time;</span><br><span class="line">            os = <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">os = minTime == max ? os : os + offset;</span><br><span class="line">    <span class="comment">// 5.根据id查询blog</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">idStr</span> <span class="operator">=</span> StrUtil.join(<span class="string">&quot;,&quot;</span>, ids);</span><br><span class="line">    List&lt;Blog&gt; blogs = query().in(<span class="string">&quot;id&quot;</span>, ids).last(<span class="string">&quot;ORDER BY FIELD(id,&quot;</span> + idStr + <span class="string">&quot;)&quot;</span>).list();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (Blog blog : blogs) &#123;</span><br><span class="line">        <span class="comment">// 5.1.查询blog有关的用户</span></span><br><span class="line">        queryBlogUser(blog);</span><br><span class="line">        <span class="comment">// 5.2.查询blog是否被点赞</span></span><br><span class="line">        isBlogLiked(blog);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 6.封装并返回</span></span><br><span class="line">    <span class="type">ScrollResult</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ScrollResult</span>();</span><br><span class="line">    r.setList(blogs);</span><br><span class="line">    r.setOffset(os);</span><br><span class="line">    r.setMinTime(minTime);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> Result.ok(r);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① List<Long> ids &#x3D; new ArrayList&lt;&gt;(typedTuples.size());</p><ul><li><p><code>List&lt;Long&gt;</code>：创建一个<strong>列表</strong>，里面专门存 <strong>数字类型的博客 ID</strong>（数据库里的 ID 是数字，不是字符串）</p></li><li><p><code>ids</code>：列表的名字，意思就是 <strong>id 集合</strong></p></li><li><p><code>new ArrayList&lt;&gt;()</code>：创建列表对象</p></li><li><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">typedTuples.size()</span><br></pre></td></tr></table></figure><p>：</p><p>提前指定列表的大小</p><ul><li>比如 Redis 查到了 2 条数据，列表就直接开 2 个位置</li><li>作用：<strong>优化性能</strong>，不用让列表自动扩容</li></ul></li></ul><p>② String idStr &#x3D; tuple.getValue(); ids.add(Long.valueOf(idStr));</p><ul><li><p><code>tuple</code>：循环中<strong>每一个 Redis 包裹</strong>（包含博客 ID + 时间戳）</p></li><li><p><code>tuple.getValue()</code>：<strong>拿出包裹里的博客 ID</strong>（注意：是字符串格式，比如 “1001”）</p></li><li><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Long.valueOf(idStr)</span><br></pre></td></tr></table></figure><p>：</p><p>把字符串 ID 转成 数字 ID</p><ul><li>因为数据库里的 ID 是 <code>Long</code> 数字类型，字符串查不到数据，必须转类型</li></ul></li><li><p><code>ids.add(...)</code>：<strong>把转换后的数字 ID，放进刚才创建的列表里</strong></p></li></ul><p>③ List<Blog> blogs &#x3D; query().in(“id”, ids).last(“ORDER BY FIELD(id,” + idStr + “)”).list();</p><p>1). 问题</p><p>Redis 查出的 <code>blogId</code> 是<strong>按时间倒序</strong>的，但 MySQL 的 <code>IN</code> 查询<strong>不会保证返回顺序</strong>！直接查会导致博客顺序乱掉。</p><p>2). 解决方案</p><p><code>ORDER BY FIELD(id, 1,2,3)</code>：强制让数据库返回的顺序 <strong>和 Redis 中的 id 顺序完全一致</strong>，保证前端展示顺序正确。</p><h3 id="附近商户"><a href="#附近商户" class="headerlink" title="附近商户"></a>附近商户</h3><h4 id="GEO数据结构的基本用法"><a href="#GEO数据结构的基本用法" class="headerlink" title="GEO数据结构的基本用法"></a>GEO数据结构的基本用法</h4><p>GEO就是Geolocation的简写形式，代表地理坐标。Redis在3.2版本中加入了对GEO的支持，允许存储地理坐标信息，帮助我们根据经纬度来检索数据。常见的命令有：</p><ul><li>GEOADD：添加一个地理空间信息，包含：经度（longitude）、纬度（latitude）、值（member）</li><li>GEODIST：计算指定的两个点之间的距离并返回</li><li>GEOHASH：将指定member的坐标转为hash字符串形式并返回</li><li>GEOPOS：返回指定member的坐标</li><li>GEORADIUS：指定圆心、半径，找到该圆内包含的所有member，并按照与圆心之间的距离排序后返回。6.以后已废弃</li><li>GEOSEARCH：在指定范围内搜索member，并按照与指定点之间的距离排序后返回。范围可以是圆形或矩形。6.2.新功能</li><li>GEOSEARCHSTORE：与GEOSEARCH功能一致，不过可以把结果存储到一个指定的key。6.2.新功能</li></ul><h4 id="导入店铺数据到GEO"><a href="#导入店铺数据到GEO" class="headerlink" title="导入店铺数据到GEO"></a>导入店铺数据到GEO</h4><p>Redis GEO 数据结构设计</p><p>① 按「商家类型」分组存储</p><p>Redis GEO 本身<strong>不支持额外的类型筛选条件</strong>（比如只查美食、排除 KTV），所以我们用<strong>分组</strong>来解决：</p><ul><li><p>给每种商家类型建一个独立的 GEO 集合，Key 格式为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">shop:geo:&#123;类型&#125;</span><br></pre></td></tr></table></figure><ul><li>美食类：<code>shop:geo:美食</code></li><li>KTV 类：<code>shop:geo:KTV</code></li></ul></li><li><p>这样用户选「美食」时，直接查询 <code>shop:geo:美食</code>，天然就过滤了类型，只查美食商家。</p></li></ul><p>② GEO 里只存「商家 ID」，不存完整信息</p><p>Redis 是内存数据库，存太多数据会占满内存，所以：</p><ul><li><strong>Value（member）</strong>：只存商家的<strong>ID</strong>（比如海底捞的 ID 是 1，就存 “1”）</li><li><strong>Score</strong>：Redis 自动把经纬度转换成 GeoHash 值，用来计算距离和排序</li><li>完整的商家信息（名称、地址、评分等）依然存在数据库（MySQL）里，之后用 ID 去查。</li></ul><p>代码实现</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">loadShopData</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.查询店铺信息</span></span><br><span class="line">    List&lt;Shop&gt; list = shopService.list();</span><br><span class="line">    <span class="comment">// 2.把店铺分组，按照typeId分组，typeId一致的放到一个集合</span></span><br><span class="line">    Map&lt;Long, List&lt;Shop&gt;&gt; map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));</span><br><span class="line">    <span class="comment">// 3.分批完成写入Redis</span></span><br><span class="line">    <span class="keyword">for</span> (Map.Entry&lt;Long, List&lt;Shop&gt;&gt; entry : map.entrySet()) &#123;</span><br><span class="line">        <span class="comment">// 3.1.获取类型id</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">typeId</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> SHOP_GEO_KEY + typeId;</span><br><span class="line">        <span class="comment">// 3.2.获取同类型的店铺的集合</span></span><br><span class="line">        List&lt;Shop&gt; value = entry.getValue();</span><br><span class="line">        List&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; locations = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(value.size());</span><br><span class="line">        <span class="comment">// 3.3.写入redis GEOADD key 经度 纬度 member</span></span><br><span class="line">        <span class="keyword">for</span> (Shop shop : value) &#123;</span><br><span class="line">            <span class="comment">// stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());</span></span><br><span class="line">            locations.add(<span class="keyword">new</span> <span class="title class_">RedisGeoCommands</span>.GeoLocation&lt;&gt;(</span><br><span class="line">                    shop.getId().toString(),</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">Point</span>(shop.getX(), shop.getY())</span><br><span class="line">            ));</span><br><span class="line">        &#125;</span><br><span class="line">        stringRedisTemplate.opsForGeo().add(key, locations);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① Map&lt;Long, List<Shop>&gt; map &#x3D; list.stream() .collect(Collectors.groupingBy(Shop::getTypeId));</p><ul><li>用 <code>groupingBy(Shop::getTypeId)</code> 把所有店铺，按<strong>类型 ID</strong>分成一组一组；</li><li>最终得到一个 <code>Map</code>：<strong>key &#x3D; 类型 ID，value &#x3D; 该类型的所有店铺</strong>；</li><li>后续给<strong>每个类型创建一个独立的 Redis GEO Key</strong>（比如 <code>shop:geo:1</code> 是美食店，<code>shop:geo:2</code> 是 KTV），查询时直接查对应 Key，天然过滤类型。</li></ul><p>② 往箱子里「装每个店铺的包裹」</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="keyword">for</span> (Shop shop : value) &#123;</span><br><span class="line">    locations.add(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">GeoLocation</span>&lt;&gt;(</span><br><span class="line">            shop.getId().toString(),  <span class="comment">// 包裹里的「快递单号」：店铺ID</span></span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Point</span>(shop.getX(), shop.getY())  <span class="comment">// 包裹里的「收货地址」：经纬度</span></span><br><span class="line">        )</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><strong><code>shop.getId().toString()</code></strong>：把店铺 ID 转成字符串，作为 Redis GEO 里的 <code>member</code>（相当于快递单号，用来后续查询店铺信息）</li><li><strong><code>new Point(shop.getX(), shop.getY())</code></strong>：封装店铺的经纬度</li></ul><h4 id="实现附近商户功能"><a href="#实现附近商户功能" class="headerlink" title="实现附近商户功能"></a>实现附近商户功能</h4><p>ShopController</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="meta">@GetMapping(&quot;/of/type&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryShopByType</span><span class="params">(</span></span><br><span class="line"><span class="params">        <span class="meta">@RequestParam(&quot;typeId&quot;)</span> Integer typeId,</span></span><br><span class="line"><span class="params">        <span class="meta">@RequestParam(value = &quot;current&quot;, defaultValue = &quot;1&quot;)</span> Integer current,</span></span><br><span class="line"><span class="params">        <span class="meta">@RequestParam(value = &quot;x&quot;, required = false)</span> Double x,</span></span><br><span class="line"><span class="params">        <span class="meta">@RequestParam(value = &quot;y&quot;, required = false)</span> Double y</span></span><br><span class="line"><span class="params">)</span> &#123;</span><br><span class="line">   <span class="keyword">return</span> shopService.queryShopByType(typeId, current, x, y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ShopServiceImpl</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><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">queryShopByType</span><span class="params">(Integer typeId, Integer current, Double x, Double y)</span> &#123;</span><br><span class="line">        <span class="comment">// 1.判断是否需要根据坐标查询</span></span><br><span class="line">        <span class="keyword">if</span> (x == <span class="literal">null</span> || y == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 不需要坐标查询，按数据库查询</span></span><br><span class="line">            Page&lt;Shop&gt; page = query()</span><br><span class="line">                    .eq(<span class="string">&quot;type_id&quot;</span>, typeId)</span><br><span class="line">                    .page(<span class="keyword">new</span> <span class="title class_">Page</span>&lt;&gt;(current, SystemConstants.DEFAULT_PAGE_SIZE));</span><br><span class="line">            <span class="comment">// 返回数据</span></span><br><span class="line">            <span class="keyword">return</span> Result.ok(page.getRecords());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2.计算分页参数</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">from</span> <span class="operator">=</span> (current - <span class="number">1</span>) * SystemConstants.DEFAULT_PAGE_SIZE;</span><br><span class="line">        <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> current * SystemConstants.DEFAULT_PAGE_SIZE;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3.查询redis、按照距离排序、分页。结果：shopId、distance</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> SHOP_GEO_KEY + typeId;</span><br><span class="line">        GeoResults&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; results = stringRedisTemplate.opsForGeo() <span class="comment">// GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE</span></span><br><span class="line">                .search(</span><br><span class="line">                        key,</span><br><span class="line">                        GeoReference.fromCoordinate(x, y),</span><br><span class="line">                        <span class="keyword">new</span> <span class="title class_">Distance</span>(<span class="number">5000</span>),</span><br><span class="line">                        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)</span><br><span class="line">                );</span><br><span class="line">        <span class="comment">// 4.解析出id</span></span><br><span class="line">        <span class="keyword">if</span> (results == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.ok(Collections.emptyList());</span><br><span class="line">        &#125;</span><br><span class="line">        List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; list = results.getContent();</span><br><span class="line">        <span class="keyword">if</span> (list.size() &lt;= from) &#123;</span><br><span class="line">            <span class="comment">// 没有下一页了，结束</span></span><br><span class="line">            <span class="keyword">return</span> Result.ok(Collections.emptyList());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 4.1.截取 from ~ end的部分</span></span><br><span class="line">        List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(list.size());</span><br><span class="line">        Map&lt;String, Distance&gt; distanceMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(list.size());</span><br><span class="line">        list.stream().skip(from).forEach(result -&gt; &#123;</span><br><span class="line">            <span class="comment">// 4.2.获取店铺id</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">shopIdStr</span> <span class="operator">=</span> result.getContent().getName();</span><br><span class="line">            ids.add(Long.valueOf(shopIdStr));</span><br><span class="line">            <span class="comment">// 4.3.获取距离</span></span><br><span class="line">            <span class="type">Distance</span> <span class="variable">distance</span> <span class="operator">=</span> result.getDistance();</span><br><span class="line">            distanceMap.put(shopIdStr, distance);</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 5.根据id查询Shop</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">idStr</span> <span class="operator">=</span> StrUtil.join(<span class="string">&quot;,&quot;</span>, ids);</span><br><span class="line">        List&lt;Shop&gt; shops = query().in(<span class="string">&quot;id&quot;</span>, ids).last(<span class="string">&quot;ORDER BY FIELD(id,&quot;</span> + idStr + <span class="string">&quot;)&quot;</span>).list();</span><br><span class="line">        <span class="keyword">for</span> (Shop shop : shops) &#123;</span><br><span class="line">            shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 6.返回</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(shops);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① .page(new Page&lt;&gt;(current, 每页条数))</p><ul><li><code>current</code>：前端传的<strong>当前页码</strong>（比如第 1 页、第 2 页）</li><li><code>SystemConstants.DEFAULT_PAGE_SIZE</code>：<strong>每页默认显示多少条店铺</strong>（比如 10 条）</li></ul><p>② return Result.ok(page.getRecords());</p><p><code>Page</code> 分页对象里包含了很多分页信息：</p><ul><li>总页数、总条数、当前页、每页条数、<strong>数据列表</strong></li><li><code>getRecords()</code> &#x3D; <strong>获取分页查询到的「店铺数据列表」</strong>（就是我们要展示给用户的店铺集合）</li></ul><p>③ <strong>指定类型 + 5 公里范围内 + 按距离排序</strong> 的店铺</p><table><thead><tr><th>参数</th><th>代码写法</th><th>大白话翻译</th></tr></thead><tbody><tr><td>第 1 个</td><td><code>key</code></td><td>查<strong>哪种类型</strong>的店铺（比如美食店）</td></tr><tr><td>第 2 个</td><td><code>GeoReference.fromCoordinate(x, y)</code></td><td>以<strong>用户当前坐标</strong>为<strong>圆心</strong></td></tr><tr><td>第 3 个</td><td><code>new Distance(5000)</code></td><td>以圆心为中心，画 <strong>5000 米（5 公里）</strong> 的圈</td></tr><tr><td>第 4 个</td><td><code>includeDistance()</code></td><td>必须<strong>计算并返回</strong>每个店铺离用户的距离</td></tr><tr><td>第 4 个</td><td><code>limit(end)</code></td><td>最多查 <code>end</code> 条数据（分页用）</td></tr></tbody></table><p>④ Map&lt;String, Distance&gt; distanceMap &#x3D; new HashMap&lt;&gt;(list.size());</p><ul><li><code>distanceMap</code>：一个<strong>空 Map</strong></li><li>作用：<strong>等会儿存 “店铺 ID → 离你多远”</strong></li><li>比如：<code>105 → 1.2km</code></li></ul><p>⑤ shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());</p><p>.getValue() 的作用是从 <code>Distance</code> 对象里，<strong>提取出纯数字的距离值</strong>（比如 <code>1.2</code>），<code>Distance</code> 对象是 Redis 封装的，包含单位、数值等信息，我们只需要数值给前端展示</p><h3 id="用户签到"><a href="#用户签到" class="headerlink" title="用户签到"></a>用户签到</h3><h4 id="BitMap的操作命令："><a href="#BitMap的操作命令：" class="headerlink" title="BitMap的操作命令："></a>BitMap的操作命令：</h4><ul><li>SETBIT：向指定位置（offset）存入一个0或1</li><li>GETBIT ：获取指定位置（offset）的bit值</li><li>BITCOUNT ：统计BitMap中值为1的bit位的数量</li><li>BITFIELD ：操作（查询、修改、自增）BitMap中bit数组中的指定位置（offset）的值</li><li>BITFIELD_RO ：获取BitMap中bit数组，并以十进制形式返回</li><li>BITOP ：将多个BitMap的结果做位运算（与 、或、异或）</li><li>BITPOS ：查找bit数组中指定范围内第一个0或1出现的位置</li></ul><h4 id="实现签到功能"><a href="#实现签到功能" class="headerlink" title="实现签到功能"></a>实现签到功能</h4><p>UserServiceImpl</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">sign</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取当前登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">// 2.获取日期</span></span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line">    <span class="comment">// 3.拼接key</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">keySuffix</span> <span class="operator">=</span> now.format(DateTimeFormatter.ofPattern(<span class="string">&quot;:yyyyMM&quot;</span>));</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> USER_SIGN_KEY + userId + keySuffix;</span><br><span class="line">    <span class="comment">// 4.获取今天是本月的第几天</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">dayOfMonth</span> <span class="operator">=</span> now.getDayOfMonth();</span><br><span class="line">    <span class="comment">// 5.写入Redis SETBIT key offset 1</span></span><br><span class="line">    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - <span class="number">1</span>, <span class="literal">true</span>);</span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="签到统计"><a href="#签到统计" class="headerlink" title="签到统计"></a>签到统计</h4><p>UserServiceImpl</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">signCount</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取当前登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">// 2.获取日期</span></span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line">    <span class="comment">// 3.拼接key</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">keySuffix</span> <span class="operator">=</span> now.format(DateTimeFormatter.ofPattern(<span class="string">&quot;:yyyyMM&quot;</span>));</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> USER_SIGN_KEY + userId + keySuffix;</span><br><span class="line">    <span class="comment">// 4.获取今天是本月的第几天</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">dayOfMonth</span> <span class="operator">=</span> now.getDayOfMonth();</span><br><span class="line">    <span class="comment">// 5.获取本月截止今天为止的所有的签到记录，返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0</span></span><br><span class="line">    List&lt;Long&gt; result = stringRedisTemplate.opsForValue().bitField(</span><br><span class="line">            key,</span><br><span class="line">            BitFieldSubCommands.create()</span><br><span class="line">                    .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(<span class="number">0</span>)</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">if</span> (result == <span class="literal">null</span> || result.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// 没有任何签到结果</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">num</span> <span class="operator">=</span> result.get(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (num == <span class="literal">null</span> || num == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.ok(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 6.循环遍历</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="comment">// 6.1.让这个数字与1做与运算，得到数字的最后一个bit位  // 判断这个bit位是否为0</span></span><br><span class="line">        <span class="keyword">if</span> ((num &amp; <span class="number">1</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 如果为0，说明未签到，结束</span></span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 如果不为0，说明已签到，计数器+1</span></span><br><span class="line">            count++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 把数字右移一位，抛弃最后一个bit位，继续下一个bit位</span></span><br><span class="line">        num &gt;&gt;&gt;= <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result.ok(count);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① 第一层判断：<code>result == null || result.isEmpty()</code></p><p><code>result</code> 是一个<strong>列表集合</strong>（比如查询数据库返回的 <code>List&lt;Long&gt;</code>）</p><ul><li><code>result == null</code>：<strong>列表对象都不存在</strong>（彻底没数据）</li><li><code>result.isEmpty()</code>：<strong>列表存在，但里面空空如也</strong>（一条数据都没有）</li><li>满足任意一个 → 直接返回 <code>0</code></li></ul><p>② 第二层判断：<code>num == null || num == 0</code></p><ul><li><code>num == null</code>：数据库里这个值是<strong>空值</strong></li><li><code>num == 0</code>：数字本身就是 0</li><li>满足任意一个 → 直接返回 <code>0</code></li></ul><p>③ 举个生活例子</p><ul><li><strong>快递柜都坏了 &#x2F; 根本没这个柜</strong> → 没包裹，返回 0</li><li>快递柜是好的 → 打开柜门</li><li><strong>柜子里空的 &#x2F; 包裹是个空盒子</strong> → 还是没东西，返回 0</li><li>有真实包裹 → 把包裹给你</li></ul>]]></content>
    
    
    <summary type="html">Redis 实战应用：短信验证码、缓存策略、分布式锁、消息队列与秒杀案例</summary>
    
    
    
    <category term="Redis" scheme="https://kon-forever.cloud/categories/Redis/"/>
    
    
    <category term="Redis" scheme="https://kon-forever.cloud/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>Redis学习笔记之基础篇</title>
    <link href="https://kon-forever.cloud/2026/03/29/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E5%9F%BA%E7%A1%80%E7%AF%87/"/>
    <id>https://kon-forever.cloud/2026/03/29/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E5%9F%BA%E7%A1%80%E7%AF%87/</id>
    <published>2026-03-29T02:00:00.000Z</published>
    <updated>2026-09-28T02:39:05.441Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Redis-学习笔记"><a href="#Redis-学习笔记" class="headerlink" title="Redis 学习笔记"></a>Redis 学习笔记</h2><h2 id="基础篇"><a href="#基础篇" class="headerlink" title="基础篇"></a>基础篇</h2><h3 id="认识-Redis"><a href="#认识-Redis" class="headerlink" title="认识 Redis"></a>认识 Redis</h3><h4 id="认识-NoSQL"><a href="#认识-NoSQL" class="headerlink" title="认识 NoSQL"></a>认识 NoSQL</h4><p><strong>NoSql</strong>可以翻译做Not Only Sql（不仅仅是SQL），或者是No Sql（非Sql的）数据库。是相对于传统关系型数据库而言，有很大差异的一种特殊的数据库，因此也称之为<strong>非关系型数据库</strong>。</p><h5 id="结构化与非结构化"><a href="#结构化与非结构化" class="headerlink" title="结构化与非结构化"></a>结构化与非结构化</h5><p>① 传统关系型数据库是结构化数据，每一张表都有严格的约束信息：字段名、字段数据类型、字段约束等等信息，插入的数据必须遵守这些约束：</p><img src="https://i-blog.csdnimg.cn/direct/c8ad967fc1734e80bc9adab8b8a604cb.png" alt="img" style="zoom:67%;" /><p>② 而NoSql则对数据库格式没有严格约束，往往形式松散，自由。可以是键值型、文档型、图格式。</p><h5 id="关联和非关联"><a href="#关联和非关联" class="headerlink" title="关联和非关联"></a>关联和非关联</h5><p>① 传统数据库的表与表之间往往存在关联，例如外键：</p><img src="https://i-blog.csdnimg.cn/direct/8b96f79e34c64b37b7e874ad06488e7b.png" alt="img" style="zoom:67%;" /><p>② 而非关系型数据库不存在关联关系，要维护关系要么靠代码中的业务逻辑，要么靠数据之间的耦合：</p><figure class="highlight javascript"><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">&#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;张三&quot;</span>,</span><br><span class="line">  <span class="attr">orders</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">       <span class="attr">id</span>: <span class="number">1</span>,</span><br><span class="line">       <span class="attr">item</span>: &#123;</span><br><span class="line"> <span class="attr">id</span>: <span class="number">10</span>, <span class="attr">title</span>: <span class="string">&quot;荣耀6&quot;</span>, <span class="attr">price</span>: <span class="number">4999</span></span><br><span class="line">       &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">       <span class="attr">id</span>: <span class="number">2</span>,</span><br><span class="line">       <span class="attr">item</span>: &#123;</span><br><span class="line"> <span class="attr">id</span>: <span class="number">20</span>, <span class="attr">title</span>: <span class="string">&quot;小米11&quot;</span>, <span class="attr">price</span>: <span class="number">3999</span></span><br><span class="line">       &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="查询方式"><a href="#查询方式" class="headerlink" title="查询方式"></a>查询方式</h5><p>① 传统关系型数据库会基于Sql语句做查询，语法有统一标准；</p><p>② 不同的非关系数据库查询语法差异极大，五花八门各种各样。</p> <img src="https://i-blog.csdnimg.cn/direct/4f64291d2f4e48149e7f0f1e468474f6.png" alt="img" style="zoom:67%;" /><h5 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h5><p>① 传统关系型数据库能满足事务ACID的原则。</p><img src="https://i-blog.csdnimg.cn/direct/c68341ed67d14a6d96f5e14c8915f1f1.png" alt="img" style="zoom:67%;" /><p>② 而非关系型数据库往往不支持事务，或者不能严格保证ACID的特性，只能实现基本的一致性。</p><h5 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h5><table><thead><tr><th></th><th>SQL</th><th>NoSQL</th></tr></thead><tbody><tr><td>数据结构</td><td>结构化(Structured)</td><td>非结构化</td></tr><tr><td>数据关联</td><td>关联的(Relational)</td><td>无关联的</td></tr><tr><td>查询方式</td><td>SQL查询</td><td>非SQL</td></tr><tr><td>事务特性</td><td>ACID</td><td>BASE</td></tr><tr><td>存储方式</td><td>磁盘</td><td>内存</td></tr><tr><td>扩展性</td><td>垂直</td><td>水平</td></tr><tr><td>使用场景</td><td>1）数据结构固定  2)对一致性、安全性要求不高</td><td>1)数据结构不固定  2)相关业务对数据安全性、一致性要求较高  3）对性能要求</td></tr></tbody></table><h4 id="Redis-桌面客户端"><a href="#Redis-桌面客户端" class="headerlink" title="Redis 桌面客户端"></a>Redis 桌面客户端</h4><p>(1) Redis安装完成后就自带了命令行客户端：redis-cli，使用方式如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-cli [options] [commonds]</span><br></pre></td></tr></table></figure><p>其中常见的options有：</p><ul><li><code>-h 127.0.0.1</code>：指定要连接的redis节点的IP地址，默认是127.0.0.1</li><li><code>-p 6379</code>：指定要连接的redis节点的端口，默认是6379</li><li><code>-a 123321</code>：指定redis的访问密码</li></ul><h3 id="Redis-常见命令"><a href="#Redis-常见命令" class="headerlink" title="Redis 常见命令"></a>Redis 常见命令</h3><h4 id="Redis-数据结构"><a href="#Redis-数据结构" class="headerlink" title="Redis 数据结构"></a>Redis 数据结构</h4><p>Redis存储的是key-value结构的数据，其中key是字符串类型，value有5中常用的数据类型</p><ul><li>字符串：String</li><li>哈希：Hash</li><li>列表：List</li><li>集合：Set</li><li>有序集合：Sorted Set</li></ul><h4 id="Redis命令-String命令"><a href="#Redis命令-String命令" class="headerlink" title="Redis命令-String命令"></a>Redis命令-String命令</h4><h5 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h5><p>String 类型，也就是字符串类型，是Redis中最简单的存储类型。其value是字符串，根据字符串的格式不同，又可以分为3类：</p><ul><li>string：普通字符串</li><li>int：整数类型，可以做自增、自减操作</li><li>float：浮点类型，可以做自增、自减操作</li></ul><img src="https://i-blog.csdnimg.cn/direct/656b5c02a88e4a4bb105f8949a489a63.png" alt="img" style="zoom:67%;" /><p><strong>说明：</strong></p><p>Redis 没有原生的 “整数类型” “浮点类型” 存储结构，String 类型的所有 value，底层都以 <strong>简单动态字符串（SDS）</strong> 这种字节数组形式存储。</p><ul><li>表格中 <code>num</code> 的 <code>10</code>：底层存的是字符 <code>&#39;1&#39;</code> 和 <code>&#39;0&#39;</code> 的字节，不是编程语言里占 4 字节的整数 10；</li><li>表格中 <code>score</code> 的 <code>92.5</code>：底层存的是字符 <code>&#39;9&#39;</code> <code>&#39;2&#39;</code> <code>&#39;.&#39;</code> <code>&#39;5&#39;</code> 的字节，不是浮点型的二进制存储；</li><li>只有格式上的区别，存储本质完全一致。</li></ul><h5 id="String-的常见命令有："><a href="#String-的常见命令有：" class="headerlink" title="String 的常见命令有："></a>String 的常见命令有：</h5><ul><li>SET：添加或者修改已经存在的一个String类型的键值对</li><li>GET：根据key获取String类型的value</li><li>MSET：批量添加多个String类型的键值对</li><li>MGET：根据多个key获取多个String类型的value</li><li>INCR：让一个整型的key自增1</li><li>INCRBY：让一个整型的key自增并指定步长，例如：incrby num 2 让num值自增2</li><li>SETNX：添加一个String类型的键值对，前提是这个key不存在，否则不执行</li><li>SETEX：添加一个String类型的键值对，并且指定有效期</li></ul><p>① SET 和 GET：如果key不存在则是新增，如果存在则是修改</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></pre></td><td class="code"><pre><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; set name Rose  <span class="comment">//原来不存在</span></span><br><span class="line">OK</span><br><span class="line"></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; get name </span><br><span class="line"><span class="string">&quot;Rose&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; set name Jack <span class="comment">//原来存在，就是修改</span></span><br><span class="line">OK</span><br><span class="line"></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; get name</span><br><span class="line"><span class="string">&quot;Jack&quot;</span></span><br></pre></td></tr></table></figure><p>② INCR 和 INCRBY 和 DECY</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></pre></td><td class="code"><pre><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; get age </span><br><span class="line"><span class="string">&quot;10&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; incr age <span class="comment">//增加1</span></span><br><span class="line">(integer) <span class="number">11</span></span><br><span class="line">    </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; get age <span class="comment">//获得age</span></span><br><span class="line"><span class="string">&quot;11&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; incrby age <span class="number">2</span> <span class="comment">//一次增加2</span></span><br><span class="line">(integer) <span class="number">13</span> <span class="comment">//返回目前的age的值</span></span><br><span class="line">    </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; incrby age <span class="number">2</span></span><br><span class="line">(integer) <span class="number">15</span></span><br><span class="line">    </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; incrby age -<span class="number">1</span> <span class="comment">//也可以增加负数，相当于减</span></span><br><span class="line">(integer) <span class="number">14</span></span><br><span class="line">    </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; incrby age -<span class="number">2</span> <span class="comment">//一次减少2个</span></span><br><span class="line">(integer) <span class="number">12</span></span><br><span class="line">    </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; DECR age <span class="comment">//相当于 incr 负数，减少正常用法</span></span><br><span class="line">(integer) <span class="number">11</span></span><br><span class="line">    </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; get age </span><br><span class="line"><span class="string">&quot;11&quot;</span></span><br></pre></td></tr></table></figure><h4 id="Redis命令-Key的层级结构"><a href="#Redis命令-Key的层级结构" class="headerlink" title="Redis命令-Key的层级结构"></a>Redis命令-Key的层级结构</h4><h5 id="存在的问题"><a href="#存在的问题" class="headerlink" title="存在的问题"></a>存在的问题</h5><p>需要存储用户、商品信息到redis，有一个用户id是1，有一个商品id恰好也是1，此时如果使用id作为key，那就会冲突了，该怎么办？</p><h5 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h5><p>① Redis的key允许有多个单词形成层级结构，多个单词之间用’:’隔开，格式如下：</p><p><img src="https://i-blog.csdnimg.cn/direct/c0df58965d294e2e99a740b5d1bec039.png" alt="img"></p><p>② 例如我们的项目名称叫 heima，有user和product两种不同类型的数据，我们可以这样定义key：</p><ul><li>user相关的key：<strong>heima:user:1</strong></li><li>product相关的key：<strong>heima:product:1</strong></li></ul><h4 id="Redis命令-Hash命令"><a href="#Redis命令-Hash命令" class="headerlink" title="Redis命令-Hash命令"></a>Redis命令-Hash命令</h4><h5 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h5><p>Hash结构可以将对象中的每个字段独立存储，可以针对单个字段做CRUD：</p><img src="https://i-blog.csdnimg.cn/direct/ca20e089c0a74eeba0232b9c2522afd2.png" alt="img" style="zoom:67%;" /><h5 id="Hash类型的常见命令"><a href="#Hash类型的常见命令" class="headerlink" title="Hash类型的常见命令"></a><strong>Hash类型的常见命令</strong></h5><ul><li>HSET key field value：添加或者修改hash类型key的field的值</li><li>HGET key field：获取一个hash类型key的field的值</li><li>HMSET：批量添加多个hash类型key的field的值</li><li>HMGET：批量获取多个hash类型key的field的值</li><li>HGETALL：获取一个hash类型的key中的所有的field和value</li><li>HKEYS：获取一个hash类型的key中的所有的field</li><li>HINCRBY:让一个hash类型key的字段值自增并指定步长</li><li>HSETNX：添加一个hash类型的key的field值，前提是这个field不存在，否则不执行</li></ul><p>① HSET 和 HGET</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="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; HSET heima:user:<span class="number">3</span> name Lucy<span class="comment">//大key是 heima:user:3 小key是name，小value是Lucy</span></span><br><span class="line">(integer) <span class="number">1</span></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; HSET heima:user:<span class="number">3</span> age <span class="number">21</span><span class="comment">// 如果操作不存在的数据，则是新增</span></span><br><span class="line">(integer) <span class="number">1</span></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; HSET heima:user:<span class="number">3</span> age <span class="number">17</span> <span class="comment">//如果操作存在的数据，则是修改</span></span><br><span class="line">(integer) <span class="number">0</span></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; HGET heima:user:<span class="number">3</span> name </span><br><span class="line"><span class="string">&quot;Lucy&quot;</span></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; HGET heima:user:<span class="number">3</span> age</span><br><span class="line"><span class="string">&quot;17&quot;</span></span><br></pre></td></tr></table></figure><p>② HKEYS和HVALS</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="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; HKEYS heima:user:<span class="number">4</span></span><br><span class="line"><span class="number">1</span>) <span class="string">&quot;name&quot;</span></span><br><span class="line"><span class="number">2</span>) <span class="string">&quot;age&quot;</span></span><br><span class="line"><span class="number">3</span>) <span class="string">&quot;sex&quot;</span></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; HVALS heima:user:<span class="number">4</span></span><br><span class="line"><span class="number">1</span>) <span class="string">&quot;LiLei&quot;</span></span><br><span class="line"><span class="number">2</span>) <span class="string">&quot;20&quot;</span></span><br><span class="line"><span class="number">3</span>) <span class="string">&quot;man&quot;</span></span><br></pre></td></tr></table></figure><h4 id="Redis命令-List命令"><a href="#Redis命令-List命令" class="headerlink" title="Redis命令-List命令"></a>Redis命令-List命令</h4><h5 id="特征"><a href="#特征" class="headerlink" title="特征"></a>特征</h5><ul><li>有序</li><li>元素可以重复</li><li>插入和删除快</li><li>查询速度一般</li></ul><h5 id="List的常见命令"><a href="#List的常见命令" class="headerlink" title="List的常见命令"></a><strong>List的常见命令</strong></h5><ul><li>LPUSH key element … ：向列表左侧插入一个或多个元素</li><li>LPOP key：移除并返回列表左侧的第一个元素，没有则返回nil</li><li>RPUSH key element … ：向列表右侧插入一个或多个元素</li><li>RPOP key：移除并返回列表右侧的第一个元素</li><li>LRANGE key star end：返回一段角标范围内的所有元素</li><li>BLPOP和BRPOP：与LPOP和RPOP类似，只不过在没有元素时等待指定时间，而不是直接返回nil</li></ul><p><img src="https://i-blog.csdnimg.cn/direct/c816dcaa45814f9cb54c1e4762e2a203.png" alt="img"></p><h4 id="Redis命令-Set命令"><a href="#Redis命令-Set命令" class="headerlink" title="Redis命令-Set命令"></a>Redis命令-Set命令</h4><h5 id="特征-1"><a href="#特征-1" class="headerlink" title="特征"></a>特征</h5><ul><li>无序</li><li>元素不可重复</li><li>查找快</li><li>支持交集、并集、差集等功能</li></ul><h5 id="常见的命令"><a href="#常见的命令" class="headerlink" title="常见的命令"></a>常见的命令</h5><table><thead><tr><th>命令</th><th>描述</th></tr></thead><tbody><tr><td>SADD key member1 [member2]</td><td>向集合添加一个或多个成员</td></tr><tr><td>SMEMBERS key</td><td>返回集合中的所有成员</td></tr><tr><td>SCARD key</td><td>获取集合的成员数</td></tr><tr><td>SINTER key1 [key2]</td><td>返回给定所有集合的交集</td></tr><tr><td>SUNION key1 [key2]</td><td>返回所有给定集合的并集</td></tr><tr><td>SDIFF key1 [key2]</td><td>返回给定所有集合的差集</td></tr><tr><td>SREM key member1 [member2]</td><td>移除集合中一个或多个成员</td></tr></tbody></table><img src="https://i-blog.csdnimg.cn/direct/505e3f58796b475c927a6a18ee9d3013.png" alt="img" style="zoom:67%;" /><h4 id="Redis命令-SortedSet类型"><a href="#Redis命令-SortedSet类型" class="headerlink" title="Redis命令-SortedSet类型"></a>Redis命令-SortedSet类型</h4><h5 id="特征-2"><a href="#特征-2" class="headerlink" title="特征"></a>特征</h5><p><code>Sorted Set</code>有序集合是<code>String</code>类型元素的集合，且不允许重复的成员。每个元素都会关联一个<code>double</code>类型的分数(<code>score</code>) 。<code>Redis</code>正是通过分数来为集合中的成员进行从小到大排序。有序集合的成员是唯一的，但分数却可以重复。</p><h5 id="常见命令"><a href="#常见命令" class="headerlink" title="常见命令"></a>常见命令</h5><ul><li>ZADD key score member：添加一个或多个元素到sorted set ，如果已经存在则更新其score值</li><li>ZREM key member：删除sorted set中的一个指定元素</li><li>ZSCORE key member : 获取sorted set中的指定元素的score值</li><li>ZRANK key member：获取sorted set 中的指定元素的排名</li><li>ZCARD key：获取sorted set中的元素个数</li><li>ZCOUNT key min max：统计score值在给定范围内的所有元素的个数</li><li>ZINCRBY key increment member：让sorted set中的指定元素自增，步长为指定的increment值</li><li>ZRANGE key min max：按照score排序后，获取指定排名范围内的元素</li><li>ZRANGEBYSCORE key min max：按照score排序后，获取指定score范围内的元素</li><li>ZDIFF、ZINTER、ZUNION：求差集、交集、并集<strong>三、</strong></li></ul><h3 id="Redis的Java客户端-Jedis（了解即可）"><a href="#Redis的Java客户端-Jedis（了解即可）" class="headerlink" title="Redis的Java客户端 - Jedis（了解即可）"></a>Redis的Java客户端 - Jedis（了解即可）</h3><h4 id="操作步骤"><a href="#操作步骤" class="headerlink" title="操作步骤"></a>操作步骤</h4><p>① 引入依赖</p><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><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--jedis--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>redis.clients<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jedis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.7.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--单元测试--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.junit.jupiter<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-jupiter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>5.7.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>② 建立连接</p><p>新建一个单元测试类，内容如下：</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">private</span> Jedis jedis;</span><br><span class="line"></span><br><span class="line"><span class="meta">@BeforeEach</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">setUp</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.建立连接</span></span><br><span class="line">    <span class="comment">// jedis = new Jedis(&quot;192.168.150.101&quot;, 6379);</span></span><br><span class="line">    jedis = JedisConnectionFactory.getJedis();</span><br><span class="line">    <span class="comment">// 2.设置密码</span></span><br><span class="line">    jedis.auth(<span class="string">&quot;123321&quot;</span>);</span><br><span class="line">    <span class="comment">// 3.选择库</span></span><br><span class="line">    jedis.select(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>③ 测试</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="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testString</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 存入数据</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> jedis.set(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;虎哥&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;result = &quot;</span> + result);</span><br><span class="line">    <span class="comment">// 获取数据</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> jedis.get(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;name = &quot;</span> + name);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testHash</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 插入hash数据</span></span><br><span class="line">    jedis.hset(<span class="string">&quot;user:1&quot;</span>, <span class="string">&quot;name&quot;</span>, <span class="string">&quot;Jack&quot;</span>);</span><br><span class="line">    jedis.hset(<span class="string">&quot;user:1&quot;</span>, <span class="string">&quot;age&quot;</span>, <span class="string">&quot;21&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取</span></span><br><span class="line">    Map&lt;String, String&gt; map = jedis.hgetAll(<span class="string">&quot;user:1&quot;</span>);</span><br><span class="line">    System.out.println(map);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>④ 释放资源</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@AfterEach</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">tearDown</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (jedis != <span class="literal">null</span>) &#123;</span><br><span class="line">        jedis.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Jedis连接池"><a href="#Jedis连接池" class="headerlink" title="Jedis连接池"></a>Jedis连接池</h4><h5 id="创建Jedis的连接池"><a href="#创建Jedis的连接池" class="headerlink" title="创建Jedis的连接池"></a>创建Jedis的连接池</h5><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">static</span> &#123; <span class="comment">// 静态代码块：类加载时自动执行，只执行一次</span></span><br><span class="line">    <span class="comment">// 1. 配置连接池的“规则”</span></span><br><span class="line">    <span class="type">JedisPoolConfig</span> <span class="variable">poolConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JedisPoolConfig</span>();</span><br><span class="line">    poolConfig.setMaxTotal(<span class="number">8</span>);      <span class="comment">// 最多同时有 8 个连接（满了就等）</span></span><br><span class="line">    poolConfig.setMaxIdle(<span class="number">8</span>);       <span class="comment">// 最多保留 8 个“空闲”的连接（多余的释放）</span></span><br><span class="line">    poolConfig.setMinIdle(<span class="number">0</span>);       <span class="comment">// 最少保留 0 个空闲连接（没有就新建）</span></span><br><span class="line">    poolConfig.setMaxWaitMillis(<span class="number">1000</span>); <span class="comment">// 没连接时最多等 1000 毫秒（1秒），超时就报错</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2. 根据规则，创建真正的连接池</span></span><br><span class="line">    jedisPool = <span class="keyword">new</span> <span class="title class_">JedisPool</span>(</span><br><span class="line">        poolConfig,          <span class="comment">// 刚才的规则</span></span><br><span class="line">        <span class="string">&quot;192.168.150.101&quot;</span>,   <span class="comment">// Redis 服务器的 IP</span></span><br><span class="line">        <span class="number">6379</span>,                <span class="comment">// Redis 端口</span></span><br><span class="line">        <span class="number">1000</span>,                <span class="comment">// 连接超时时间（毫秒）</span></span><br><span class="line">        <span class="string">&quot;123321&quot;</span>             <span class="comment">// Redis 密码</span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Jedis <span class="title function_">getJedis</span><span class="params">()</span> &#123; <span class="comment">// 对外提供的“借连接”的方法</span></span><br><span class="line">    <span class="keyword">return</span> jedisPool.getResource(); <span class="comment">// 从连接池里拿一个可用的连接</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Redis的Java客户端-SpringDataRedis"><a href="#Redis的Java客户端-SpringDataRedis" class="headerlink" title="Redis的Java客户端-SpringDataRedis"></a>Redis的Java客户端-SpringDataRedis</h3><h4 id="介绍-1"><a href="#介绍-1" class="headerlink" title="介绍"></a>介绍</h4><p>SpringDataRedis中提供了RedisTemplate工具类，其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中：</p><img src="https://i-blog.csdnimg.cn/direct/bcb31babe903442b8add783b2a6b17be.png" alt="img" style="zoom:67%;" /><h4 id="快速入门"><a href="#快速入门" class="headerlink" title="快速入门"></a>快速入门</h4><p>① 需要导入它的maven坐标</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--Spring Boot-redis的依赖包--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>② 配置文件</p><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">spring:</span><br><span class="line">  redis:</span><br><span class="line">    host: 192.168.150.101</span><br><span class="line">    port: 6379</span><br><span class="line">    password: 123321</span><br><span class="line">    lettuce:</span><br><span class="line">      pool:</span><br><span class="line">        max-active: 8  #最大连接</span><br><span class="line">        max-idle: 8   #最大空闲连接</span><br><span class="line">        min-idle: 0   #最小空闲连接</span><br><span class="line">        max-wait: 100ms #连接等待时间</span><br></pre></td></tr></table></figure><p>③ 测试代码</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RedisDemoApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate&lt;String, Object&gt; redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 写入一条String数据</span></span><br><span class="line">        redisTemplate.opsForValue().set(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;虎哥&quot;</span>);</span><br><span class="line">        <span class="comment">// 获取string数据</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">name</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;name = &quot;</span> + name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="数据序列化器"><a href="#数据序列化器" class="headerlink" title="数据序列化器"></a>数据序列化器</h4><h5 id="自定义RedisTemplate的序列化方式，代码如下："><a href="#自定义RedisTemplate的序列化方式，代码如下：" class="headerlink" title="自定义RedisTemplate的序列化方式，代码如下："></a>自定义RedisTemplate的序列化方式，代码如下：</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisTemplate&lt;String, Object&gt; <span class="title function_">redisTemplate</span><span class="params">(RedisConnectionFactory connectionFactory)</span>&#123;</span><br><span class="line">        <span class="comment">// 创建RedisTemplate对象</span></span><br><span class="line">        RedisTemplate&lt;String, Object&gt; template = <span class="keyword">new</span> <span class="title class_">RedisTemplate</span>&lt;&gt;();</span><br><span class="line">        <span class="comment">// 设置连接工厂</span></span><br><span class="line">        template.setConnectionFactory(connectionFactory);</span><br><span class="line">        <span class="comment">// 创建JSON序列化工具</span></span><br><span class="line">        <span class="type">GenericJackson2JsonRedisSerializer</span> <span class="variable">jsonRedisSerializer</span> <span class="operator">=</span> </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>();</span><br><span class="line">        <span class="comment">// 设置Key的序列化</span></span><br><span class="line">        template.setKeySerializer(RedisSerializer.string());</span><br><span class="line">        template.setHashKeySerializer(RedisSerializer.string());</span><br><span class="line">        <span class="comment">// 设置Value的序列化</span></span><br><span class="line">        template.setValueSerializer(jsonRedisSerializer);</span><br><span class="line">        template.setHashValueSerializer(jsonRedisSerializer);</span><br><span class="line">        <span class="comment">// 返回</span></span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>之所以要写 HashKey 和 HashValue，是因为Redis 里的 <code>Hash</code> 类型，就像一个「嵌套的 Map」：</p><ul><li>顶层结构：<code>RedisKey（顶层Key） → Hash结构（顶层Value）</code></li><li>Hash 内部结构：<code>HashKey → HashValue</code></li></ul><h4 id="StringRedisTemplate"><a href="#StringRedisTemplate" class="headerlink" title="StringRedisTemplate"></a>StringRedisTemplate</h4><h5 id="存在的问题-1"><a href="#存在的问题-1" class="headerlink" title="存在的问题"></a>存在的问题</h5><p>默认情况下，<code>RedisTemplate</code> 使用的序列化器（比如 JdkSerializationRedisSerializer）会把对象的 <strong>完整类信息（class 类型）</strong> 也写入到 JSON 结果中，再存入 Redis。</p><p>比如一个 <code>User</code> 对象，序列化后可能变成这样：</p><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;@class&quot;: &quot;com.example.User&quot;,</span><br><span class="line">  &quot;name&quot;: &quot;Jack&quot;,</span><br><span class="line">  &quot;age&quot;: 21</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的 <code>&quot;@class&quot;: &quot;com.example.User&quot;</code> 就是额外的类信息，它会占用不必要的内存空间。</p><h5 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h5><ul><li>先使用<code>ObjectMapper</code>把对象手动转换成纯 JSON 字符串（不带 class 信息）。</li><li>再用 <code>StringRedisTemplate</code>把这个 JSON 字符串存入 Redis。</li><li>读取时，先从 Redis 取出 JSON 字符串，再手动用 Jackson 把它反序列化为对象。</li></ul><h5 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RedisStringTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 写入一条String数据</span></span><br><span class="line">        stringRedisTemplate.opsForValue().set(<span class="string">&quot;verify:phone:13600527634&quot;</span>, <span class="string">&quot;124143&quot;</span>);</span><br><span class="line">        <span class="comment">// 获取string数据</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">name</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;name = &quot;</span> + name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ObjectMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testSaveUser</span><span class="params">()</span> <span class="keyword">throws</span> JsonProcessingException &#123;</span><br><span class="line">        <span class="comment">// 创建对象</span></span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;虎哥&quot;</span>, <span class="number">21</span>);</span><br><span class="line">        <span class="comment">// 手动序列化</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> mapper.writeValueAsString(user);</span><br><span class="line">        <span class="comment">// 写入数据</span></span><br><span class="line">        stringRedisTemplate.opsForValue().set(<span class="string">&quot;user:200&quot;</span>, json);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取数据</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">jsonUser</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(<span class="string">&quot;user:200&quot;</span>);</span><br><span class="line">        <span class="comment">// 手动反序列化</span></span><br><span class="line">        <span class="type">User</span> <span class="variable">user1</span> <span class="operator">=</span> mapper.readValue(jsonUser, User.class);</span><br><span class="line">        System.out.println(<span class="string">&quot;user1 = &quot;</span> + user1);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Hash结构操作"><a href="#Hash结构操作" class="headerlink" title="Hash结构操作"></a>Hash结构操作</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RedisStringTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testHash</span><span class="params">()</span> &#123;</span><br><span class="line">        stringRedisTemplate.opsForHash().put(<span class="string">&quot;user:400&quot;</span>, <span class="string">&quot;name&quot;</span>, <span class="string">&quot;虎哥&quot;</span>);</span><br><span class="line">        stringRedisTemplate.opsForHash().put(<span class="string">&quot;user:400&quot;</span>, <span class="string">&quot;age&quot;</span>, <span class="string">&quot;21&quot;</span>);</span><br><span class="line"></span><br><span class="line">        Map&lt;Object, Object&gt; entries = stringRedisTemplate.opsForHash().entries(<span class="string">&quot;user:400&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;entries = &quot;</span> + entries);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Redis 基础入门：NoSQL 概述、Redis 数据类型、常用命令与 Jedis 客户端操作</summary>
    
    
    
    <category term="Redis" scheme="https://kon-forever.cloud/categories/Redis/"/>
    
    
    <category term="Redis" scheme="https://kon-forever.cloud/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>MySQL学习笔记之运维篇</title>
    <link href="https://kon-forever.cloud/2026/03/28/MySQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E8%BF%90%E7%BB%B4%E7%AF%87/"/>
    <id>https://kon-forever.cloud/2026/03/28/MySQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E8%BF%90%E7%BB%B4%E7%AF%87/</id>
    <published>2026-03-28T04:00:00.000Z</published>
    <updated>2026-09-28T02:40:41.141Z</updated>
    
    <content type="html"><![CDATA[<h3 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h3><h4 id="错误日志"><a href="#错误日志" class="headerlink" title="错误日志"></a>错误日志</h4><p>(1) 错误日志是 MySQL 中最重要的日志之一，它记录了当 mysqld 启动和停止时，以及服务器在运行过程中发生任何严重错误时的相关信息当数据库出现任何故障导致无法正常使用时，建议首先查看此日志。</p><p>(2) 该日志是默认开启的，默认存放目录 &#x2F;var&#x2F;log&#x2F;，默认的日志文件名为 mysqld.log 。查看日志位置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show variables like <span class="string">&#x27;%log_error%&#x27;</span></span><br></pre></td></tr></table></figure><h4 id="二进制日志"><a href="#二进制日志" class="headerlink" title="二进制日志"></a>二进制日志</h4><p>(1) 二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句，但不包括数据查询(SELECT、SHOW)语句。</p><p>(2) 作用：</p><p>① 灾难时的数据恢复；</p><p>② MySQL的主从复制。</p><p>在MySQL8版本中，默认二进制日志是开启着的，涉及到的参数如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show variables like <span class="string">&#x27;%log_bin%&#x27;</span></span><br></pre></td></tr></table></figure><p>(3) 日志格式</p><p>MySQL服务器中提供了多种格式来记录二进制记录，具体格式及特点如下：</p><table><thead><tr><th>日志格式</th><th>含义</th></tr></thead><tbody><tr><td>statement</td><td>基于SQL语句的日志记录，记录的是SQL语句，对数据进行修改的SQL都会记录在日志文件中。</td></tr><tr><td>row</td><td>基于行的日志记录，记录的是每一行的数据变更。(默认)</td></tr><tr><td>mixed</td><td>混合了STATEMENT和ROW两种格式，默认采用STATEMENT，在某些特殊情况下会自动切换为ROW进行记录。</td></tr></tbody></table><p>查看参数方式：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show variables like <span class="string">&#x27;%binlog_format%&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(4) 日志查看</p><p>由于日志是以二进制方式存储的，不能直接读取，需要通过二进制日志查询工具 mysqlbinlog 来查看，具体语法：</p><figure class="highlight bash"><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">mysqlbinlog[参数选项]logfilename</span><br><span class="line"></span><br><span class="line">参数选项：</span><br><span class="line">    -d    指定数据库名称，只列出指定的数据库相关的操作。</span><br><span class="line">    -o    忽略掉日志中的前n行命令。</span><br><span class="line">    -v    将行事件(数据变更)重构为SOL语句。</span><br><span class="line">    -vv    将行事件(数据变更)重构为SQL语句，并输出注释信息</span><br></pre></td></tr></table></figure><p>(5) 日志删除</p><p>对于比较繁忙的业务系统，每天生成的binlog数据巨大，如果长时间不清除，将会占用大量磁盘空间。可以通过以下几种方式清理日志：</p><table><thead><tr><th>指令</th><th>含义</th></tr></thead><tbody><tr><td>reset master</td><td>删除全部 binlog 日志，删除之后，日志编号，将从 binlog.000001重新开始</td></tr><tr><td>purge master logs to ‘binlog.***’</td><td>删除 *** 编号之前的所有日志</td></tr><tr><td>purge master logs before ‘yyyy-mm-dd hh24:mi:ss’</td><td>删除日志为”yyyy-mm-dd hh24:mi:ss”之前产生的所有日志</td></tr></tbody></table><p>也可以在mysql的配置文件中配置二进制日志的过期时间，设置了之后，二进制日志过期会自动删除。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show variables like <span class="string">&#x27;%binlog_expire_logs_seconds%&#x27;</span></span><br></pre></td></tr></table></figure><h4 id="查询日志"><a href="#查询日志" class="headerlink" title="查询日志"></a>查询日志</h4><p>(1) 查询日志中记录了客户端的所有操作语句，而二进制日志不包含查询数据的SQL语句。默认情况下，查询日志是未开启的。如果需要开启查询日志，可以设置一下配置：</p><p>修改MySQL的配置文件 &#x2F;etc&#x2F;my.cnf 文件，添加如下内容：</p><figure class="highlight bash"><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="comment">#该选项用来开启查询日志，可选值：0或者1；0代表关闭，1代表开启</span></span><br><span class="line">general_log=1</span><br><span class="line"><span class="comment">#设置日志的文件名 ， 如果没有指定，默认的文件名为 host_name.log</span></span><br><span class="line">general_log_file=mysql_query.log</span><br></pre></td></tr></table></figure><h4 id="慢查询日志"><a href="#慢查询日志" class="headerlink" title="慢查询日志"></a>慢查询日志</h4><p>(1) 慢查询日志记录了所有执行时间超过参数 long_query_time 设置值并且扫描记录数不小于 min_examined_row_limit的所有的SQL语句的日志，默认未开启。long_query_time 默认为 10 秒，最小为0，精度可以到微秒。</p><figure class="highlight bash"><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="comment">#慢查询日志</span></span><br><span class="line">slow_query_log=1</span><br><span class="line"><span class="comment">#执行时间参数</span></span><br><span class="line">long_query_time=2</span><br></pre></td></tr></table></figure><p>(2) 默认情况下，不会记录管理语句，也不会记录不使用索引进行查找的查询。可以使用log_slow_admin_statements和更改此行为log_queries_not_using_indexes，如下所述。</p><figure class="highlight bash"><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="comment">#记录执行较慢的管理语句</span></span><br><span class="line">log_slow_admin_statements = 1</span><br><span class="line"><span class="comment">#记录执行较慢的未使用索引的语句</span></span><br><span class="line">log_queries_not_using_indexes = 1</span><br></pre></td></tr></table></figure><h3 id="主从复制"><a href="#主从复制" class="headerlink" title="主从复制"></a>主从复制</h3><h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>(1) 主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志传到从库服务器中，然后在从库上对这些日志重新执行（也叫做重做），从而使得从库和主库的数据保持同步。</p><p>(2) MySQL 支持一台主库同时向多台从库进行复制，从库同时也可以作为其他服务器的主库，实现链式复制。</p><p>(3) MySQL 复制的优点主要包含以下三个方面：</p><p>① 主库出现问题，可以快速切换到从库提供服务。</p><p>② 实现读写分离，降低主库的访问压力。</p><p>③ 可以在从库中执行备份，以避免备份期间影响主服务器。</p><h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><img src="https://i-blog.csdnimg.cn/direct/1995691d74904bae8145f9d4e7e8aec7.png" alt="img" style="zoom:67%;" /><p>从上图来看，复制分成三步：</p><p>(1) Master 主库在事务提交时，会把数据变更记录在二进制日志文件 Binlog 中。</p><p>(2) 从库读取主库的二进制日志文件 Binlog，写入到从库的中继日志 Relay log。</p><p>(3) slave 重做中继日志中的事件，将改变反映它自己的数据。</p><h4 id="搭建"><a href="#搭建" class="headerlink" title="搭建"></a>搭建</h4><p><img src="https://i-blog.csdnimg.cn/direct/b49db5a2f7ca48a48d48800da7b37344.png" alt="img"></p><h4 id="主库配置"><a href="#主库配置" class="headerlink" title="主库配置"></a>主库配置</h4><p>(1) 修改配置文件 &#x2F;etc&#x2F;my.cnf</p><figure class="highlight bash"><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="comment">#mysql 服务ID，保证整个集群环境中唯一，取值范围：1 - 2^32-1，默认为1</span></span><br><span class="line">server-id=1</span><br><span class="line"><span class="comment">#是否只读 1代表只读，0代表读写</span></span><br><span class="line">read-only=0</span><br><span class="line"><span class="comment">#忽略的数据库，指不需要同步的数据库</span></span><br><span class="line"><span class="comment">#binlog-ignore-db=mysql</span></span><br><span class="line"><span class="comment">#指定同步的数据库</span></span><br><span class="line"><span class="comment">#binlog-do-db=db01</span></span><br></pre></td></tr></table></figure><p>(2) 重启 MySQL 服务器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart mysqld</span><br></pre></td></tr></table></figure><p>(3) 登录 mysql，创建远程连接的账号，并授予主从复制权限</p><figure class="highlight bash"><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="comment">#创建itcast用户，并设置密码，该用户可在任意主机连接该MySQL服务</span></span><br><span class="line">CREATE USER <span class="string">&#x27;itcast&#x27;</span>@<span class="string">&#x27;%&#x27;</span> IDENTIFIED WITH mysql_native_password BY <span class="string">&#x27;Root@123456&#x27;</span>;</span><br><span class="line"><span class="comment">#为&#x27;itcast&#x27;@&#x27;%&#x27;用户分配主从复制权限</span></span><br><span class="line">GRANT REPLICATION SLAVE ON *.* TO <span class="string">&#x27;itcast&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(4) 通过指令，查看二进制日志坐标</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show master status;</span><br></pre></td></tr></table></figure><p>字段含义说明：</p><p>file：从哪个日志文件开始推送日志文件</p><p>position：从哪个位置开始推送日志</p><p>binlog_ignore_db：指定不需要同步的数据库</p><h4 id="从库配置"><a href="#从库配置" class="headerlink" title="从库配置"></a>从库配置</h4><p>(1) 修改配置文件 &#x2F;etc&#x2F;my.cnf</p><figure class="highlight bash"><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="comment">#mysql服务ID，保证整个集群环境中唯一，取值范围：1 - 2^32-1，和主库不一样即可</span></span><br><span class="line">server-id=2</span><br><span class="line"><span class="comment">#是否只读：1代表只读，0代表读写</span></span><br><span class="line">read-only=1</span><br></pre></td></tr></table></figure><p>(2) 重新启动 MySQL 服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart mysqld</span><br></pre></td></tr></table></figure><p>(3) 登录 mysql，设置主库配置</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CHANGE REPLICATION SOURCE TO SOURCE_HOST=<span class="string">&#x27;xxx&#x27;</span>, SOURCE_USER=<span class="string">&#x27;xxx&#x27;</span>, SOURCE_PASSWORD=<span class="string">&#x27;xxx&#x27;</span>, SOURCE_LOG_FILE=<span class="string">&#x27;xxx&#x27;</span>, SOURCE_LOG_POS=xxx;</span><br></pre></td></tr></table></figure><p>上述是 8.0.23 中的语法。如果 mysql 是 8.0.23 之前的版本，执行如下 SQL：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CHANGE MASTER TO MASTER_HOST=<span class="string">&#x27;xxx.xxx.xxx.xxx&#x27;</span>, MASTER_USER=<span class="string">&#x27;xxx&#x27;</span>, MASTER_PASSWORD=<span class="string">&#x27;xxx&#x27;</span>, MASTER_LOG_FILE=<span class="string">&#x27;xxx&#x27;</span>, MASTER_LOG_POS=xxx;</span><br></pre></td></tr></table></figure><table><thead><tr><th>参数名</th><th>含义</th><th>8.0.23 之前</th></tr></thead><tbody><tr><td>SOURCE_HOST</td><td>主库 IP 地址</td><td>MASTER_HOST</td></tr><tr><td>SOURCE_USER</td><td>连接主库的用户名</td><td>MASTER_USER</td></tr><tr><td>SOURCE_PASSWORD</td><td>连接主库的密码</td><td>MASTER_PASSWORD</td></tr><tr><td>SOURCE_LOG_FILE</td><td>binlog 日志文件名</td><td>MASTER_LOG_FILE</td></tr><tr><td>SOURCE_LOG_POS</td><td>binlog 日志文件位置</td><td>MASTER_LOG_POS</td></tr></tbody></table><p>(4) 开启同步操作</p><figure class="highlight bash"><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">start replica ;  <span class="comment">#8.0.22之后</span></span><br><span class="line">start slave ;    <span class="comment">#8.0.22之前</span></span><br></pre></td></tr></table></figure><p>(5) 查看主从同步状态</p><figure class="highlight bash"><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">show replica status ;  <span class="comment">#8.0.22之后</span></span><br><span class="line">show slave status ;    <span class="comment">#8.0.22之前</span></span><br></pre></td></tr></table></figure><h4 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h4><p>(1) 在主库上创建数据库、表，并插入数据</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><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> database db01;</span><br><span class="line">use db01;</span><br><span class="line"><span class="keyword">create table</span> tb_use(</span><br><span class="line">id <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">primary key</span> <span class="keyword">not null</span> auto_increment,</span><br><span class="line">name <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">not null</span>,</span><br><span class="line">sex <span class="type">varchar</span>(<span class="number">1</span>)</span><br><span class="line">)engine<span class="operator">=</span>innodb <span class="keyword">default</span> charset<span class="operator">=</span>utf8mb4;</span><br><span class="line"><span class="keyword">insert into</span> tb_user(id, name, sex) valurs (<span class="keyword">null</span>, <span class="string">&#x27;Tom&#x27;</span>, <span class="string">&#x27;1&#x27;</span>), (<span class="keyword">null</span>, <span class="string">&#x27;Trigger&#x27;</span>, <span class="string">&#x27;0&#x27;</span>), (<span class="keyword">null</span>, <span class="string">&#x27;Dawn&#x27;</span>, <span class="string">&#x27;1&#x27;</span>);</span><br></pre></td></tr></table></figure><p>(2) 在从库中查询数据，验证主从是否同步。</p><h3 id="分库分表"><a href="#分库分表" class="headerlink" title="分库分表"></a>分库分表</h3><h4 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h4><p>(1) 问题分析</p><p>随着互联网及移动互联网的发展，应用系统的数据量也是成指数式增长，若采用单数据库进行数据存储，存在以下性能瓶颈：</p><p>① I&#x2F;O 瓶颈：热点数据太多，数据库缓存不足，产生大量磁盘 I&#x2F;O，效率较低。请求数据太多，带宽不够，网络 I&#x2F;O 瓶颈。</p><p>② CPU 瓶颈：排序、分组、连接查询、聚合统计等 SQL 会耗费大量的 CPU 资源，请求数太多，CPU 出现瓶颈。</p><p>分库分表的中心思想都是将数据分散存储，使得单一数据库 &#x2F; 表的数据量变小来缓解单一数据库的性能问题，从而达到提升数据库性能的目的。</p><p>(2) 拆分策略</p><p><img src="https://i-blog.csdnimg.cn/direct/51c6a3212fee45cc888ff4556108a4cf.png" alt="img"></p><p>(3) 垂直拆分</p><p>① 垂直分库</p><img src="https://i-blog.csdnimg.cn/direct/f4562b894ab843c0b4093156e5be8268.png" alt="img" style="zoom:50%;" /><p>以表为依据，根据业务将不同表拆分到不同库中。</p><p>特点：</p><ul><li>每个库的表结构都不一样。</li><li>每个库的数据也不一样。</li><li>所有库的并集是全量数据。</li></ul><p>② 垂直分表</p><img src="https://i-blog.csdnimg.cn/direct/78648e3f32bd4555b66edba7c2d2245a.png" alt="img" style="zoom:50%;" /><p>以字段为依据，根据字段属性将不同字段拆分到不同表中。</p><p>特点：</p><ul><li>每个表的结构都不一样。</li><li>每个表的数据也不一样，一般通过一列（主键 &#x2F; 外键）关联。</li><li>所有表的并集是全量数据。</li></ul><p>(4) 水平拆分</p><p>① 水平分库</p><img src="https://i-blog.csdnimg.cn/direct/70bb4993d9ff41e88f80d6ba789f6b0e.png" alt="img" style="zoom:50%;" /><p>以字段为依据，按照一定策略，将一个库的数据拆分到多个库中。</p><p>特点：</p><ul><li>每个库的表结构都一样。</li><li>每个库的数据都不一样。</li><li>所有库的并集是全量数据。</li></ul><p>② 水平分表</p><img src="https://i-blog.csdnimg.cn/direct/e20c779df7c44b03a93b228f39b9993c.png" alt="img" style="zoom: 50%;" /><p>以字段为依据，按照一定策略，将一个表的数据拆分到多个表中。</p><p>特点：</p><ul><li>每个表的表结构都一样。</li><li>每个表的数据都不一样。</li><li>所有表的并集是全量数据。</li></ul><h4 id="Mycat-入门"><a href="#Mycat-入门" class="headerlink" title="Mycat 入门"></a>Mycat 入门</h4><p>(1) 安装</p><p>Mycat 是采用 java 语言开发的开源的数据库中间件，支持 Windows 和 Linux 运行环境，下面介绍 Mycat 在 Linux 中的环境搭建，我们需要在准备好的服务器中安装如下软件。</p><ul><li>MySQL</li><li>JDK</li><li>Mycat</li></ul><table><thead><tr><th>服务器</th><th>安装软件</th><th>说明</th></tr></thead><tbody><tr><td>192.168.200.210</td><td>JDK、Mycat</td><td>Mycat 中间件服务器</td></tr><tr><td>192.168.200.210</td><td>MySQL</td><td>分片服务器</td></tr><tr><td>192.168.200.213</td><td>MySQL</td><td>分片服务器</td></tr><tr><td>192.168.200.214</td><td>MySQL</td><td>分片服务器</td></tr></tbody></table><p>(2) 概念介绍</p><img src="https://i-blog.csdnimg.cn/direct/0e4daa08c0184e36aec31200ed6790fc.png" alt="img" style="zoom: 50%;" /><p>(3) 需求</p><p>由于 tb_order 表中数据量很大，磁盘 IO 及容量都到达了瓶颈，现在需要对 tb_order 表进行数据分片，分为三个数据节点，每一个节点主机位于不同的服务器上，具体的结构，参考下图：</p><img src="https://i-blog.csdnimg.cn/direct/254354b1e86f4af8a1e5f145cfd1d62a.png" alt="img" style="zoom:50%;" /><p>(4) 分片配置</p><p><img src="https://i-blog.csdnimg.cn/direct/4b47c4415a1e45609efe6ad252faac58.png" alt="img"></p><p><img src="https://i-blog.csdnimg.cn/direct/8c5673245b744940a264fdb43551f95c.png" alt="img" style="zoom:50%;" />编</p><p>(5) 启动服务</p><p>切换到 Mycat 的安装目录，执行如下指令，启动 Mycat：</p><figure class="highlight bash"><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="comment">#启动</span></span><br><span class="line">bin/mycat start</span><br><span class="line"><span class="comment">#停止</span></span><br><span class="line">bin/mycat stop</span><br></pre></td></tr></table></figure><p>Mycat 启动之后，占用端口号 8066。</p><p>启动完毕之后，可以查看 logs 目录下的启动日志，查看 Mycat 是否启动完成。</p><p><img src="https://i-blog.csdnimg.cn/direct/78baa6f05ef24990bbb9f8a45fb2914a.png" alt="img"></p><p>(6) 分片测试</p><p>① 通过如下指令，就可以连接并操作 Mycat。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql -h 192.168.200.210 -P 8066 -uroot -p123456</span><br></pre></td></tr></table></figure><p>② 然后就可以在 Mycat 中创建表，并往结构中插入数据，查看数据在 MySQL 中的分布情况。</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><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="keyword">CREATE TABLE</span> TB_ORDER(</span><br><span class="line">  id <span class="type">BIGINT</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  title <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  <span class="keyword">PRIMARY KEY</span> (id)</span><br><span class="line">)ENGINE<span class="operator">=</span>INNODB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8;</span><br><span class="line"></span><br><span class="line"><span class="keyword">INSERT INTO</span> TB_ORDER(id,title) <span class="keyword">VALUES</span>(<span class="number">1</span>,<span class="string">&#x27;goods1&#x27;</span>);</span><br><span class="line"><span class="keyword">INSERT INTO</span> TB_ORDER(id,title) <span class="keyword">VALUES</span>(<span class="number">2</span>,<span class="string">&#x27;goods2&#x27;</span>);</span><br><span class="line"><span class="keyword">INSERT INTO</span> TB_ORDER(id,title) <span class="keyword">VALUES</span>(<span class="number">3</span>,<span class="string">&#x27;goods3&#x27;</span>);</span><br><span class="line"><span class="keyword">INSERT INTO</span> TB_ORDER(id,title) <span class="keyword">VALUES</span>(<span class="number">1000000</span>,<span class="string">&#x27;goods1000000&#x27;</span>);</span><br><span class="line"><span class="keyword">INSERT INTO</span> TB_ORDER(id,title) <span class="keyword">VALUES</span>(<span class="number">10000000</span>,<span class="string">&#x27;goods10000000&#x27;</span>);</span><br></pre></td></tr></table></figure><h4 id="MyCat-配置"><a href="#MyCat-配置" class="headerlink" title="MyCat 配置"></a>MyCat 配置</h4><p>(1) schema.xml</p><p>schema.xml作为MyCat中最重要的配置文件之一,涵盖了MyCat的逻辑库 、逻辑表 、分片规则、分片节点及数据源的配置。</p><p>主要包含以下三组标签：</p><ul><li>schema标签</li><li>datanode标签</li><li>datahost标签</li></ul><p>① schema标签（第一行）</p><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">schema</span> <span class="attr">name</span>=<span class="string">&quot;DB01&quot;</span> <span class="attr">checkSQLschema</span>=<span class="string">&quot;true&quot;</span> <span class="attr">sqlMaxLimit</span>=<span class="string">&quot;100&quot;</span> &gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;TB_ORDER&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1,dn2,dn3&quot;</span> <span class="attr">rule</span>=<span class="string">&quot;auto-sharding-long&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">schema</span>&gt;</span></span><br></pre></td></tr></table></figure><p>schema 标签用于定义 MyCat 实例中的逻辑库，一个 MyCat 实例中，可以有多个逻辑库，可以通过 schema 标签来划分不同的逻辑库。</p><p>MyCat 中的逻辑库的概念，等同于 MySQL 中的 database 概念，需要操作某个逻辑库下的表时，也需要切换逻辑库 (use xxx)。</p><p><strong>核心属性：</strong></p><ul><li>name：指定自定义的逻辑库库名</li><li>checkSQLschema：在 SQL 语句操作时指定了数据库名称，执行时是否自动去除；true：自动去除，false：不自动去除</li><li>sqlMaxLimit：如果未指定 limit 进行查询，列表查询模式查询多少条记录</li></ul><p>② table 标签</p><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">schema</span> <span class="attr">name</span>=<span class="string">&quot;DB01&quot;</span> <span class="attr">checkSQLschema</span>=<span class="string">&quot;true&quot;</span> <span class="attr">sqlMaxLimit</span>=<span class="string">&quot;100&quot;</span> &gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;TB_ORDER&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1,dn2,dn3&quot;</span> <span class="attr">rule</span>=<span class="string">&quot;auto-sharding-long&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">schema</span>&gt;</span></span><br></pre></td></tr></table></figure><p>table 标签定义了 MyCat 中逻辑库 schema 下的逻辑表，所有需要拆分的表都需要在 table 标签中定义。</p><p><strong>核心属性：</strong></p><ul><li>name：定义逻辑表表名，在该逻辑库下唯一</li><li>dataNode：定义逻辑表所属的 dataNode，该属性需要与 dataNode 标签中 name 对应；多个 dataNode 逗号分隔</li><li>rule：分片规则的名字，分片规则名字是在 rule.xml 中定义的</li><li>primaryKey：逻辑表对应真实表的主键</li><li>type：逻辑表的类型，目前逻辑表只有全局表和普通表，如果未配置，就是普通表；全局表，配置为 global</li></ul><p>③ dataNode 标签</p><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn1&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost1&quot;</span> <span class="attr">database</span>=<span class="string">&quot;db01&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn2&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost2&quot;</span> <span class="attr">database</span>=<span class="string">&quot;db01&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn3&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost3&quot;</span> <span class="attr">database</span>=<span class="string">&quot;db01&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>dataNode 标签中定义了 MyCat 中的数据节点，也就是我们通常说的数据分片。一个 dataNode 标签就是一个独立的数据分片。</p><p><strong>核心属性：</strong></p><ul><li>name：定义数据节点名称</li><li>dataHost：数据库实例主机名称，引用自 dataHost 标签中 name 属性</li><li>database：定义分片所属数据库</li></ul><p>④ dataHost 标签</p><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dataHost</span> <span class="attr">name</span>=<span class="string">&quot;dhost1&quot;</span> <span class="attr">maxCon</span>=<span class="string">&quot;1000&quot;</span> <span class="attr">minCon</span>=<span class="string">&quot;10&quot;</span> <span class="attr">balance</span>=<span class="string">&quot;0&quot;</span> <span class="attr">writeType</span>=<span class="string">&quot;0&quot;</span> <span class="attr">dbType</span>=<span class="string">&quot;mysql&quot;</span> <span class="attr">dbDriver</span>=<span class="string">&quot;jdbc&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">heartbeat</span>&gt;</span>select user()<span class="tag">&lt;/<span class="name">heartbeat</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">writeHost</span> <span class="attr">host</span>=<span class="string">&quot;master&quot;</span> <span class="attr">url</span>=<span class="string">&quot;jdbc:mysql://192.168.200.210:3306?useSSL=false<span class="symbol">&amp;amp;</span>serverTimezone=Asia/Shanghai<span class="symbol">&amp;amp;</span>characterEncoding=utf8&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">user</span>=<span class="string">&quot;root&quot;</span> <span class="attr">password</span>=<span class="string">&quot;1234&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">writeHost</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dataHost</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>核心属性：</strong></p><ul><li>name：唯一标识，供上层标签使用</li><li>maxCon&#x2F;minCon：最大连接数 &#x2F; 最小连接数</li><li>balance：负载均衡策略，取值 0,1,2,3</li><li>writeType：写操作分发方式（0：写操作转发到第一个 writeHost，第一个挂了，切换到第二个；1：写操作随机分发到配置的 writeHost）</li><li>dbDriver：数据库驱动，支持 native、jdbc</li></ul><img src="https://i-blog.csdnimg.cn/direct/b906f37594b04429adf521a53bccb4b6.png" alt="img" style="zoom:50%;" /><p>(2) rule.xml</p><p>rule.xml 中定义所有分表的规则，在使用过程中可以灵活的使用分片算法，或者对同一个分片算法使用不同的参数，它让分片过程可配置化。主要包含两类标签：tableRule、function。</p><img src="https://i-blog.csdnimg.cn/direct/82a29dfaf585416192f9fcbd34d75313.png" alt="img" style="zoom:50%;" /><p>(3) server.xml</p><p>① system 标签</p><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">system</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;nonePasswordLogin&quot;</span>&gt;</span>0<span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;useHandshakeV10&quot;</span>&gt;</span>1<span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;useSqlStat&quot;</span>&gt;</span>1<span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">system</span>&gt;</span></span><br></pre></td></tr></table></figure><p>② user 标签</p><img src="https://i-blog.csdnimg.cn/direct/4e6cf21dd74b4a2b9c33e085f338e13b.png" alt="img" style="zoom:50%;" /><h3 id="分库分表-1"><a href="#分库分表-1" class="headerlink" title="分库分表"></a>分库分表</h3><h4 id="垂直拆分"><a href="#垂直拆分" class="headerlink" title="垂直拆分"></a>垂直拆分</h4><p>(1) 场景</p><img src="https://i-blog.csdnimg.cn/direct/d51e8b2beab84fa88a64c883e54ab43b.png" alt="img" style="zoom:50%;" /><p>(2) 配置</p><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><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="tag">&lt;<span class="name">schema</span> <span class="attr">name</span>=<span class="string">&quot;SHOPPING&quot;</span> <span class="attr">checkSQLschema</span>=<span class="string">&quot;true&quot;</span> <span class="attr">sqlMaxLimit</span>=<span class="string">&quot;100&quot;</span> &gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_goods_base&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_goods_cat&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_goods_desc&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;goods_id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_goods_item&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_order_item&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn2&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_order_master&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn2&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;order_id&quot;</span> <span class="attr">rule</span>=<span class="string">&quot;order_rule&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_order_pay_log&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn2&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;out_trade_no&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_user&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn3&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_areas_provinces&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn3&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_areas_city&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn3&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_areas_region&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn3&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_user_address&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn3&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">schema</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn1&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost1&quot;</span> <span class="attr">database</span>=<span class="string">&quot;shopping&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn2&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost2&quot;</span> <span class="attr">database</span>=<span class="string">&quot;shopping&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn3&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost3&quot;</span> <span class="attr">database</span>=<span class="string">&quot;shopping&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>schema 节点下关联多个 table 标签，不同 table 对应不同 dataNode：</p><p>① dn1 对应商品类表（如 tb_goods_base、tb_goods_cat 等）</p><p>② dn2 对应订单类表（如 tb_order_item、tb_order_master 等）</p><p>③ dn3 对应用户及地址类表（如 tb_user、tb_areas_provinces 等）</p><p>(3) 测试</p><p>① 在 mycat 的命令行中，通过 source 指令导入表结构及对应数据，查看数据分布情况：</p><figure class="highlight bash"><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="built_in">source</span> /root/shopping_table.sql</span><br><span class="line"><span class="built_in">source</span> /root/shopping_insert.sql</span><br></pre></td></tr></table></figure><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">select</span> ua.user_id,ua.contact,p.province,c.city,r.area,ua.address <span class="keyword">from</span> tb_user_address ua,tb_areas_city c,tb_areas_provinces p,tb_areas_region r <span class="keyword">where</span> ua.province_id <span class="operator">=</span> p.provinceid <span class="keyword">and</span> ua.city_id <span class="operator">=</span> c.cityid <span class="keyword">and</span> ua.town_id <span class="operator">=</span> r.areaid ;</span><br></pre></td></tr></table></figure><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">SELECT</span> o.order_id, p.payment,o.receiver,p.province,c.city,r.area <span class="keyword">FROM</span> tb_order_master o,tb_areas_provinces p,tb_areas_city c,tb_areas_region r <span class="keyword">WHERE</span> o.receiver_province <span class="operator">=</span> p.provinceid <span class="keyword">AND</span> o.receiver_city <span class="operator">=</span> c.cityid <span class="keyword">AND</span> o.receiver_region <span class="operator">=</span> r.areaid ;</span><br></pre></td></tr></table></figure><p>(4) 全局表配置</p><p>对于省、市、区 &#x2F; 县表<code>tb_areas_provinces</code>, <code>tb_areas_city</code>, <code>tb_areas_region</code>，是属于数据字典表，在多个业务模块中都可能会遇到，可以将其设置为全局表，利于业务操作。</p><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_areas_provinces&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1,dn2,dn3&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span> <span class="attr">type</span>=<span class="string">&quot;global&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_areas_city&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1,dn2,dn3&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span> <span class="attr">type</span>=<span class="string">&quot;global&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_areas_region&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn1,dn2,dn3&quot;</span> <span class="attr">primaryKey</span>=<span class="string">&quot;id&quot;</span> <span class="attr">type</span>=<span class="string">&quot;global&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><h4 id="水平拆分"><a href="#水平拆分" class="headerlink" title="水平拆分"></a>水平拆分</h4><p>(1) 场景</p><p>在业务系统中，有一张表 (日志表)，业务系统每天都会产生大量的日志数据，单台服务器的数据存储及处理能力是有限的，可以对数据库表进行拆分。</p><p>(2) 配置</p><p>① shema.xml</p><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">schema</span> <span class="attr">name</span>=<span class="string">&quot;ITCAST&quot;</span> <span class="attr">checkSQLschema</span>=<span class="string">&quot;true&quot;</span> <span class="attr">sqlMaxLimit</span>=<span class="string">&quot;100&quot;</span> &gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_log&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn4,dn5,dn6&quot;</span> <span class="attr">rule</span>=<span class="string">&quot;mod-long&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">schema</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn4&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost1&quot;</span> <span class="attr">database</span>=<span class="string">&quot;itcast&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn5&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost2&quot;</span> <span class="attr">database</span>=<span class="string">&quot;itcast&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn6&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost3&quot;</span> <span class="attr">database</span>=<span class="string">&quot;itcast&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>② server.xml</p><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><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">user</span> <span class="attr">name</span>=<span class="string">&quot;root&quot;</span> <span class="attr">defaultAccount</span>=<span class="string">&quot;true&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;password&quot;</span>&gt;</span>123456<span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;schemas&quot;</span>&gt;</span>SHOPPING,ITCAST<span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 表级 DML 权限设置 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--</span></span><br><span class="line"><span class="comment">    &lt;privileges check=&quot;false&quot;&gt;</span></span><br><span class="line"><span class="comment">        &lt;schema name=&quot;TESTDB&quot; dml=&quot;0110&quot;&gt;</span></span><br><span class="line"><span class="comment">            &lt;table name=&quot;tb01&quot; dml=&quot;0000&quot;&gt;&lt;/table&gt;</span></span><br><span class="line"><span class="comment">            &lt;table name=&quot;tb02&quot; dml=&quot;1111&quot;&gt;&lt;/table&gt;</span></span><br><span class="line"><span class="comment">        &lt;/schema&gt;</span></span><br><span class="line"><span class="comment">    &lt;/privileges&gt;</span></span><br><span class="line"><span class="comment">    --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">user</span>&gt;</span></span><br></pre></td></tr></table></figure><p>(3) 测试</p><p>在 mycat 的命令行中，执行如下 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><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="keyword">CREATE TABLE</span> tb_log (</span><br><span class="line">id <span class="type">bigint</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;ID&#x27;</span>,</span><br><span class="line">model_name <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;模块名&#x27;</span>,</span><br><span class="line">model_value <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;模块值&#x27;</span>,</span><br><span class="line">type <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;类型&#x27;</span>,</span><br><span class="line">return_value <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;返回值&#x27;</span>,</span><br><span class="line">return_class <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;返回类型&#x27;</span>,</span><br><span class="line">operate_user <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;操作用户&#x27;</span>,</span><br><span class="line">operate_time <span class="type">timestamp</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;操作时间&#x27;</span>,</span><br><span class="line">param_and_value <span class="type">varchar</span>(<span class="number">500</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;请求参数名及参数值&#x27;</span>,</span><br><span class="line">operate_class <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;操作类&#x27;</span>,</span><br><span class="line">operate_method <span class="type">varchar</span>(<span class="number">200</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;操作方法&#x27;</span>,</span><br><span class="line">cost_time <span class="type">bigint</span>(<span class="number">20</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;执行方法耗时,单位ms&#x27;</span>,</span><br><span class="line">summary text <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;摘要 1.2,Arond.102&#x27;</span>,</span><br><span class="line"><span class="keyword">PRIMARY KEY</span> (id)</span><br><span class="line">)ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4;</span><br></pre></td></tr></table></figure><h4 id="分片规则"><a href="#分片规则" class="headerlink" title="分片规则"></a>分片规则</h4><p>(1) 范围分片</p><p>根据指定的字段及其配置的范围与数据节点的对应情况，来决定该数据属于哪一个分片。</p><img src="https://i-blog.csdnimg.cn/direct/c27181c1e5cd4338b7115a70f4060317.png" alt="img" style="zoom:50%;" /><p><strong>说明：</strong></p><ul><li>0~5000000 → dataNode1</li><li>5000001~10000000 → dataNode2</li><li>10000001 及以上 → dataNode3</li></ul><img src="https://i-blog.csdnimg.cn/direct/7f89fdd445334948b94c7a0c8d4a692a.png" alt="img" style="zoom: 80%;" /><p>(2) 取模分片</p><p>根据指定的字段值与节点数量进行求模运算，根据运算结果，来决定该数据属于哪一个分片。</p><img src="https://i-blog.csdnimg.cn/direct/42ee9400cf404ca997810aa1a0ba99b2.png" alt="img" style="zoom:67%;" /><p>**说明：**通过<code>id % 3</code>的结果分配数据：</p><ul><li><code>id%3=0</code> → dataNode1</li><li><code>id%3=1</code> → dataNode2</li><li><code>id%3=2</code> → dataNode3</li></ul><p>(3) 一致性hash 分片</p><p>所谓一致性哈希，相同的哈希因子计算总是被划分到相同的分区表中，不会因为分区节点的增加而改变原来数据的分区位置。</p><img src="https://i-blog.csdnimg.cn/direct/9d1546669bef408885704b730862444e.png" alt="img" style="zoom:67%;" /><p>① schema.xml</p><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">table</span> <span class="attr">name</span>=<span class="string">&quot;tb_order&quot;</span> <span class="attr">dataNode</span>=<span class="string">&quot;dn4,dn5,dn6&quot;</span> <span class="attr">rule</span>=<span class="string">&quot;sharding-by-murmur&quot;</span> /&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn4&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost1&quot;</span> <span class="attr">database</span>=<span class="string">&quot;itcast&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn5&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost2&quot;</span> <span class="attr">database</span>=<span class="string">&quot;itcast&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dataNode</span> <span class="attr">name</span>=<span class="string">&quot;dn6&quot;</span> <span class="attr">dataHost</span>=<span class="string">&quot;dhost3&quot;</span> <span class="attr">database</span>=<span class="string">&quot;itcast&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>② rule.xml</p><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><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">tableRule</span> <span class="attr">name</span>=<span class="string">&quot;sharding-by-murmur&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">rule</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">columns</span>&gt;</span>id<span class="tag">&lt;/<span class="name">columns</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">algorithm</span>&gt;</span>murmur<span class="tag">&lt;/<span class="name">algorithm</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">rule</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">tableRule</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">function</span> <span class="attr">name</span>=<span class="string">&quot;murmur&quot;</span> <span class="attr">class</span>=<span class="string">&quot;io.mycat.route.function.PartitionByMurmurHash&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;seed&quot;</span>&gt;</span>0<span class="tag">&lt;/<span class="name">property</span>&gt;</span> <span class="comment">&lt;!-- 默认是0 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;count&quot;</span>&gt;</span>3<span class="tag">&lt;/<span class="name">property</span>&gt;</span> <span class="comment">&lt;!-- 要分片的数据库节点数，必须指定，否则没法分片 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;virtualBucketTimes&quot;</span>&gt;</span>160<span class="tag">&lt;/<span class="name">property</span>&gt;</span> <span class="comment">&lt;!-- 一个实际的数据库节点被映射为这么多虚拟节点，默认是160倍，也就是虚拟节点数是物理节点数的160倍 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;property name=&quot;weightMapFile&quot;&gt;weightMapFile&lt;/property&gt; 节点的权重，没有指定权重的节点默认是1。 --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">function</span>&gt;</span></span><br></pre></td></tr></table></figure><p>(4) 枚举分片</p><p>通过在配置文件中配置可能的枚举值，指定数据分布到不同数据库节点上。本规则适用于按照省份、性别、状态拆分数据等业务。</p><img src="https://i-blog.csdnimg.cn/direct/2e016703bb864e3ea632e9b4a066a05a.png" alt="img" style="zoom: 50%;" /><p>(5) 应用指定算法</p><p>运行阶段由应用自主决定路由到哪个分片，直接根据字符串（必须是数字）计算分片号。</p><p><img src="https://i-blog.csdnimg.cn/direct/873c0bc5a3cb41c9b092b5b3a341041d.png" alt="img"></p><p><strong>说明：</strong></p><ul><li>00xxxx → 对应 dataNode1 的数据库</li><li>01xxxx → 对应 dataNode2 的数据库</li><li>02xxxx → 对应 dataNode3 的数据库</li></ul><img src="https://i-blog.csdnimg.cn/direct/0093a75c30d541dfb3f8fa26c711ce25.png" alt="img" style="zoom: 80%;" /><p>(6) 固定hash算法</p><p>该算法类似于十进制的求模运算，但是为二进制的操作，例如，取 id 的二进制低 10 位与<code>1111111111</code>进行位 &amp; 运算。</p><img src="https://i-blog.csdnimg.cn/direct/06f2a8048b424d6b82e4dc9a442f5f4b.png" alt="img" style="zoom: 50%;" /><p><strong>说明：</strong></p><p>① 任何一个十位的二进制与 1111111111（十位全1）进行与运算后的结果，<strong>结果就是这个十位二进制数本身。</strong></p><p>② 原因是这个二进制数的每一位都是 1，因此在与运算中：</p><ul><li>如果原数某一位是 1 → 1 &amp; 1 &#x3D; 1</li><li>如果原数某一位是 0 → 0 &amp; 1 &#x3D; 0</li></ul><p>也就是说，它不会改变原数的任何一位，只是保留原数的所有位。</p><p>③ 举例验证：假设原数是 <strong><code>1010101010</code></strong>（十位）：</p><figure class="highlight bash"><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">  1010101010</span><br><span class="line">&amp; 1111111111</span><br><span class="line">-----------</span><br><span class="line">  1010101010  ← 结果和原数完全相同</span><br></pre></td></tr></table></figure><img src="https://i-blog.csdnimg.cn/direct/ec12e1391c5447e29d4581ef01dd06e3.png" alt="img" style="zoom:80%;" /><p>(7) 字符串hash解析</p><p>截取字符串中的指定位置的子字符串，进行hash算法，算出分片</p><img src="https://i-blog.csdnimg.cn/direct/b4c75d51d43a415fa808373bfc8189f9.png" alt="img" style="zoom:67%;" /><img src="https://i-blog.csdnimg.cn/direct/5a3f64339e7f4cfebee538f3d59c2d7d.png" alt="img" style="zoom: 80%;" /><p><strong>运算示例：</strong></p><p>以 “world” 为例：</p><ul><li>截取子串：<code>world</code> → 取<code>0:2</code>对应 “wor”</li><li>执行 hash 运算：得到结果 26629</li><li>计算分片：<code>26629 &amp; (1024-1) = 5</code></li></ul><p>(8) 按天分片</p><img src="https://i-blog.csdnimg.cn/direct/3202a10b80e048e6b7a75e6ca370c764.png" alt="img" style="zoom: 80%;" /><p><strong>说明：</strong></p><p><strong>配置参数：</strong><code>begin:2022-01-01</code>、<code>end:2022-01-30</code>、<code>partitionDay:10</code></p><p>分片时间区间与 dataNode 对应：</p><ul><li><code>2022-01-01 ~ 2022-01-10</code> → dataNode1</li><li><code>2022-01-11 ~ 2022-01-20</code> → dataNode2</li><li><code>2022-01-21 ~ 2022-01-30</code> → dataNode3</li></ul><img src="https://i-blog.csdnimg.cn/direct/65fbfb349c6c49e5bfafaf42dcf52b16.png" alt="img" style="zoom:80%;" /><p>(9) 按月分片</p><p>使用场景为按月份来分片，每个自然月为一个分片</p><img src="https://i-blog.csdnimg.cn/direct/4e408a54917444cdbfef34025e91efba.png" alt="img" style="zoom:80%;" /><p><strong>说明：</strong></p><p>分片时间范围为 <code>begin:2022-01-01</code> 至 <code>end:2022-03-31</code>，数据按月份对应到不同 dataNode：</p><ul><li>dataNode1 对应数据：2022-01-10、2022-04-12（注：结束时间后会循环分片）</li><li>dataNode2 对应数据：2022-02-20</li><li>dataNode3 对应数据：2022-03-31</li></ul><img src="https://i-blog.csdnimg.cn/direct/a290c4c0e7e144449de600820a624df6.png" alt="img" style="zoom:80%;" /><h4 id="Mycat-管理及监控"><a href="#Mycat-管理及监控" class="headerlink" title="Mycat 管理及监控"></a>Mycat 管理及监控</h4><p>(1) Mycat 原理</p><img src="https://i-blog.csdnimg.cn/direct/452493d868b6455880536d26c7c585dc.png" alt="img" style="zoom:80%;" /><p>① <code>select * from tb_user;</code></p><ul><li>客户端把 SQL 发给 Mycat Server；</li><li>Mycat 先<strong>解析 SQL</strong>，再做<strong>分片分析</strong>（按规则，这个 SQL 要查所有分片）；</li><li>经<strong>路由分析</strong>，确定要访问右边 3 个数据库节点；</li><li>从 3 个节点取数据后，做<strong>分页、排序、聚合、结果合并</strong>；</li><li>把合并后的结果返回给客户端。</li></ul><p>② <code>select * from tb_user where status in(1,3) order by id;</code></p><ul><li>客户端发 SQL 后，Mycat 解析出<code>status</code>条件；</li><li>分片分析 + 路由分析：只需要访问<code>status=1</code>（dn1）和<code>status=3</code>（dn3）的数据库；</li><li>从这 2 个节点取数据后，做<strong>排序处理 + 结果合并</strong>；</li><li>返回最终结果给客户端。</li></ul><p>(2) Mycat 管理</p><p>① Mycat 默认开通 2 个端口，可在<code>server.xml</code>中修改：</p><ul><li>8066 数据访问端口，用于执行 DML 和 DDL 操作。</li><li>9066 数据库管理端口，用于管理 Mycat 集群状态。</li></ul><p>② 连接管理端口的命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql -h 192.168.200.210 -P 9066 -uroot -p123456</span><br></pre></td></tr></table></figure><p>③ Mycat 管理命令及含义：</p><table><thead><tr><th>命令</th><th>含义</th></tr></thead><tbody><tr><td>show @@help</td><td>查看 Mycat 管理工具文档</td></tr><tr><td>show @@version</td><td>查看 Mycat 的版本</td></tr><tr><td>reload @@config</td><td>重新加载 Mycat 的配置文件</td></tr><tr><td>show @@datasource</td><td>查看 Mycat 的数据源信息</td></tr><tr><td>show @@datanode</td><td>查看 Mycat 现有的分片节点信息</td></tr><tr><td>show @@threadpool</td><td>查看 Mycat 的线程池信息</td></tr><tr><td>show @@sql</td><td>查看执行的 SQL</td></tr><tr><td>show @@sql.sum</td><td>查看执行的 SQL 统计</td></tr></tbody></table><h3 id="读写分离"><a href="#读写分离" class="headerlink" title="读写分离"></a>读写分离</h3><h4 id="介绍-1"><a href="#介绍-1" class="headerlink" title="介绍"></a>介绍</h4><p>(1) 读写分离，简单地说是把对数据库的读和写操作分开，对应不同的数据库服务器。主数据库提供写操作，从数据库提供读操作，这样能有效地减轻单台数据库的压力。</p><p>(2) 通过 MyCat 即可轻易实现上述功能，不仅可以支持 MySQL，也可以支持 Oracle 和 SQL Server。</p><img src="https://i-blog.csdnimg.cn/direct/5e4bb87fc0d643f282e8d04987e0e880.png" alt="img" style="zoom: 67%;" /><p><strong>说明：</strong></p><p>① 应用程序连接 MyCat，MyCat 将<code>insert/update/delete</code>操作路由到<code>writeHost</code>对应的主库（mysql (m)）</p><p>② 将<code>select</code>操作路由到<code>readHost</code>对应的从库（mysql (s)）</p><p>③ 主库与从库之间通过主从复制（blog）同步数据</p><h4 id="一主一从读写分离"><a href="#一主一从读写分离" class="headerlink" title="一主一从读写分离"></a>一主一从读写分离</h4><p>(1) 配置</p><img src="https://i-blog.csdnimg.cn/direct/8052b0e8bfc34409b5931de7d5f9854e.png" alt="img" style="zoom: 67%;" /><p><img src="https://i-blog.csdnimg.cn/direct/226b69aee3c040ea9a39b66796d27424.png" alt="img"></p><p>Mycat 中<code>balance</code>参数的取值及含义说明表：</p><table><thead><tr><th>参数值</th><th>含义</th></tr></thead><tbody><tr><td>0</td><td>不开启读写分离机制，所有读操作都发送到当前可用的 writeHost 上</td></tr><tr><td>1</td><td>全部的 readHost 与备用的 writeHost 都参与 select 语句的负载均衡（主要针对于双主双从模式）</td></tr><tr><td>2</td><td>所有的读写操作都随机在 writeHost、readHost 上分发</td></tr><tr><td>3</td><td>所有的读请求随机分发到 writeHost 对应的 readHost 上执行，writeHost 不负担读压力</td></tr></tbody></table><h4 id="双主双从"><a href="#双主双从" class="headerlink" title="双主双从"></a>双主双从</h4><p>(1) 介绍</p><p>一个主机 Master1 用于处理所有写请求，它的从机 Slave1 和另一台主机 Master2 还有它的从机 Slave2 负责所有读请求。当 Master1 主机宕机后，Master2 主机负责写请求，Master1、Master2 互为备机。架构图如下：</p><p><img src="https://i-blog.csdnimg.cn/direct/e66e9933ea8640469c1dc526f7680726.png" alt="img"></p><p><strong>说明：</strong></p><p>① 应用程序连接 MyCat，MyCat 分发请求；</p><p>② mysql (m1)（Master1）复制数据到 mysql (s1)（Slave1），mysql (m2)（Master2）复制数据到 mysql (s2)（Slave2），同时 mysql (m1) 与 mysql (m2) 之间也存在复制关系，mysql (m1) 为主、mysql (m2) 为备</p><p>**Q：**主库不是负责写的吗？为什么 Master2 负责读了？</p><p>**A：<strong>双主双从中，Master1 是 “主用主库”</strong>：默认承担所有写请求；**<strong>而 Master2 是</strong>“备用主库”</p><ul><li>平时（Master1 正常运行时），它不处理写请求，而是作为<strong>读节点</strong>来分担读压力（和 Slave1、Slave2 一起负责读请求）；</li><li>只有当 Master1 宕机后，它才会切换为 “主库”，接手写请求。</li></ul><p>(2) 准备工作</p><p>我们需要准备 5 台服务器，具体的服务器及软件安装情况如下：</p><table><thead><tr><th>编号</th><th>IP</th><th>预装软件</th><th>角色</th></tr></thead><tbody><tr><td>1</td><td>192.168.200.210</td><td>MyCat、MySQL</td><td>MyCat 中间件服务器</td></tr><tr><td>2</td><td>192.168.200.211</td><td>MySQL</td><td>M1（Master1）</td></tr><tr><td>3</td><td>192.168.200.212</td><td>MySQL</td><td>S1（Slave1）</td></tr><tr><td>4</td><td>192.168.200.213</td><td>MySQL</td><td>M2（Master2）</td></tr><tr><td>5</td><td>192.168.200.214</td><td>MySQL</td><td>S2（Slave2）</td></tr></tbody></table><p>关闭以上所有服务器的防火墙：</p><ul><li><code>systemctl stop firewalld</code></li><li><code>systemctl disable firewalld</code></li></ul><p>(3) 搭建</p><p>① 主库配置（M1）</p><p>修改配置文件 &#x2F;etc&#x2F;my.cnf</p><figure class="highlight bash"><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="comment">#mysql 服务ID，保证整个集群环境中唯一，取值范围：1 - 2^32-1，默认为1</span></span><br><span class="line">server-id=1</span><br><span class="line"><span class="comment">#指定同步的数据库</span></span><br><span class="line">binlog-do-db=db01</span><br><span class="line">binlog-do-db=db02</span><br><span class="line">binlog-do-db=db03</span><br><span class="line"><span class="comment">#在作为从数据库的时候，有写入操作也要更新二进制日志文件</span></span><br><span class="line">log-slave-updates</span><br></pre></td></tr></table></figure><p>重启 MySQL 服务器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart mysqld</span><br></pre></td></tr></table></figure><p>② 主库配置（M2）</p><p>修改配置文件 <code>/etc/my.cnf</code></p><figure class="highlight bash"><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="comment">#mysql 服务ID，保证整个集群环境中唯一，取值范围：1 - 2^32-1，默认为1</span></span><br><span class="line">server-id=3</span><br><span class="line"><span class="comment">#指定同步的数据库</span></span><br><span class="line">binlog-do-db=db01</span><br><span class="line">binlog-do-db=db02</span><br><span class="line">binlog-do-db=db03</span><br><span class="line"><span class="comment">#在作为从数据库的时候，有写入操作也要更新二进制日志文件</span></span><br><span class="line">log-slave-updates</span><br></pre></td></tr></table></figure><p>重启 MySQL 服务器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart mysqld</span><br></pre></td></tr></table></figure><p>③ 两台主库创建账户并授权（注：该操作需在 Master1、Master2 两台主库分别执行）</p><figure class="highlight bash"><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"># 创建itcast用户，并设置密码，该用户可在任意主机连接该MySQL服务</span></span><br><span class="line">CREATE USER <span class="string">&#x27;itcast&#x27;</span>@<span class="string">&#x27;%&#x27;</span> IDENTIFIED WITH mysql_native_password BY <span class="string">&#x27;Root@123456&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 为&#x27;itcast&#x27;@&#x27;%&#x27;用户分配主从复制权限</span></span><br><span class="line">GRANT REPLICATION SLAVE ON *.* TO <span class="string">&#x27;itcast&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br></pre></td></tr></table></figure><p>查看两台主库的二进制日志坐标</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show master status;</span><br></pre></td></tr></table></figure><p>④ 从库配置（S1）</p><p>修改配置文件 <code>/etc/my.cnf</code></p><figure class="highlight bash"><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">#mysql 服务ID，保证整个集群环境中唯一，取值范围：1 - 2^32-1，默认为1</span></span><br><span class="line">server-id=2</span><br></pre></td></tr></table></figure><p>重启 MySQL 服务器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart mysqld</span><br></pre></td></tr></table></figure><p>⑤ 从库配置（S2）</p><p>修改配置文件 <code>/etc/my.cnf</code></p><figure class="highlight bash"><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">#mysql 服务ID，保证整个集群环境中唯一，取值范围：1 - 2^32-1，默认为1</span></span><br><span class="line">server-id=4</span><br></pre></td></tr></table></figure><p>重启 MySQL 服务器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart mysqld</span><br></pre></td></tr></table></figure><p>⑥ 两台从库关联的从库</p><p>执行<code>CHANGE MASTER TO</code>命令，关联对应的主库（注意：slave1 对应 master1，slave2 对应 master2）：</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">CHANGE MASTER <span class="keyword">TO</span> </span><br><span class="line">MASTER_HOST<span class="operator">=</span><span class="string">&#x27;xxx.xxx.xxx.xxx&#x27;</span>,  # 对应主库的IP</span><br><span class="line">MASTER_USER<span class="operator">=</span><span class="string">&#x27;xxx&#x27;</span>,             # 主库授权的复制用户（如itcast）</span><br><span class="line">MASTER_PASSWORD<span class="operator">=</span><span class="string">&#x27;xxx&#x27;</span>,         # 复制用户的密码（如Root<span class="variable">@123456</span>）</span><br><span class="line">MASTER_LOG_FILE<span class="operator">=</span><span class="string">&#x27;xxx&#x27;</span>,         # 主库`<span class="keyword">show</span> master status`查到的日志文件名</span><br><span class="line">MASTER_LOG_POS<span class="operator">=</span>xxx;            # 主库`<span class="keyword">show</span> master status`查到的日志位置</span><br></pre></td></tr></table></figure><p>启动两台从库主从复制，查看从库状态</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></pre></td><td class="code"><pre><span class="line"># 启动主从复制</span><br><span class="line"><span class="keyword">start</span> slave;</span><br><span class="line"></span><br><span class="line"># 查看从库状态（需确保Slave_IO_Running、Slave_SQL_Running均为Yes）</span><br><span class="line"><span class="keyword">show</span> slave status\G;</span><br></pre></td></tr></table></figure><p>(4) 测试</p><p>分别在两台主库Master1、Master2上执行DDL、DML语句，查看涉及到的数据库服务器的数据同步情况。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> database db01;</span><br><span class="line">use db01;</span><br><span class="line"><span class="keyword">create table</span> tb_user(</span><br><span class="line">id <span class="keyword">in</span>(<span class="number">11</span>)<span class="keyword">not null</span> <span class="keyword">primary key</span>,</span><br><span class="line">name <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">not null</span>,</span><br><span class="line">sex varcahr(<span class="number">1</span>)</span><br><span class="line">)engine<span class="operator">=</span>innodb <span class="keyword">default</span> charset<span class="operator">=</span>utf8mb4</span><br><span class="line"></span><br><span class="line"><span class="keyword">insert into</span> tb <span class="keyword">user</span>(id,name,sex) <span class="keyword">values</span>(l,<span class="string">&#x27;Tom&#x27;</span>,<span class="string">&#x27;1&#x27;</span>);</span><br><span class="line"><span class="keyword">insert into</span> tb <span class="keyword">user</span>(id,name,sex) <span class="keyword">values</span>(<span class="number">2</span>,<span class="string">&#x27;Trigger&#x27;</span>,<span class="string">&#x27;0&#x27;</span>);</span><br><span class="line"><span class="keyword">insert into</span> tb <span class="keyword">user</span>(id,name,sex) <span class="keyword">values</span>(<span class="number">3</span>,<span class="string">&#x27;Dawn&#x27;</span>,<span class="string">&#x27;1&#x27;</span>);</span><br><span class="line"><span class="keyword">insert into</span> tb <span class="keyword">user</span>(id,name,sex) <span class="keyword">values</span>(<span class="number">4</span>,&quot;ack Ma&#x27;,&#x27;1&#x27;);</span><br><span class="line">insertinto tb user(id,name,sex) values(5,&#x27;Coco&#x27;,&#x27;0&#x27;);</span><br><span class="line">insert into tb user(id,name,sex) values(6,&#x27;erry&#x27;,&#x27;1&#x27;);</span><br></pre></td></tr></table></figure><h4 id="双主双从读写分离"><a href="#双主双从读写分离" class="headerlink" title="双主双从读写分离"></a>双主双从读写分离</h4><p>(1) 配置</p><p><img src="https://i-blog.csdnimg.cn/direct/b5d3b8b0175447338de4620bad79b7f6.png" alt="img"></p><p><img src="https://i-blog.csdnimg.cn/direct/453a8ea27dda45fbb11d594ea78f077d.png" alt="img"></p><p>(2) 测试</p><p>① 登录MyCat，测试查询及更新操作，判定是否能够进行读写分离，以及读写分离的策略是否正确。</p><p>② 当主库挂掉一个之后，是否能够自动切换。</p>]]></content>
    
    
    <summary type="html">MySQL 日志系统、主从复制原理、分库分表方案与数据库备份恢复策略</summary>
    
    
    
    <category term="MySQL" scheme="https://kon-forever.cloud/categories/MySQL/"/>
    
    
    <category term="MySQL" scheme="https://kon-forever.cloud/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>MySQL学习笔记之进阶篇</title>
    <link href="https://kon-forever.cloud/2026/03/28/MySQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E8%BF%9B%E9%98%B6%E7%AF%87/"/>
    <id>https://kon-forever.cloud/2026/03/28/MySQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E8%BF%9B%E9%98%B6%E7%AF%87/</id>
    <published>2026-03-28T03:00:00.000Z</published>
    <updated>2026-09-28T02:40:41.143Z</updated>
    
    <content type="html"><![CDATA[<h3 id="存储引擎"><a href="#存储引擎" class="headerlink" title="存储引擎"></a>存储引擎</h3><h4 id="MySQL体系结构："><a href="#MySQL体系结构：" class="headerlink" title="MySQL体系结构："></a>MySQL体系结构：</h4><img src="https://i-blog.csdnimg.cn/direct/6170649c7b214229a6eca670767bffe5.png" alt="img" style="zoom: 50%;" /><p>(1) 连接层：最上层是一些客户端和链接服务，主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。</p><p>(2) 服务层：第二层架构主要完成大多数的核心服务功能，如 SQL 接口，并完成缓存的查询，SQL 的分析和优化，部分内置函数的执行。所有跨存储引擎的功能也在这一层实现，如过程、函数等。</p><p>(3) 引擎层：存储引擎真正的负责了 MySQL 中数据的存储和提取，服务器通过 API 和存储引擎进行通信。不同的存储引擎具有不同的功能，这样我们可以根据自己的需要，来选取合适的存储引擎。</p><p>(4) 存储层：主要是将数据存储在文件系统之上，并完成与存储引擎的交互。</p><h4 id="存储引擎-1"><a href="#存储引擎-1" class="headerlink" title="存储引擎"></a>存储引擎</h4><p>(1) 存储引擎就是存储数据、建立索引、更新&#x2F;查询数据等技术的实现方式。存储引擎是基于表而不是基于库的，所以存储引擎也可以被称为表引擎。</p><p>(2) 默认存储引擎是InnoDB。</p><p>(3) 相关操作：</p><figure class="highlight plaintext"><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><br><span class="line">show create table account;</span><br><span class="line">-- 建表时指定存储引擎</span><br><span class="line">CREATE TABLE 表名(</span><br><span class="line">...</span><br><span class="line">) ENGINE=INNODB;</span><br><span class="line">-- 查看当前数据库支持的存储引擎</span><br><span class="line">show engines;</span><br></pre></td></tr></table></figure><h4 id="InnoDB"><a href="#InnoDB" class="headerlink" title="InnoDB"></a>InnoDB</h4><p>(1) InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎，在 MySQL 5.5 之后，InnoDB 是默认的 MySQL 引擎</p><p>(2) 特点：</p><ul><li>DML 操作遵循 ACID 模型，支持<strong>事务</strong></li><li><strong>行级锁</strong>，提高并发访问性能</li><li>支持<strong>外键</strong>约束，保证数据的完整性和正确性</li></ul><p><strong>① 表锁：<strong>锁住</strong>整个表</strong>的锁。当某个人操作（增删改）这个表时，会把整个表 “锁起来”，其他人在锁释放前，<strong>不能操作这个表的任何数据</strong>。</p><p><strong>② 行锁（hang suo）：<strong>只锁住</strong>表中某一行（或几行）数据</strong>的锁。当某个人操作某一行时，只锁这一行，其他行的数据还是可以被其他人正常操作。</p><p>(3) 文件：</p><ul><li>xxx.ibd: xxx代表表名，InnoDB 引擎的每张表都会对应这样一个表空间文件，存储该表的表结构（frm、sdi）、数据和索引。</li></ul><p>参数：innodb_file_per_table，决定多张表共享一个表空间还是每张表对应一个表空间</p><p>(4) InnoDB 逻辑存储结构：</p><img src="https://i-blog.csdnimg.cn/direct/806d7a819fbc4a2cb1b14109a51da70b.png" alt="img" style="zoom:50%;" /><h4 id="MyISAM"><a href="#MyISAM" class="headerlink" title="MyISAM"></a>MyISAM</h4><p>(1) MyISAM 是 MySQL 早期的默认存储引擎。</p><p>(2) 特点：</p><ul><li>不支持事务，不支持外键</li><li>支持表锁，不支持行锁</li><li>访问速度快</li></ul><p>(3) 文件：</p><ul><li>xxx.sdi: 存储表结构信息</li><li>xxx.MYD: 存储数据</li><li>xxx.MYI: 存储索引</li></ul><h4 id="Memory"><a href="#Memory" class="headerlink" title="Memory"></a>Memory</h4><p>(1) Memory 引擎的表数据是存储在内存中的，受硬件问题、断电问题的影响，只能将这些表作为临时表或缓存使用。</p><p>(2) 特点：</p><ul><li>存放在内存中，速度快</li><li>hash索引（默认）</li></ul><p>(3) 文件：</p><ul><li>xxx.sdi: 存储表结构信息</li></ul><h4 id="存储引擎特点"><a href="#存储引擎特点" class="headerlink" title="存储引擎特点"></a>存储引擎特点</h4><table><thead><tr><th>特点</th><th>InnoDB</th><th>MyISAM</th><th>Memory</th></tr></thead><tbody><tr><td>存储限制</td><td>64TB</td><td>有</td><td>有</td></tr><tr><td>事务安全</td><td>支持</td><td>-</td><td>-</td></tr><tr><td>锁机制</td><td>行锁</td><td>表锁</td><td>表锁</td></tr><tr><td>B+tree索引</td><td>支持</td><td>支持</td><td>支持</td></tr><tr><td>Hash索引</td><td>-</td><td>-</td><td>支持</td></tr><tr><td>全文索引</td><td>支持（5.6版本之后）</td><td>支持</td><td>-</td></tr><tr><td>空间使用</td><td>高</td><td>低</td><td>N&#x2F;A</td></tr><tr><td>内存使用</td><td>高</td><td>低</td><td>中等</td></tr><tr><td>批量插入速度</td><td>低</td><td>高</td><td>高</td></tr><tr><td>支持外键</td><td>支持</td><td>-</td><td>-</td></tr></tbody></table><h4 id="存储引擎的选择"><a href="#存储引擎的选择" class="headerlink" title="存储引擎的选择"></a>存储引擎的选择</h4><p>(1) 在选择存储引擎时，应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统，还可以根据实际情况选择多种存储引擎进行组合。</p><ul><li>InnoDB: 如果应用对事物的完整性有比较高的要求，在并发条件下要求数据的一致性，数据操作除了插入和查询之外，还包含很多的更新、删除操作，则 InnoDB 是比较合适的选择</li><li>MyISAM: 如果应用是以读操作和插入操作为主，只有很少的更新和删除操作，并且对事务的完整性、并发性要求不高，那这个存储引擎是非常合适的。</li><li>Memory: 将所有数据保存在内存中，访问速度快，通常用于临时表及缓存。Memory 的缺陷是对表的大小有限制，太大的表无法缓存在内存中，而且无法保障数据的安全性</li></ul><p>电商中的足迹和评论适合使用 MyISAM 引擎，缓存适合使用 Memory 引擎。</p><h3 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h3><p>索引是帮助 MySQL <strong>高效获取数据</strong>的<strong>数据结构（有序）</strong>。在数据之外，数据库系统还维护着满足特定查找算法的数据结构，这些数据结构以某种方式引用（指向）数据，这样就可以在这些数据结构上实现高级查询算法，这种数据结构就是索引。</p><h4 id="优缺点："><a href="#优缺点：" class="headerlink" title="优缺点："></a>优缺点：</h4><p>① 优点：</p><ul><li>提高数据检索效率，降低数据库的IO成本</li><li>通过索引列对数据进行排序，降低数据排序的成本，降低CPU的消耗</li></ul><p>② 缺点：</p><ul><li>索引列也是要占用空间的</li><li>索引大大提高了查询效率，但降低了更新的速度，比如 INSERT、UPDATE、DELETE</li></ul><h4 id="索引结构"><a href="#索引结构" class="headerlink" title="索引结构"></a>索引结构</h4><table><thead><tr><th>索引结构</th><th>描述</th></tr></thead><tbody><tr><td>B+Tree</td><td>最常见的索引类型，大部分引擎都支持B+树索引</td></tr><tr><td>Hash</td><td>底层数据结构是用哈希表实现，只有精确匹配索引列的查询才有效，不支持范围查询</td></tr><tr><td>R-Tree(空间索引)</td><td>空间索引是 MyISAM 引擎的一个特殊索引类型，主要用于地理空间数据类型，通常使用较少</td></tr><tr><td>Full-Text(全文索引)</td><td>是一种通过建立倒排索引，快速匹配文档的方式，类似于 Lucene, Solr, ES</td></tr></tbody></table><table><thead><tr><th>索引</th><th>InnoDB</th><th>MyISAM</th><th>Memory</th></tr></thead><tbody><tr><td>B+Tree索引</td><td>支持</td><td>支持</td><td>支持</td></tr><tr><td>Hash索引</td><td>不支持</td><td>不支持</td><td>支持</td></tr><tr><td>R-Tree索引</td><td>不支持</td><td>支持</td><td>不支持</td></tr><tr><td>Full-text</td><td>5.6版本后支持</td><td>支持</td><td>不支持</td></tr></tbody></table><p>(1) B-Tree（<strong>多路</strong>平衡查找树）</p><img src="https://i-blog.csdnimg.cn/direct/a79e23400bf24d3cb599188fc6389891.png" alt="img" style="zoom: 67%;" /><p>① 二叉树缺点：顺序插入时，会形成一个链表，查询性能大大降低。大数据量情况下，层级较深，检索速度慢。而二叉树的缺点可以用红黑树来解决：</p><img src="https://i-blog.csdnimg.cn/direct/83021ec8ce2746398e3082eb74e00e40.png" alt="img" style="zoom:67%;" /><p>② 红黑树也存在大数据量情况下，层级较深，检索速度慢的问题。为了解决上述问题，可以使用 B-Tree 结构。</p><p>③ B-Tree 以一棵最大度数（max-degree，指一个节点的子节点个数）为5（5阶）的 b-tree 为例（每个节点最多存储4个key，5个指针）</p><img src="https://i-blog.csdnimg.cn/direct/3e4a3f88550542169e75520eb1060a8a.png" alt="img" style="zoom:67%;" /><p>(2) B+Tree</p><p><img src="https://i-blog.csdnimg.cn/direct/18a3a74a0989417bb6b48e1b69d2a952.png" alt="img">① 与 B-Tree 的区别：</p><ul><li>所有的数据都会出现在叶子节点</li><li>叶子节点形成一个单向链表</li></ul><p>② MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上，增加一个指向相邻叶子节点的链表指针，就形成了带有顺序指针的 B+Tree，提高区间访问的性能。</p><img src="https://i-blog.csdnimg.cn/direct/343b7ac70c0f43958e82be3112479f8f.png" alt="img" style="zoom:50%;" /><p>(3) Hash</p><p>① 哈希索引就是采用一定的hash算法，将键值换算成新的hash值，映射到对应的槽位上，然后存储在hash表中。如果两个（或多个）键值，映射到一个相同的槽位上，他们就产生了hash冲突（也称为hash碰撞），可以通过链表来解决。</p><img src="https://i-blog.csdnimg.cn/direct/48d37204478e4b20abd0a1342e541706.png" alt="img" style="zoom:50%;" /><p>② 特点：</p><ul><li>Hash索引只能用于对等比较（&#x3D;、in），不支持范围查询（betwwn、&gt;、&lt;、…）</li><li>无法利用索引完成排序操作</li><li>查询效率高，通常只需要一次检索就可以了，效率通常要高于 B+Tree 索引</li></ul><p>③ 存储引擎支持：</p><ul><li>Memory</li><li>InnoDB: 具有自适应hash功能，hash索引是存储引擎根据 B+Tree 索引在指定条件下自动构建的</li></ul><p>(4) 面试题：为什么 InnoDB 存储引擎选择使用 B+Tree 索引结构？</p><ul><li>相对于二叉树，层级更少，搜索效率高</li><li>对于 B-Tree，无论是叶子节点还是非叶子节点，都会保存数据，这样导致一页中存储的键值减少，指针也跟着减少，要同样保存大量数据，只能增加树的高度，导致性能降低</li><li>相对于 Hash 索引，B+Tree 支持范围匹配及排序操作</li></ul><h4 id="索引分类"><a href="#索引分类" class="headerlink" title="索引分类"></a>索引分类</h4><table><thead><tr><th>分类</th><th>含义</th><th>特点</th><th>关键字</th></tr></thead><tbody><tr><td>主键索引</td><td>针对于表中主键创建的索引</td><td>默认自动创建，只能有一个</td><td>PRIMARY</td></tr><tr><td>唯一索引</td><td>避免同一个表中某数据列中的值重复</td><td>可以有多个</td><td>UNIQUE</td></tr><tr><td>常规索引</td><td>快速定位特定数据</td><td>可以有多个</td><td></td></tr><tr><td>全文索引</td><td>全文索引查找的是文本中的关键词，而不是比较索引中的值</td><td>可以有多个</td><td>FULLTEXT</td></tr></tbody></table><p>(1) 在 InnoDB 存储引擎中，根据索引的存储形式，又可以分为以下两种：</p><table><thead><tr><th>分类</th><th>含义</th><th>特点</th></tr></thead><tbody><tr><td>聚集索引(Clustered Index)</td><td>将数据存储与索引放一块，索引结构的叶子节点保存了行数据</td><td>必须有，而且只有一个</td></tr><tr><td>二级索引(Secondary Index)</td><td>将数据与索引分开存储，索引结构的叶子节点关联的是对应的主键</td><td>可以存在多个</td></tr></tbody></table><p>Q：叶子节点是什么？</p><p>A：叶子节点是 <strong>树结构中没有子节点的节点</strong>，也就是树的「最底层终端节点」</p><p>(2) 演示图：</p><img src="https://i-blog.csdnimg.cn/direct/7ca8e2a9c225486f9147ae0f3edd19cb.png" alt="img" style="zoom: 50%;" /><p>(3) 聚集索引选取规则：</p><ul><li>如果存在主键，主键索引就是聚集索引</li><li>如果不存在主键，将使用第一个唯一(UNIQUE)索引作为聚集索引</li><li>如果表没有主键或没有合适的唯一索引，则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引</li></ul><p>(4) 思考题</p><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><span class="line">3</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> <span class="keyword">user</span> <span class="keyword">where</span> id <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;Arm&#x27;</span>;</span><br><span class="line"><span class="comment">-- 备注：id为主键，name字段创建的有索引</span></span><br></pre></td></tr></table></figure><p>答：第一条语句，因为第二条需要回表查询，相当于两个步骤。</p><p>(2) InnoDB 主键索引的 B+Tree 高度为多少？</p><p>答：假设一行数据大小为1k，一页中可以存储16行这样的数据。InnoDB 的指针占用6个字节的空间，主键假设为bigint，占用字节数为8，可得公式：</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">n×<span class="number">8</span>（主键总大小） <span class="operator">+</span> (n<span class="operator">+</span><span class="number">1</span>)×<span class="number">6</span>（指针总大小） <span class="operator">=</span> <span class="number">16</span>×<span class="number">1024</span>（页大小）</span><br></pre></td></tr></table></figure><p>其中 8 表示 bigint 占用的字节数，n 表示当前节点存储的key的数量，(n + 1) 表示指针数量（比key多一个）。算出n约为1170。</p><p>① 如果树的高度为2，那么他能存储的数据量为：</p><ul><li>根节点是 1 个非叶子节点，有 1171 个指针；</li><li>每个指针对应 1 个叶子节点，每个叶子节点存 16 行；</li><li>总数据量 &#x3D; 1171（叶子节点数量） × 16（每个叶子节点行数） &#x3D; 18736 行。</li></ul><p>② 如果树的高度为3，那么他能存储的数据量为：<code>1171 * 1171 * 16 = 21939856</code>。</p><ul><li>根节点有 1171 个指针，对应 1171 个中间层非叶子节点；</li><li>每个中间层非叶子节点又有 1171 个指针，对应 1171 个叶子节点；</li><li>总数据量 &#x3D; 1171（中间层数量） × 1171（叶子节点数量） × 16（每个叶子节点行数） ≈ 2193 万行。</li></ul><h4 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h4><p>(1) 创建索引：</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">UNIQUE</span> <span class="operator">|</span> FULLTEXT ] INDEX index_name <span class="keyword">ON</span> table_name (index_col_name, ...);</span><br></pre></td></tr></table></figure><p>如果 CREATE 后面不加索引类型参数，则创建的是常规索引</p><p>(2) 查看索引：</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">SHOW</span> INDEX <span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><p>(3) 删除索引：</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">DROP</span> INDEX index_name <span class="keyword">ON</span> table_name;</span><br></pre></td></tr></table></figure><p>(4) 案例：</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><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="comment">-- name字段为姓名字段，该字段的值可能会重复，为该字段创建索引</span></span><br><span class="line"><span class="keyword">create</span> index idx_user_name <span class="keyword">on</span> tb_user(name);</span><br><span class="line"><span class="comment">-- phone手机号字段的值非空，且唯一，为该字段创建唯一索引</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">unique</span> index idx_user_phone <span class="keyword">on</span> tb_user (phone);</span><br><span class="line"><span class="comment">-- 为profession, age, status创建联合索引</span></span><br><span class="line"><span class="keyword">create</span> index idx_user_pro_age_stat <span class="keyword">on</span> tb_user(profession, age, status);</span><br><span class="line"><span class="comment">-- 为email建立合适的索引来提升查询效率</span></span><br><span class="line"><span class="keyword">create</span> index idx_user_email <span class="keyword">on</span> tb_user(email);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除索引</span></span><br><span class="line"><span class="keyword">drop</span> index idx_user_email <span class="keyword">on</span> tb_user;</span><br></pre></td></tr></table></figure><h4 id="性能分析"><a href="#性能分析" class="headerlink" title="性能分析"></a>性能分析</h4><p>(1) 查看执行频次</p><p>查看当前数据库的 INSERT, UPDATE, DELETE, SELECT 访问频次：</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">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Com_______&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(2) 慢查询日志</p><p>① 慢查询日志记录了所有执行时间超过指定参数（long_query_time，单位：秒，默认10秒）的所有SQL语句的日志。</p><p>② MySQL的慢查询日志默认没有开启，需要在MySQL的配置文件（&#x2F;etc&#x2F;my.cnf）中配置如下信息：</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><br><span class="line">slow_query_log<span class="operator">=</span><span class="number">1</span></span><br><span class="line"># 设置慢查询日志的时间为<span class="number">2</span>秒，<span class="keyword">SQL</span>语句执行时间超过<span class="number">2</span>秒，就会视为慢查询，记录慢查询日志</span><br><span class="line">long_query_time<span class="operator">=</span><span class="number">2</span></span><br></pre></td></tr></table></figure><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">show</span> variables <span class="keyword">like</span> <span class="string">&#x27;slow_query_log&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(3) profile</p><p>① show profile 能在做SQL优化时帮我们了解时间都耗费在哪里。通过 have_profiling 参数，能看到当前 MySQL 是否支持 profile 操作：</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="variable">@have_profiling</span>;</span><br></pre></td></tr></table></figure><p>② profiling 默认关闭，可以通过set语句在session&#x2F;global级别开启 profiling：</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> profiling <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><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">show</span> profiles;</span><br></pre></td></tr></table></figure><p>④ 查看指定query_id的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">show</span> profile <span class="keyword">for</span> query query_id;</span><br></pre></td></tr></table></figure><p>⑤ 查看指定query_id的SQL语句CPU的使用情况</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">show</span> profile cpu <span class="keyword">for</span> query query_id;</span><br></pre></td></tr></table></figure><p>(4) explain</p><p>① EXPLAIN 或者 DESC 命令获取 MySQL 如何执行 SELECT 语句的信息，包括在 SELECT 语句执行过程中表如何连接和连接的顺序。</p><p>② 语法：</p><figure class="highlight bash"><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"># 直接在select语句之前加上关键字 explain / desc</span></span><br><span class="line">EXPLAIN SELECT 字段列表 FROM 表名 HWERE 条件;</span><br></pre></td></tr></table></figure><p>③ EXPLAIN 各字段含义：</p><ul><li>id：select 查询的序列号，表示查询中执行 select 子句或者操作表的顺序（id相同，执行顺序从上到下；id不同，值越大越先执行）</li><li>select_type：表示 SELECT 的类型，常见取值有 SIMPLE（简单表，即不适用表连接或者子查询）、PRIMARY（主查询，即外层的查询）、UNION（UNION中的第二个或者后面的查询语句）、SUBQUERY（SELECT&#x2F;WHERE之后包含了子查询）等</li><li>type：表示连接类型，性能由好到差的连接类型为 NULL、system、const、eq_ref、ref、range、index、all</li><li>possible_key：可能应用在这张表上的索引，一个或多个</li><li>Key：实际使用的索引，如果为 NULL，则没有使用索引</li><li>Key_len：表示索引中使用的字节数，该值为索引字段最大可能长度，并非实际使用长度，在不损失精确性的前提下，长度越短越好</li><li>rows：MySQL认为必须要执行的行数，在InnoDB引擎的表中，是一个估计值，可能并不总是准确的</li><li>filtered：表示返回结果的行数占需读取行数的百分比，filtered的值越大越好</li></ul><h4 id="使用规则"><a href="#使用规则" class="headerlink" title="使用规则"></a>使用规则</h4><p>(1) 最左前缀法则</p><p>① 如果索引关联了多列（联合索引），要遵守最左前缀法则，最左前缀法则指的是查询从索引的最左列开始，并且不跳过索引中的列。</p><p>② 如果跳跃某一列，<strong>索引将部分失效</strong>（后面的字段索引失效）。</p><p>③ 代码示例：</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><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">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> profession <span class="operator">=</span> <span class="string">&#x27;软件工程&#x27;</span> <span class="keyword">and</span> age <span class="operator">=</span> <span class="number">31</span> <span class="keyword">and</span> status <span class="operator">=</span> <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> age <span class="operator">=</span> <span class="number">31</span> <span class="keyword">and</span> profession <span class="operator">=</span> <span class="string">&#x27;软件工程&#x27;</span> <span class="keyword">and</span> status <span class="operator">=</span> <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 不走索引，全部失效</span></span><br><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> age <span class="operator">=</span> <span class="number">31</span> <span class="keyword">and</span> status <span class="operator">=</span> <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 部分失效</span></span><br><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> profession <span class="operator">=</span> <span class="string">&#x27;软件工程&#x27;</span> <span class="keyword">and</span> status <span class="operator">=</span> <span class="string">&#x27;0&#x27;</span>;</span><br></pre></td></tr></table></figure><p>④ 联合索引中，出现范围查询（&lt;, &gt;），范围查询右侧的列索引失效。可以用 &gt;&#x3D; 或者 &lt;&#x3D; 来规避索引失效问题。</p><p>(2) 索引失效情况</p><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">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> <span class="built_in">substring</span>(phone, <span class="number">10</span>, <span class="number">2</span>) <span class="operator">=</span> <span class="string">&#x27;15&#x27;</span>;</span><br></pre></td></tr></table></figure><p>② 字符串类型字段使用时，不加引号，索引将失效。例如：</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="comment">-- 此处phone的值没有加引号</span></span><br><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> phone <span class="operator">=</span> <span class="number">17799990015</span>;</span><br></pre></td></tr></table></figure><p>③ 模糊查询中，如果仅仅是尾部模糊匹配，索引不会是失效；如果是头部模糊匹配，索引失效。例如：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 索引失效 </span></span><br><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> profession <span class="keyword">like</span> <span class="string">&#x27;软件%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 索引不失效</span></span><br><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> profession <span class="keyword">like</span> <span class="string">&#x27;%工程&#x27;</span>;</span><br></pre></td></tr></table></figure><p>④ 用 or 分割开的条件，如果 or 其中一个条件的列没有索引，那么涉及的索引都不会被用到。只有两侧都有索引的时候，索引才会生效。如果要让数据的索引不失效，那么要给没有索引的创建索引。</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"><span class="comment">-- 假设 age 无索引，则第一句索引失效</span></span><br><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> id <span class="operator">=</span> <span class="number">10</span> <span class="keyword">or</span> age <span class="operator">=</span> <span class="number">23</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 为 age 创建索引后，索引成功</span></span><br><span class="line"><span class="keyword">create</span> index idx_user_age <span class="keyword">on</span> tb_user(age);</span><br><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> id <span class="operator">=</span> <span class="number">10</span> <span class="keyword">or</span> age <span class="operator">=</span> <span class="number">23</span>;</span><br></pre></td></tr></table></figure><p>⑤ 如果 MySQL 评估使用索引比全表更慢，则不使用索引。</p><h4 id="SQL-提示"><a href="#SQL-提示" class="headerlink" title="SQL 提示"></a>SQL 提示</h4><p>这是优化数据库的一个重要手段，简单来说，就是在SQL语句中加入一些人为的提示来达到优化操作的目的。例如：</p><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">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user use index(idx_user_pro) <span class="keyword">where</span> profession<span class="operator">=</span>&quot;软件工程&quot;;</span><br></pre></td></tr></table></figure><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">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user ignore index(idx_user_pro) <span class="keyword">where</span> profession<span class="operator">=</span>&quot;软件工程&quot;;</span><br></pre></td></tr></table></figure><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">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_user force index(idx_user_pro) <span class="keyword">where</span> profession<span class="operator">=</span>&quot;软件工程&quot;;</span><br></pre></td></tr></table></figure><p>use 是建议，实际使用哪个索引 MySQL 还会自己权衡运行速度去更改，force就是无论如何都强制使用该索引。</p><h4 id="覆盖索引-回表查询"><a href="#覆盖索引-回表查询" class="headerlink" title="覆盖索引&amp;回表查询"></a>覆盖索引&amp;回表查询</h4><p>(1) 尽量使用覆盖索引（查询使用了索引，并且需要返回的列，在该索引中已经全部能找到），减少 select *。explain 中 extra 字段含义：</p><p>① <code>using index condition</code>：查找使用了索引，但是需要回表查询数据</p><p>② <code>using where; using index;</code>：查找使用了索引，但是需要的数据都在索引列中能找到，所以不需要回表查询</p><p>(2) 如果在辅助索引中找聚集索引，只需要通过辅助索引(name)查找到对应的id，返回name和name索引对应的id即可，只需要一次查询。例如：</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> id, name <span class="keyword">from</span> xxx <span class="keyword">where</span> name<span class="operator">=</span><span class="string">&#x27;xxx&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(3) 如果是通过辅助索引查找其他字段，则需要回表查询，例如：</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> id, name, gender <span class="keyword">from</span> xxx <span class="keyword">where</span> name<span class="operator">=</span><span class="string">&#x27;xxx&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(4) 所以尽量不要用<code>select *</code>，容易出现回表查询，降低效率，除非有联合索引包含了所有字段</p><img src="https://i-blog.csdnimg.cn/direct/50a1c0a31f024c2594a4062fde46710d.png" alt="img" style="zoom:50%;" /><p>(5) 面试题：一张表，有四个字段（id, username, password, status），由于数据量大，需要对以下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> id, username, password <span class="keyword">from</span> tb_user <span class="keyword">where</span> username<span class="operator">=</span><span class="string">&#x27;itcast&#x27;</span>;</span><br></pre></td></tr></table></figure><p>解：给username和password字段建立联合索引，则不需要回表查询，直接覆盖索引，这个优化的核心就是「让索引覆盖查询所需的所有字段」：</p><ul><li>利用 InnoDB 二级索引默认带主键的特性；</li><li>把查询需要的非主键字段（password、username）加到联合索引里；</li><li>最终实现 “一次索引查询就能拿到所有数据”，彻底规避回表的性能损耗。</li></ul><h4 id="前缀索引"><a href="#前缀索引" class="headerlink" title="前缀索引"></a>前缀索引</h4><p>(1) 当字段类型为字符串（varchar, text等）时，有时候需要索引很长的字符串，这会让索引变得很大，查询时，浪费大量的磁盘IO，影响查询效率，此时可以只将字符串的一部分前缀，建立索引，这样可以大大节约索引空间，从而提高索引效率。</p><p>(2) 语法：</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">create</span> index idx_xxxx <span class="keyword">on</span> table_name(columnn(n));</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 案例：</span></span><br><span class="line"><span class="keyword">create</span> index idx_email_5 <span class="keyword">on</span> tb_user(email(<span class="number">5</span>));</span><br></pre></td></tr></table></figure><p>(3) 前缀长度：可以根据索引的选择性来决定，而选择性是指不重复的索引值（基数）和数据表的记录总数的比值，**索引选择性越高则查询效率越高，**唯一索引的选择性是1，这是最好的索引选择性，性能也是最好的。</p><p>(4) 求选择性公式：</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="built_in">count</span>(<span class="keyword">distinct</span> email) <span class="operator">/</span> <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> tb_user;</span><br><span class="line"><span class="keyword">select</span> <span class="built_in">count</span>(<span class="keyword">distinct</span> <span class="built_in">substring</span>(email, <span class="number">1</span>, <span class="number">5</span>)) <span class="operator">/</span> <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> tb_user;</span><br></pre></td></tr></table></figure><p>show index 里面的sub_part可以看到接取的长度</p><h4 id="单列索引-联合索引"><a href="#单列索引-联合索引" class="headerlink" title="单列索引&amp;联合索引"></a>单列索引&amp;联合索引</h4><p>(1) 概念</p><p>① 单列索引：即一个索引只包含单个列</p><p>② 联合索引：即一个索引包含了多个列</p><p>在业务场景中，如果存在多个查询条件，考虑针对于查询字段建立索引时，建议建立联合索引，而非单列索引。</p><p>(2) 单列索引情况：</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="comment">-- 这句只会用到phone索引字段</span></span><br><span class="line">explain <span class="keyword">select</span> id, phone, name <span class="keyword">from</span> tb_user <span class="keyword">where</span> phone <span class="operator">=</span> <span class="string">&#x27;17799990010&#x27;</span> <span class="keyword">and</span> name <span class="operator">=</span> <span class="string">&#x27;韩信&#x27;</span>;</span><br></pre></td></tr></table></figure><p><strong>注意事项</strong></p><ul><li>多条件联合查询时，MySQL优化器会评估哪个字段的索引效率更高，会选择该索引完成本次查询</li></ul><p>(3) 设计原则</p><p>① 针对于<strong>数据量较大，且查询比较频繁</strong>的表建立索引</p><p>② 针对于常作为查询条件（where）、排序（order by）、分组（group by）操作的字段建立索引</p><p>③ 尽量选择区分度高的列作为索引，尽量**建立唯一索引，**区分度越高，使用索引的效率越高</p><p>④ 如果是字符串类型的字段，字段长度较长，可以针对于字段的特点，建立<strong>前缀索引</strong></p><p>⑤ 尽量<strong>使用联合索引</strong>，减少单列索引，查询时，联合索引很多时候可以覆盖索引，节省存储空间，避免回表，提高查询效率</p><p>⑥ 要控制索引的数量，索引并不是多多益善，索引越多，维护索引结构的代价就越大，会影响增删改的效率</p><p>⑦ 如果索引列不能存储NULL值，请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时，它可以更好地确定哪个索引最有效地用于查询</p><h3 id="SQL-优化"><a href="#SQL-优化" class="headerlink" title="SQL 优化"></a>SQL 优化</h3><h4 id="插入数据"><a href="#插入数据" class="headerlink" title="插入数据"></a>插入数据</h4><h5 id="普通插入"><a href="#普通插入" class="headerlink" title="普通插入"></a>普通插入</h5><p>① 采用批量插入（一次插入的数据不建议超过1000条）</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">Insert into</span> tb_test <span class="keyword">values</span>(<span class="number">1</span>,<span class="string">&#x27;Tom&#x27;</span>),(<span class="number">2</span>,<span class="string">&#x27;Cat&#x27;</span>),(<span class="number">3</span>,<span class="string">&#x27;Jerry&#x27;</span>);</span><br></pre></td></tr></table></figure><p>② 手动提交事务</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">start</span> transaction;</span><br><span class="line"><span class="keyword">insert into</span> tb_test <span class="keyword">values</span>(<span class="number">1</span>,<span class="string">&#x27;Tom&#x27;</span>),(<span class="number">2</span>,<span class="string">&#x27;Cat&#x27;</span>),(<span class="number">3</span>,<span class="string">&#x27;Jerry&#x27;</span>);</span><br><span class="line"><span class="keyword">insert into</span> tb_test <span class="keyword">values</span>(<span class="number">4</span>,<span class="string">&#x27;Tom&#x27;</span>),(<span class="number">5</span>,<span class="string">&#x27;Cat&#x27;</span>),(<span class="number">6</span>,<span class="string">&#x27;Jerry&#x27;</span>);</span><br><span class="line"><span class="keyword">insert into</span> tb_test <span class="keyword">values</span>(<span class="number">7</span>,<span class="string">&#x27;Tom&#x27;</span>),(<span class="number">8</span>,<span class="string">&#x27;Cat&#x27;</span>),(<span class="number">9</span>,<span class="string">&#x27;Jerry&#x27;</span>);</span><br><span class="line"><span class="keyword">commit</span>;</span><br></pre></td></tr></table></figure><p>③ 主键顺序插入</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="number">8</span> <span class="number">1</span> <span class="number">9</span> <span class="number">21</span> <span class="number">88</span> <span class="number">2</span> <span class="number">4</span> <span class="number">15</span> <span class="number">89</span> <span class="number">5</span> <span class="number">7</span> <span class="number">3</span></span><br><span class="line">主键顺序插入：<span class="number">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">7</span> <span class="number">8</span> <span class="number">9</span> <span class="number">15</span> <span class="number">21</span> <span class="number">88</span> <span class="number">89</span></span><br></pre></td></tr></table></figure><h5 id="大批量插入："><a href="#大批量插入：" class="headerlink" title="大批量插入："></a>大批量插入：</h5><p>如果一次性需要插入大批量数据，使用insert语句插入性能较低，此时可以使用MySQL数据库提供的load指令插入。</p><figure class="highlight bash"><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="comment"># 客户端连接服务端时，加上参数 --local-infile（这一行在bash/cmd界面输入）</span></span><br><span class="line">mysql --local-infile -u root -p</span><br><span class="line"><span class="comment"># 设置全局参数local_infile为1，开启从本地加载文件导入数据的开关</span></span><br><span class="line"><span class="built_in">set</span> global local_infile = 1;</span><br><span class="line"><span class="keyword">select</span> @@local_infile;</span><br><span class="line"><span class="comment"># 执行load指令将准备好的数据，加载到表结构中</span></span><br><span class="line">load data <span class="built_in">local</span> infile <span class="string">&#x27;/root/load_user_100w_sort.sql&#x27;</span> into table tb_user fields terminated by <span class="string">&#x27;,&#x27;</span> lines terminated by <span class="string">&#x27;\n&#x27;</span>;</span><br></pre></td></tr></table></figure><h4 id="主键优化"><a href="#主键优化" class="headerlink" title="主键优化"></a>主键优化</h4><h5 id="数据组织方式："><a href="#数据组织方式：" class="headerlink" title="数据组织方式："></a>数据组织方式：</h5><p>在InnoDB存储引擎中，表数据都是根据主键顺序组织存放的，这种存储方式的表称为索引组织表（Index organized table, IOT）</p><p><img src="https://i-blog.csdnimg.cn/direct/84357dbd81d042f593977164f0938ca6.png" alt="img"></p><p>数据库的存储是：</p><p><strong>表空间（Tablespace） → 段（Segment） → 区（Extent） → 页（Page） → 行（Row）</strong></p><h5 id="页分裂："><a href="#页分裂：" class="headerlink" title="页分裂："></a>页分裂：</h5><p>页可以为空，也可以填充一半，也可以填充100%，每个页包含了2-N行数据（如果一行数据过大，会行溢出），根据主键排列。</p><p>① 触发场景：当你往表中插入数据时，InnoDB 会把数据存到对应的页里。如果<strong>当前页已经没有足够空间存新行</strong>（比如页的使用率快满了），就会触发 “页分裂”。</p><p>② 典型过程（以非自增主键插入为例）</p><p><img src="https://i-blog.csdnimg.cn/direct/202f1b67d9d242ef9913ae3689ccd4f7.png" alt="img"></p><p><strong>步骤 1：插入前的页状态</strong></p><ul><li>1# 页：存了主键 <code>1、5、9、23、47</code> 的 5 条数据（页已经快存满了）；</li><li>2# 页：存了主键 <code>55、67、89、101、107</code> 的 5 条数据；</li><li>现在要插入 <strong>主键 &#x3D; 50</strong> 的新行。</li></ul><img src="https://i-blog.csdnimg.cn/direct/5c5fe6f0cfb147a198fe8c050e834513.png" alt="img" style="zoom:50%;" /><p><strong>步骤 2：插入时的 “位置冲突”</strong></p><p>主键是有序存储的，50 的大小介于 1# 页的最大主键（47）和 2# 页的最小主键（55）之间，所以<strong>50 应该存在 1# 和 2# 页之间</strong>。</p><p>但问题是：1# 页已经快存满了，没有空间放 50 这条新数据 → 触发<strong>页分裂</strong>。</p><p><strong>步骤 3：页分裂后的结果</strong></p><p>分裂后：</p><ul><li>1# 页：把原来的部分数据（23、47）移出去，只留 <code>1、5、9</code>；</li><li>新建 3# 页：把移出的 23、47，加上新插入的 50，存在 3# 页（主键<code>23、47、50</code>）；</li><li>2# 页：保持原来的 <code>55、67、89、101、107</code> 不变。</li></ul><p><img src="https://i-blog.csdnimg.cn/direct/bbea732734e049988fe21e7d7b377b9a.png" alt="img"></p><h4 id="页合并："><a href="#页合并：" class="headerlink" title="页合并："></a>页合并：</h4><p>当删除一行记录时，实际上记录并没有被物理删除，只是记录被标记（flaged）为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录到达 MERGE_THRESHOLD（默认为页的50%），InnoDB会开始寻找最靠近的页（前后）看看是否可以将这两个页合并以优化空间使用。</p><p>MERGE_THRESHOLD：合并页的阈值，可以自己设置，在创建表或创建索引时指定</p><h4 id="主键设计原则："><a href="#主键设计原则：" class="headerlink" title="主键设计原则："></a>主键设计原则：</h4><ul><li>满足业务需求的情况下，尽量降低主键的长度。主键越长，每个 “索引页（Page）” 能存的主键数量就越少 → 索引树的层数会变多</li><li>插入数据时，尽量选择顺序插入，选择使用 AUTO_INCREMENT 自增主键</li><li>尽量不要使用 UUID 做主键或者是其他的自然主键，如身份证号。UUID 是随机生成的，插入时会像之前 “插入 50” 那样，随机插在页的中间位置 → 频繁触发页分裂，既耗性能又产生碎片；</li><li>业务操作时，避免对主键的修改</li></ul><h4 id="order-by-优化"><a href="#order-by-优化" class="headerlink" title="order by 优化"></a>order by 优化</h4><h5 id="Using-filesort："><a href="#Using-filesort：" class="headerlink" title="Using filesort："></a>Using filesort：</h5><p>通过表的索引或全表扫描，读取满足条件的数据行，然后在排序缓冲区 sort buffer 中完成排序操作，所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序</p><h5 id="Using-index："><a href="#Using-index：" class="headerlink" title="Using index："></a>Using index：</h5><p>通过有序索引顺序扫描直接返回有序数据，这种情况即为 using index，不需要额外排序，操作效率高</p><p>(3) 如果order by字段全部使用升序排序或者降序排序，则都会走索引，但是如果一个字段升序排序，另一个字段降序排序，则不会走索引，explain的extra信息显示的是<code>Using index, Using filesort</code>，如果要优化掉Using filesort，则需要另外再创建一个索引，如：<code>create index idx_user_age_phone_ad on tb_user(age asc, phone desc);</code>，此时使用<code>select id, age, phone from tb_user order by age asc, phone desc;</code>会全部走索引</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建索引</span></span><br><span class="line"><span class="keyword">create</span> index idx_user_age_phone <span class="keyword">on</span> tb_user(age,phone);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 走索引</span></span><br><span class="line">explain <span class="keyword">select</span> id, age, phone <span class="keyword">from</span> tb_user <span class="keyword">order</span> <span class="keyword">by</span> age <span class="keyword">desc</span>, phone <span class="keyword">desc</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 不走索引，因为索引是 “先age，后phone” 排序的，而 order by 是 “先phone,后age”</span></span><br><span class="line">explain <span class="keyword">select</span> id, age, phone <span class="keyword">from</span> tb_user <span class="keyword">order</span> <span class="keyword">by</span> phone, age;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 不走索引</span></span><br><span class="line">explain <span class="keyword">select</span> id, age, phone <span class="keyword">from</span> tb_user <span class="keyword">order</span> <span class="keyword">by</span> age, phone <span class="keyword">desc</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建新的索引后，上面那句走索引</span></span><br><span class="line"><span class="keyword">create</span> index idx_user_age_pho_ad <span class="keyword">on</span> tb_user(age <span class="keyword">asc</span>, phone <span class="keyword">desc</span>);</span><br></pre></td></tr></table></figure><p>(4) 总结：</p><ul><li>根据排序字段建立合适的索引，多字段排序时，也遵循最左前缀法则</li><li>尽量使用覆盖索引</li><li>多字段排序，一个升序一个降序，此时需要注意联合索引在创建时的规则（ASC&#x2F;DESC）</li><li>如果不可避免出现filesort，大数据量排序时，可以适当增大排序缓冲区大小 sort_buffer_size（默认256k）</li></ul><h4 id="group-by优化"><a href="#group-by优化" class="headerlink" title="group by优化"></a>group by优化</h4><p>(1) 在分组操作时，可以通过索引来提高效率</p><p>(2) 分组操作时，索引的使用也是<strong>遵循最左前缀法则</strong>的</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建索引</span></span><br><span class="line"><span class="keyword">create</span> index idx_user_pro_age_sta <span class="keyword">on</span> tb_user(profession,age,status);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 走索引</span></span><br><span class="line">explain <span class="keyword">select</span> profession, <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> tb_user <span class="keyword">group</span> <span class="keyword">by</span> profession;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 不走索引，不满足最左前缀法则</span></span><br><span class="line">explain <span class="keyword">select</span> age, <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> tb_user <span class="keyword">group</span> <span class="keyword">by</span> age;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 走索引</span></span><br><span class="line">explain <span class="keyword">select</span> profession, age, <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> tb_user <span class="keyword">group</span> <span class="keyword">by</span> profession, age;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 走索引</span></span><br><span class="line">explain <span class="keyword">select</span> profession, age, <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> tb_user <span class="keyword">where</span> profession <span class="operator">=</span> <span class="string">&#x27;软件工程&#x27;</span> <span class="keyword">group</span> <span class="keyword">by</span> age;</span><br></pre></td></tr></table></figure><p>如索引为<code>idx_user_pro_age_stat</code>，则句式可以是<code>select ... where profession order by age</code>，这样也符合最左前缀法则</p><h4 id="limit优化"><a href="#limit优化" class="headerlink" title="limit优化"></a>limit优化</h4><h5 id="常见的问题："><a href="#常见的问题：" class="headerlink" title="常见的问题："></a>常见的问题：</h5><p>如<code>limit 2000000, 10</code>，此时需要 MySQL 排序前2000000条记录，但仅仅返回2000000 - 2000010的记录，其他记录丢弃，查询排序的代价非常大。</p><h5 id="优化方案："><a href="#优化方案：" class="headerlink" title="优化方案："></a>优化方案：</h5><p>一般分页查询时，通过创建覆盖索引能够比较好地提高性能，可以通过覆盖索引加子查询形式进行优化</p><h5 id="例如："><a href="#例如：" class="headerlink" title="例如："></a>例如：</h5><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><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="operator">-</span> 此语句耗时很长</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_sku limit <span class="number">9000000</span>, <span class="number">10</span>;</span><br><span class="line"><span class="comment">-- 通过覆盖索引加快速度，直接通过主键索引进行排序及查询</span></span><br><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> tb_sku <span class="keyword">order</span> <span class="keyword">by</span> id limit <span class="number">9000000</span>, <span class="number">10</span>;</span><br><span class="line"><span class="comment">-- 下面的语句是错误的，因为 MySQL 不支持 in 里面使用 limit</span></span><br><span class="line"><span class="comment">-- select * from tb_sku where id in (select id from tb_sku order by id limit 9000000, 10);</span></span><br><span class="line"><span class="comment">-- 通过连表查询即可实现第一句的效果，并且能达到第二句的速度</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_sku <span class="keyword">as</span> s, (<span class="keyword">select</span> id <span class="keyword">from</span> tb_sku <span class="keyword">order</span> <span class="keyword">by</span> id limit <span class="number">9000000</span>, <span class="number">10</span>) <span class="keyword">as</span> a <span class="keyword">where</span> s.id <span class="operator">=</span> a.id;</span><br></pre></td></tr></table></figure><h4 id="count优化"><a href="#count优化" class="headerlink" title="count优化"></a>count优化</h4><p>(1) MyISAM 引擎把一个表的总行数存在了磁盘上，因此执行 count(*) 的时候会直接返回这个数，效率很高（前提是不适用where）；</p><p>(2) InnoDB 在执行 count(*) 时，需要把数据一行一行地从引擎里面读出来，然后累计计数。</p><p>(3) 优化方案：自己计数，如创建key-value表存储在内存或硬盘，或者是用redis</p><p>(4) count 的几种用法：</p><p>① 如果 count 函数的参数（count里面写的那个字段）不是NULL（字段值不为NULL），累计值就加一，最后返回累计值</p><p>② 用法：count(*)、count(主键)、count(字段)、count(1)</p><p>③ count(主键) 跟 count(<em>) 一样，因为主键不能为空；count(字段) 只计算字段值不为NULL的行；count(1)引擎会为每行添加一个1，然后就count这个1，返回结果也跟count(</em>)一样；count(null)返回0</p><p>(5) 各种用法的性能：</p><p>① count(主键)：InnoDB引擎会遍历整张表，把每行的主键id值都取出来，返回给服务层，服务层拿到主键后，直接按行进行累加（主键不可能为空）</p><p>② count(字段)：没有not null约束的话，InnoDB引擎会遍历整张表把每一行的字段值都取出来，返回给服务层，服务层判断是否为null，不为null，计数累加；有not null约束的话，InnoDB引擎会遍历整张表把每一行的字段值都取出来，返回给服务层，直接按行进行累加</p><p>③ count(1)：InnoDB 引擎遍历整张表，但不取值。服务层对于返回的每一层，放一个数字 1 进去，直接按行进行累加</p><p>④ count(*)：InnoDB 引擎并不会把全部字段取出来，而是专门做了优化，不取值，服务层直接按行进行累加</p><p>按效率排序：count(字段) &lt; count(主键) &lt; count(1) &lt; count(<em>)，所以尽量使用 count(</em>)</p><h4 id="update优化（避免行锁升级为表锁）"><a href="#update优化（避免行锁升级为表锁）" class="headerlink" title="update优化（避免行锁升级为表锁）"></a>update优化（避免行锁升级为表锁）</h4><p>(1) InnoDB 的行锁是针对索引加的锁，不是针对记录加的锁，并且该索引不能失效，否则会从行锁升级为表锁。</p><p>(2) 案例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 这句由于id有主键索引，所以只会锁这一行</span></span><br><span class="line"><span class="keyword">update</span> student <span class="keyword">set</span> <span class="keyword">no</span> <span class="operator">=</span> <span class="string">&#x27;123&#x27;</span> <span class="keyword">where</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 这句由于name没有索引，所以会把整张表都锁住进行数据更新，解决方法是给name字段添加索引</span></span><br><span class="line"><span class="keyword">update</span> student <span class="keyword">set</span> <span class="keyword">no</span> <span class="operator">=</span> <span class="string">&#x27;123&#x27;</span> <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;test&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="视图"><a href="#视图" class="headerlink" title="视图"></a>视图</h3><h4 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h4><p>(1) 视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在，行和列数据来自定义视图的查询中使用的表，并且是在使用视图时动态生成的。</p><p>(2) 通俗的讲，视图只保存了查询的SQL逻辑，不保存查询结果。所以我们在创建视图的时候，主要的工作就落在创建这条SQL查询语句上。</p><h4 id="语法-1"><a href="#语法-1" class="headerlink" title="语法"></a>语法</h4><h5 id="创建视图："><a href="#创建视图：" class="headerlink" title="创建视图："></a>创建视图：</h5><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">OR</span> REPLACE] <span class="keyword">VIEW</span> 视图名称[(列名列表)] <span class="keyword">AS</span> <span class="keyword">SELECT</span>语句 [<span class="keyword">WITH</span>[<span class="keyword">CASCADED</span> <span class="operator">|</span> <span class="keyword">LOCAL</span>] <span class="keyword">CHECK</span> OPTION</span><br></pre></td></tr></table></figure><h5 id="查询视图："><a href="#查询视图：" class="headerlink" title="查询视图："></a>查询视图：</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">SHOW</span> CRETE <span class="keyword">VIEW</span> 视图名称;</span><br></pre></td></tr></table></figure><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">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> 视图名称…;</span><br></pre></td></tr></table></figure><h5 id="修改视图："><a href="#修改视图：" class="headerlink" title="修改视图："></a>修改视图：</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">CREATE</span> [<span class="keyword">OR</span> REPLACE] <span class="keyword">VIEW</span> 视图名称[(列名列表)] <span class="keyword">AS</span> <span class="keyword">SELECT</span>语句 [<span class="keyword">WITH</span>[<span class="keyword">CASCADED</span> <span class="operator">|</span> <span class="keyword">LOCAL</span>] <span class="keyword">CHECK</span> OPTION</span><br></pre></td></tr></table></figure><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">ALTER</span> <span class="keyword">VIEW</span> 视图名称[(列名列表)] <span class="keyword">AS</span> <span class="keyword">SELECT</span>语句 [<span class="keyword">WITH</span>[<span class="keyword">CASCADED</span> <span class="operator">|</span> <span class="keyword">LOCAL</span>] <span class="keyword">CHECK</span> OPTION]</span><br></pre></td></tr></table></figure><h5 id="删除视图："><a href="#删除视图：" class="headerlink" title="删除视图："></a>删除视图：</h5><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">DROP</span> <span class="keyword">VIEW</span> [IF <span class="keyword">EXISTS</span>]视图名称 [,视图名称]</span><br></pre></td></tr></table></figure><h5 id="案例："><a href="#案例：" class="headerlink" title="案例："></a>案例：</h5><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建视图</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace <span class="keyword">view</span> stu_v_1 <span class="keyword">as</span> <span class="keyword">select</span> id, name <span class="keyword">from</span> student <span class="keyword">where</span> id <span class="operator">&lt;=</span> <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询视图</span></span><br><span class="line"><span class="keyword">show</span> <span class="keyword">create</span> <span class="keyword">view</span> stu_v_1;</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> stu_v_1;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 修改视图</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace <span class="keyword">view</span> stu_v_1 <span class="keyword">as</span> <span class="keyword">select</span> id, name, <span class="keyword">no</span> <span class="keyword">from</span> student <span class="keyword">where</span> id <span class="operator">&lt;=</span> <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">view</span> stu_v_1 <span class="keyword">as</span> <span class="keyword">select</span> id, name <span class="keyword">from</span> student <span class="keyword">where</span> id <span class="operator">&lt;=</span> <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除视图</span></span><br><span class="line"><span class="keyword">drop</span> <span class="keyword">view</span> if <span class="keyword">exists</span> stu_v_1;</span><br></pre></td></tr></table></figure><h4 id="检查选项"><a href="#检查选项" class="headerlink" title="检查选项"></a>检查选项</h4><h5 id="视图的检查选项："><a href="#视图的检查选项：" class="headerlink" title="视图的检查选项："></a><strong>视图的检查选项：</strong></h5><p>① 当使用 WITH CHECK OPTION 子句创建视图时，MySQL 会通过视图检查正在更改的每个行，例如：插入，更新，删除，以使其符合视图的定义。MySQL 允许基于另一个视图创建视图，它还会检查依赖视图中的规则以保持一致性。</p><p>② 为了确定检查的范围，mysql 提供了两个选项:CASCADED 和 LOCAL，默认值为CASCADED。</p><ul><li><strong>cascaded：<strong>在对创建时含有该字段的视图，插入数据时，该视图依赖的视图都会加上检查，需要</strong>所有条件</strong>都满足才能够插入成功。</li><li><strong>local：<strong>在对创建时含有该字段的视图，插入数据时，对于该视图依赖的视图中</strong>含有检查语句的条件</strong>进行检查判断。</li></ul><h5 id="with-cascaded-check-option-的案例："><a href="#with-cascaded-check-option-的案例：" class="headerlink" title="with cascaded check option 的案例："></a>with cascaded check option 的案例：</h5><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><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="comment">-- 创建视图，但不加 with check option</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace <span class="keyword">view</span> stu_v_1 <span class="keyword">as</span> <span class="keyword">select</span> id, name <span class="keyword">from</span> student <span class="keyword">where</span> id <span class="operator">&lt;=</span> <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入成功，会存入基表中（基表无约束），而且会显示在stu_v1视图中</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_1 <span class="keyword">values</span>(<span class="number">5</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入成功，虽然id=25不满足stu_v1的id &lt;=20，但插入操作会成功（数据会存入student基表，但不会显示在stu_v1视图中），插入成功。</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_1 <span class="keyword">values</span>(<span class="number">25</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace <span class="keyword">view</span> stu_v_2 <span class="keyword">as</span> <span class="keyword">select</span> id, name <span class="keyword">from</span> stu_v_1 <span class="keyword">where</span> id <span class="operator">&gt;=</span> <span class="number">10</span> <span class="keyword">with</span> <span class="keyword">cascaded</span> <span class="keyword">check</span> option;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入失败，因为不满足stu_v_2的条件：id &gt;= 10</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_2 <span class="keyword">values</span>(<span class="number">7</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入失败，因为不满足基表stu_v1的条件：id &lt;= 20</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_2 <span class="keyword">values</span>(<span class="number">26</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入成功</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_1 <span class="keyword">values</span>(<span class="number">8</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br></pre></td></tr></table></figure><p>① 向<strong>无<code>WITH CHECK OPTION</code>的视图</strong>插入数据时，只要满足<strong>基表（<code>student</code>）的约束</strong>，插入会成功（但不满足视图筛选条件的行，不会显示在视图中）。</p><p>② 向<strong>有<code>WITH CHECK OPTION</code>的视图</strong>插入数据时，必须满足<strong>当前视图 + 所有底层视图的筛选条件</strong>（<code>cascaded</code>是级联检查所有底层视图），否则插入失败。</p><h5 id="with-local-check-option-的案例："><a href="#with-local-check-option-的案例：" class="headerlink" title="with local check option 的案例："></a>with local check option 的案例：</h5><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建视图</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace <span class="keyword">view</span> stu_v_4 <span class="keyword">as</span> <span class="keyword">select</span> id, name <span class="keyword">from</span> student <span class="keyword">where</span> id <span class="operator">&lt;=</span> <span class="number">15</span> <span class="keyword">with</span> <span class="keyword">local</span> <span class="keyword">check</span> option;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入成功，id = 5 满足 stu_v4 的 id &lt;= 15，数据存入基表并且会显示在 stu_v4 中</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_4 <span class="keyword">values</span>(<span class="number">5</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入失败，16 &gt; 15 不满足 stu_v_4 的 Local 约束，会被直接拦截</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_4 <span class="keyword">values</span>(<span class="number">16</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace <span class="keyword">view</span> stu_v_5 <span class="keyword">as</span> <span class="keyword">select</span> id, name <span class="keyword">from</span> stu_v_4 <span class="keyword">where</span> id <span class="operator">&gt;=</span> <span class="number">10</span> <span class="keyword">with</span> <span class="keyword">local</span> <span class="keyword">check</span> option;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入成功</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_5 <span class="keyword">values</span> (<span class="number">13</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入失败，因为不满足 stu_v_4 的约束 id &lt;= 15</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_5 <span class="keyword">values</span> (<span class="number">17</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">insert into</span> stu_v_5 <span class="keyword">values</span> (<span class="number">18</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace <span class="keyword">view</span> stu_v_6 <span class="keyword">as</span> <span class="keyword">select</span> id, name <span class="keyword">from</span> stu_v_5 <span class="keyword">where</span> id <span class="operator">&lt;</span> <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入成功，stu_v_6 无 check option，但插入会触发底层的 stu_v_5 的 local 约束</span></span><br><span class="line"><span class="comment">-- 14 &gt;= 10 满足 stu_v_5 的条件，14 &lt;= 15 满足 stu_v_4 的条件</span></span><br><span class="line"><span class="keyword">insert into</span> stu_v_6 <span class="keyword">values</span> (<span class="number">14</span>,<span class="string">&#x27;Tom&#x27;</span>);</span><br></pre></td></tr></table></figure><h5 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h5><p>① 没有 <code>CHECK OPTION</code>：不会检查当前视图的<code>WHERE</code>条件，即使数据不满足该条件，也能成功存入<strong>基表</strong>；但后续查询该视图时，这条不满足条件的数据<strong>不会在视图结果中显示</strong></p><p>② 有 check option：会按<code>LOCAL</code>&#x2F;<code>CASCADED</code>的规则检查对应范围的<code>WHERE</code>条件（当前视图 &#x2F; 底层视图），不满足条件则<strong>插入 &#x2F; 更新操作直接失败</strong>，数据既不会存入基表，自然也不会在视图中显示</p><h4 id="更新及作用"><a href="#更新及作用" class="headerlink" title="更新及作用"></a>更新及作用</h4><h5 id="视图的更新："><a href="#视图的更新：" class="headerlink" title="视图的更新："></a><strong>视图的更新：</strong></h5><p>要使视图可更新，视图中的行与基础表中的行之间<strong>必须存在一对一的关系</strong>。如果视图包含以下任何一项，则该<strong>视图不可更新</strong>：</p><ul><li>聚合函数或窗口函数(SUM()、MIN()、MAX()、COUNT()等</li><li>DISTINCT</li><li>GROUP BY</li><li>HAVINGA</li><li>UNION 或者 UNION ALL</li></ul><h5 id="作用："><a href="#作用：" class="headerlink" title="作用："></a><strong>作用：</strong></h5><p>**① 简单：**视图不仅可以简化用户对数据的理解，也可以简化他们的操作。那些被经常使用的查询可以被定义为视图，从而使得用户不必为以后的操作每次指定全部的条件。</p><p>**② 安全：**数据库可以授权，但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据。</p><p>**③ 数据独立：**视图可帮助用户屏蔽真实表结构变化带来的影响。</p><h5 id="案例：-1"><a href="#案例：-1" class="headerlink" title="案例："></a>案例：</h5><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><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="comment">-- 1.为了保证数据库表的安全性，开发人员在操作tb_user表时，只能看到的用户的基本字段，屏蔽手机号和邮箱两个字段。</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> tb <span class="keyword">user</span> <span class="keyword">view</span> <span class="keyword">as</span> <span class="keyword">select</span> id,name,profession, age,gender,status,createtime <span class="keyword">from</span> tb_user;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span><span class="keyword">from</span> tb <span class="keyword">user</span> <span class="keyword">view</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2.查询每个学生所选修的课程（三张表联查），这个功能在很多的业务中都有使用到，为了简化操作，定义一个视图。</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> tb_stu_course_view </span><br><span class="line"><span class="keyword">select</span> s.name student_name, s.no student_no, c.name course_name </span><br><span class="line"><span class="keyword">from</span> student s, stuent_course sc, course c </span><br><span class="line"><span class="keyword">where</span> s.id <span class="operator">=</span> sc.studentid <span class="keyword">and</span> sc.courseid <span class="operator">=</span> c.id;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 以后每次只需要进行查询视图即可</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_stu_course_view;</span><br></pre></td></tr></table></figure><h3 id="存储过程"><a href="#存储过程" class="headerlink" title="存储过程"></a>存储过程</h3><h4 id="介绍-1"><a href="#介绍-1" class="headerlink" title="介绍"></a>介绍</h4><p>(1) 存储过程是事先经过编译并存储在数据库中的一段 SQL语句的集合，调用存储过程可以简化应用开发人员的很多工作，减少数据在数据库和应用服务器之间的传输，对于提高数据处理的效率是有好处的。</p><p>(2) 存储过程思想上很简单，就是数据库 SOL语言层面的代码封装与重用。</p><h4 id="特点："><a href="#特点：" class="headerlink" title="特点："></a><strong>特点：</strong></h4><ul><li>封装，复用</li><li>可以接收参数，也可以返回数据</li><li>减少网络交互，效率提升</li></ul><h4 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h4><h5 id="创建："><a href="#创建：" class="headerlink" title="创建："></a>创建：</h5><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">CREATE</span> <span class="keyword">PROCEDURE</span> 存储过程名称([参数列表])</span><br><span class="line"><span class="keyword">BEGIN</span></span><br><span class="line">    <span class="comment">-- 这里写要执行的SQL语句（比如查询、插入、更新等）</span></span><br><span class="line"><span class="keyword">END</span>;</span><br></pre></td></tr></table></figure><h5 id="调用："><a href="#调用：" class="headerlink" title="调用："></a>调用：</h5><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">CALL</span> 名称([参数]);</span><br></pre></td></tr></table></figure><h5 id="查看："><a href="#查看：" class="headerlink" title="查看："></a>查看：</h5><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查询数据库的存储过程及状态信息</span></span><br><span class="line"><span class="keyword">SELECT</span><span class="operator">*</span> <span class="keyword">FROM</span> INFORMATION SCHEMA.ROUTINES <span class="keyword">WHERE</span> ROUTINE_SCHEMA<span class="operator">=</span><span class="string">&#x27;xx&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询某个存储过程的定义</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">CREATE</span> <span class="keyword">PROCEDURE</span> 存储过程名称;</span><br></pre></td></tr></table></figure><h5 id="删除："><a href="#删除：" class="headerlink" title="删除："></a>删除：</h5><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">DROP</span> <span class="keyword">PROCEDURE</span> [IF <span class="keyword">EXISTS</span>] 存储过程名称;</span><br></pre></td></tr></table></figure><h5 id="案例：-2"><a href="#案例：-2" class="headerlink" title="案例："></a>案例：</h5><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><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="comment">-- 创建</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p1()</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">  <span class="keyword">select</span> <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> student;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 调用</span></span><br><span class="line"><span class="keyword">call</span> p1();</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> information_schema.ROUTINES <span class="keyword">where</span> ROUTINE_SCHEMA <span class="operator">=</span> <span class="string">&#x27;itcast&#x27;</span>;</span><br><span class="line"><span class="keyword">show</span> <span class="keyword">create</span> <span class="keyword">procedure</span> p1;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除</span></span><br><span class="line"><span class="keyword">drop</span> <span class="keyword">procedure</span> if <span class="keyword">exists</span> p1;</span><br></pre></td></tr></table></figure><h4 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h4><h5 id="系统变量"><a href="#系统变量" class="headerlink" title="系统变量"></a>系统变量</h5><p>① 系统变量 是 MySQL 服务器提供，不是用户定义的，属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)。</p><p>② 查看系统变量</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><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查看所有系统变量</span></span><br><span class="line"><span class="keyword">SHOW</span> [SESSION <span class="operator">|</span><span class="keyword">GLOBAL</span>] VARIABLES ; </span><br><span class="line"></span><br><span class="line"><span class="comment">-- 可以通过LKE模糊匹配方式查找变量</span></span><br><span class="line"><span class="keyword">SHOW</span>[SESSION<span class="operator">|</span><span class="keyword">GLOBAL</span>] VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;......&#x27;</span>; </span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看指定变量的值</span></span><br><span class="line"><span class="keyword">SELECT</span> @@[SESSION<span class="operator">|</span><span class="keyword">GLOBAL</span>] 系统变量名; </span><br></pre></td></tr></table></figure><p>③ 设置系统变量</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 语法1：直接指定作用域（SESSION/全局）</span></span><br><span class="line"><span class="keyword">SET</span> [SESSION <span class="operator">|</span> <span class="keyword">GLOBAL</span>] 系统变量名 <span class="operator">=</span> 值;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 语法2：用@@指定作用域（和语法1效果一致）</span></span><br><span class="line"><span class="keyword">SET</span> @@[SESSION <span class="operator">|</span> <span class="keyword">GLOBAL</span>]系统变量名 <span class="operator">=</span> 值;</span><br></pre></td></tr></table></figure><p>④ 案例：</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><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="comment">-- 变量：系统变量</span></span><br><span class="line"><span class="comment">-- 查看系统变量</span></span><br><span class="line"><span class="keyword">show</span> session variables;</span><br><span class="line"><span class="keyword">show</span> session variables <span class="keyword">like</span> <span class="string">&#x27;auto%&#x27;</span>;</span><br><span class="line"><span class="keyword">show</span> glabal variables <span class="keyword">like</span> <span class="string">&#x27;auto%&#x27;</span>;</span><br><span class="line"><span class="keyword">select</span> @<span class="variable">@global</span>.autocommit;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 设置系统变量</span></span><br><span class="line"><span class="keyword">set</span> session autocommit <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">insert into</span> course(id, name) <span class="keyword">values</span> (<span class="number">5</span>, <span class="string">&#x27;Oracle&#x27;</span>);</span><br><span class="line"><span class="keyword">set</span> <span class="keyword">global</span> auto <span class="keyword">commit</span> <span class="operator">=</span> <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><ul><li>如果没有指定 session &#x2F; global，默认 session，会话变量</li><li>mysql 服务器重启之后，所设置的全局参数会失效，要想不失效，需要更改&#x2F;etc&#x2F;my.cnf 中的配置。</li></ul><h5 id="用户定义变量"><a href="#用户定义变量" class="headerlink" title="用户定义变量"></a>用户定义变量</h5><p>① 用户定义变量 是用户根据需要自己定义的变量，用户变量不用提前声明，在用的时候直接用“@变量名”使用就可以。其作用域为当前连接。</p><p>② 赋值：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">SET</span> <span class="variable">@var</span> name <span class="operator">=</span> expr [, <span class="variable">@var_name</span> <span class="operator">=</span> expr]...;</span><br><span class="line"><span class="keyword">SET</span> <span class="variable">@var</span> name :<span class="operator">=</span> expr [, <span class="variable">@var_name</span> :<span class="operator">=</span> expr]...;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="variable">@var</span> name :<span class="operator">=</span> expr , <span class="variable">@var</span> name :<span class="operator">=</span> expr ...;</span><br><span class="line"><span class="keyword">SELECT</span> 字段名 <span class="keyword">INTO</span> <span class="variable">@var_name</span> <span class="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><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">SELECT</span> <span class="variable">@var_name</span>;</span><br></pre></td></tr></table></figure><p>④ 案例：</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><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="comment">-- 变量：用户变量</span></span><br><span class="line"><span class="comment">-- 赋值</span></span><br><span class="line"><span class="keyword">set</span> <span class="variable">@myname</span> <span class="operator">=</span> <span class="string">&#x27;itcast&#x27;</span>;</span><br><span class="line"><span class="keyword">set</span> <span class="variable">@myage</span> :<span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="variable">@mycolor</span> :<span class="operator">=</span> <span class="string">&#x27;red&#x27;</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">into</span> <span class="variable">@mycount</span> <span class="keyword">from</span> tb_user;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 使用</span></span><br><span class="line"><span class="keyword">select</span> <span class="variable">@myname</span>, <span class="variable">@myage</span>, <span class="variable">@mycolor</span>, <span class="variable">@mycount</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="variable">@abc</span>; <span class="comment">-- 输出为NULL</span></span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><p>用户定义的变量无需对其进行声明或者初始化，只不过获取到的值为 NULL。</p><h5 id="局部变量"><a href="#局部变量" class="headerlink" title="局部变量"></a>局部变量</h5><p>① 局部变量 是根据需要定义的在局部生效的变量，访问之前，需要DECLARE声明。可用作存储过程内的局部变量和输入参数，局部变量的范围是在其内声明的BEGIN .. END块。</p><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">DECLARE</span> 变量名 变量类型 [DEFAULT..];</span><br></pre></td></tr></table></figure><p>变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。</p><p>③ 赋值：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">SET</span> 变量名<span class="operator">=</span>值;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SET</span> 变量名:<span class="operator">=</span>值;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> 字段名 <span class="keyword">INTO</span> 变量名 <span class="keyword">FROM</span> 表名 ...;</span><br></pre></td></tr></table></figure><p>④ 案例：</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><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="comment">-- 变量：局部变量</span></span><br><span class="line"><span class="comment">-- 声明 - declare</span></span><br><span class="line"><span class="comment">-- 赋值 -</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p2()</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">  <span class="keyword">declare</span> stu_count <span class="type">int</span> <span class="keyword">default</span> <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">select</span> <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">into</span> stu_count <span class="keyword">from</span> student;</span><br><span class="line">  <span class="keyword">select</span> stu_count;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">call</span> p2();</span><br></pre></td></tr></table></figure><h4 id="if-判断"><a href="#if-判断" class="headerlink" title="if 判断"></a>if 判断</h4><h5 id="语法："><a href="#语法：" class="headerlink" title="语法："></a>语法：</h5><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">IF 条件<span class="number">1</span> <span class="keyword">THEN</span></span><br><span class="line">        ...</span><br><span class="line">ELSEIF 条件<span class="number">2</span> <span class="keyword">THEN</span> <span class="comment">-- 可选</span></span><br><span class="line">        ...</span><br><span class="line"><span class="keyword">ELSE</span>              <span class="comment">-- 可选</span></span><br><span class="line">        ...</span><br><span class="line"><span class="keyword">END</span> IF;</span><br></pre></td></tr></table></figure><h5 id="案例：-3"><a href="#案例：-3" class="headerlink" title="案例："></a>案例：</h5><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p3()</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">  <span class="keyword">declare</span> score <span class="type">int</span> <span class="keyword">default</span> <span class="number">58</span>;</span><br><span class="line">  <span class="keyword">declare</span> <span class="keyword">result</span> <span class="type">varchar</span>(<span class="number">10</span>);</span><br><span class="line">  if score <span class="operator">&gt;=</span> <span class="number">85</span> <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span><span class="string">&#x27;优秀&#x27;</span>;</span><br><span class="line">  elseif score <span class="operator">&gt;=</span> <span class="number">60</span> <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span><span class="string">&#x27;及格&#x27;</span>;</span><br><span class="line">  <span class="keyword">else</span></span><br><span class="line">    <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span><span class="string">&#x27;不及格&#x27;</span>;</span><br><span class="line">  <span class="keyword">end</span> if;</span><br><span class="line">  <span class="keyword">select</span> <span class="keyword">result</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br></pre></td></tr></table></figure><h4 id="参数（in-out-inout"><a href="#参数（in-out-inout" class="headerlink" title="参数（in, out, inout)"></a>参数（in, out, inout)</h4><table><thead><tr><th>类型</th><th>含义</th><th>备注</th></tr></thead><tbody><tr><td>IN</td><td>该类参数作为输入，也就是需要调用时传入值</td><td>默认</td></tr><tr><td>OUT</td><td>该类参数作为输出，也就是该参数可以作为返回值</td><td></td></tr><tr><td>INOUT</td><td>既可以作为输入参数，也可以作为输出参数****</td><td></td></tr></tbody></table><h5 id="用法："><a href="#用法：" class="headerlink" title="用法："></a><strong>用法：</strong></h5><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">CREATE</span> <span class="keyword">PROCEDURE</span> 存储过程名称([<span class="keyword">IN</span><span class="operator">/</span><span class="keyword">OUT</span><span class="operator">/</span><span class="keyword">INOUT</span> 参数名 参数类型 ])</span><br><span class="line"><span class="keyword">BEGIN</span></span><br><span class="line">    <span class="comment">-- SQL语句</span></span><br><span class="line"><span class="keyword">END</span> :</span><br></pre></td></tr></table></figure><h5 id="案例：-4"><a href="#案例：-4" class="headerlink" title="案例："></a><strong>案例：</strong></h5><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 根据传入(in)参数score，判定当前分数对应的分数等级，并返回(out)</span></span><br><span class="line"><span class="comment">-- score &gt;= 85分，等级为优秀。</span></span><br><span class="line"><span class="comment">-- score &gt;= 60分 且 score &lt; 85分，等级为及格</span></span><br><span class="line"><span class="comment">-- score &lt; 60分，等级为不及格。</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p4(<span class="keyword">in</span> score <span class="type">int</span>, <span class="keyword">out</span> <span class="keyword">result</span> <span class="type">varchar</span>(<span class="number">10</span>))</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">    if score <span class="operator">&gt;=</span> <span class="number">85</span> <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span> <span class="string">&#x27;优秀&#x27;</span>;</span><br><span class="line">    elseif score <span class="operator">&gt;=</span> <span class="number">60</span> <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span> <span class="string">&#x27;及格&#x27;</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span> <span class="string">&#x27;不及格&#x27;</span>;</span><br><span class="line">    <span class="keyword">end</span> if;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">call</span> p4(<span class="number">68</span>,<span class="variable">@result</span>);</span><br><span class="line"><span class="keyword">select</span> <span class="variable">@result</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 将传入的200分制的分数，进行换算，换算成百分制，然后返回分数 --&gt; inout</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p5(<span class="keyword">inout</span> score <span class="keyword">double</span>)</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">  <span class="keyword">set</span> score :<span class="operator">=</span> score <span class="operator">*</span> <span class="number">0.5</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span> <span class="variable">@score</span> <span class="operator">=</span> <span class="number">198</span>;</span><br><span class="line"><span class="keyword">call</span> p5(score);</span><br><span class="line"><span class="keyword">select</span> <span class="variable">@score</span>;</span><br></pre></td></tr></table></figure><h4 id="case"><a href="#case" class="headerlink" title="case"></a>case</h4><h5 id="语法一："><a href="#语法一：" class="headerlink" title="语法一："></a><strong>语法一：</strong></h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">CASE</span> <span class="keyword">case</span> <span class="keyword">value</span></span><br><span class="line">  <span class="keyword">WHEN</span> when_value1 <span class="keyword">THEN</span> statement_list1</span><br><span class="line">  [<span class="keyword">WHEN</span> when_value2 <span class="keyword">THEN</span> statement_list2]...</span><br><span class="line">  [<span class="keyword">ELSE</span> statement_list ]</span><br><span class="line"><span class="keyword">END</span> <span class="keyword">CASE</span>;</span><br></pre></td></tr></table></figure><h5 id="语法二："><a href="#语法二：" class="headerlink" title="语法二："></a><strong>语法二：</strong></h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">CASE</span></span><br><span class="line">  <span class="keyword">WHEN</span> search_conditionl <span class="keyword">THEN</span> statement_list1</span><br><span class="line">  <span class="keyword">WHEN</span> search_condition2 <span class="keyword">THEN</span> statement_list2]...</span><br><span class="line">  [<span class="keyword">ELSE</span> statement_list]</span><br><span class="line"><span class="keyword">END</span> <span class="keyword">CASE</span>;</span><br></pre></td></tr></table></figure><h5 id="案例：-5"><a href="#案例：-5" class="headerlink" title="案例："></a><strong>案例：</strong></h5><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- case</span></span><br><span class="line"><span class="comment">-- 根据传入的月份，判定月份所属的季节(要求采用case结构)</span></span><br><span class="line"><span class="comment">-- 1-3月份，为第一季度</span></span><br><span class="line"><span class="comment">-- 4-6月份，为第二季度</span></span><br><span class="line"><span class="comment">-- 7-9月份，为第三季度</span></span><br><span class="line"><span class="comment">-- 10-12月份，为第四季度</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p6(<span class="keyword">in</span> <span class="keyword">month</span> <span class="type">int</span>)</span><br><span class="line"><span class="keyword">begin</span> </span><br><span class="line">  <span class="keyword">declare</span> <span class="keyword">result</span> <span class="type">varchar</span>(<span class="number">10</span>);</span><br><span class="line">  <span class="keyword">case</span> </span><br><span class="line">    <span class="keyword">when</span> <span class="keyword">month</span> <span class="operator">&gt;=</span> <span class="number">1</span> <span class="keyword">and</span> <span class="keyword">month</span> <span class="operator">&lt;=</span> <span class="number">3</span> <span class="keyword">then</span></span><br><span class="line">      <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span> <span class="string">&#x27;第一季度&#x27;</span>;</span><br><span class="line">    <span class="keyword">when</span> <span class="keyword">month</span> <span class="operator">&gt;=</span> <span class="number">4</span> <span class="keyword">and</span> <span class="keyword">month</span> <span class="operator">&lt;=</span> <span class="number">6</span> <span class="keyword">then</span></span><br><span class="line">      <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span> <span class="string">&#x27;第二季度&#x27;</span>;</span><br><span class="line">    <span class="keyword">when</span> <span class="keyword">month</span> <span class="operator">&gt;=</span> <span class="number">7</span> <span class="keyword">and</span> <span class="keyword">month</span> <span class="operator">&lt;=</span> <span class="number">9</span> <span class="keyword">then</span></span><br><span class="line">      <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span> <span class="string">&#x27; 第三季度&#x27;</span>;</span><br><span class="line">    <span class="keyword">when</span> <span class="keyword">month</span> <span class="operator">&gt;=</span> <span class="number">10</span> <span class="keyword">and</span> <span class="keyword">month</span> <span class="operator">&lt;=</span> <span class="number">12</span> <span class="keyword">then</span></span><br><span class="line">      <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span> <span class="string">&#x27;第四季度&#x27;</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">      <span class="keyword">set</span> <span class="keyword">result</span> :<span class="operator">=</span> <span class="string">&#x27;非法参数&#x27;</span>;</span><br><span class="line">  <span class="keyword">end</span> <span class="keyword">case</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- concat() 用于字符串的拼接</span></span><br><span class="line">  <span class="keyword">select</span> concat(<span class="string">&#x27;你输入的月份为：&#x27;</span>, <span class="keyword">month</span>, <span class="string">&#x27;，所属季度为：&#x27;</span>, <span class="keyword">result</span>);</span><br><span class="line"><span class="keyword">end</span>;</span><br></pre></td></tr></table></figure><h4 id="循环"><a href="#循环" class="headerlink" title="循环"></a>循环</h4><h5 id="while"><a href="#while" class="headerlink" title="while"></a>while</h5><p>① while 循环是有条件的循环控制语句。满足条件后，再执行循环体中的SQL语句。</p><p>② 语法：</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="comment">-- 先判定条件，如果条件为true，则执行逻辑，否则，不执行逻辑</span></span><br><span class="line">WHILE 条件 DO</span><br><span class="line">  <span class="keyword">SQL</span>逻辑...</span><br><span class="line"><span class="keyword">END</span> WHILE;</span><br></pre></td></tr></table></figure><p>③ 案例：</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><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">-- while计算从1累加到 n 的值，n 为传入的参数值。</span></span><br><span class="line"><span class="comment">-- A.定义局部变量，记录累加之后的值;</span></span><br><span class="line"><span class="comment">-- B.每循环一次，就会对 n 进行减1，如果 n 减到0，则退出循环</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p7(<span class="keyword">in</span> n <span class="type">int</span>)</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">  <span class="keyword">declare</span> total <span class="type">int</span> <span class="keyword">default</span> <span class="number">0</span>;</span><br><span class="line">  </span><br><span class="line">  while n<span class="operator">&gt;</span><span class="number">0</span> do</span><br><span class="line">    <span class="keyword">set</span> total :<span class="operator">=</span> total <span class="operator">+</span> n</span><br><span class="line">    <span class="keyword">set</span> n:<span class="operator">=</span>n<span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">end</span> while;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">select</span> total;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"><span class="keyword">call</span> p7(<span class="number">100</span>);</span><br></pre></td></tr></table></figure><h5 id="repeat"><a href="#repeat" class="headerlink" title="repeat"></a>repeat</h5><p>① repeat是有条件的循环控制语句,当满足条件的时候退出循环。</p><p>② 语法：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 先执行一次逻辑，然后判定逻辑是否满足，如果满足，则退出。如果不满足，则继续下一次循环</span></span><br><span class="line">REPEAT</span><br><span class="line">  <span class="keyword">SQL</span>逻辑.</span><br><span class="line">  UNTIL 条件</span><br><span class="line"><span class="keyword">END</span> REPEAT;</span><br></pre></td></tr></table></figure><p>③ 案例：</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><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="comment">-- while计算从1累加到 n 的值，n 为传入的参数值。</span></span><br><span class="line"><span class="comment">-- A.定义局部变量，记录累加之后的值;</span></span><br><span class="line"><span class="comment">-- B.每循环一次，就会对 n 进行减1，如果 n 减到0，则退出循环</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p8(innint)</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">  <span class="keyword">declare</span> total <span class="type">int</span> <span class="keyword">default</span> <span class="number">0</span>;</span><br><span class="line">  </span><br><span class="line">  repeat</span><br><span class="line">    <span class="keyword">set</span> total :<span class="operator">=</span> total <span class="operator">+</span> n;</span><br><span class="line">    <span class="keyword">set</span> n :<span class="operator">=</span> n <span class="operator">-</span> <span class="number">1</span>;</span><br><span class="line">  until n <span class="operator">&lt;=</span> <span class="number">0</span></span><br><span class="line">  <span class="keyword">end</span> repeat;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">select</span> total;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">call</span> p8(<span class="number">10</span>);</span><br><span class="line"><span class="keyword">call</span> p8(<span class="number">100</span>);</span><br></pre></td></tr></table></figure><p>④ 与 while 区别：</p><ul><li>先进行循环一次再判断。相当于 c 语言中的 do while();</li><li>满足条件则退出</li></ul><h5 id="loop"><a href="#loop" class="headerlink" title="loop"></a>loop</h5><p>① LOOP 实现简单的循环，如果不在SQL逻辑中增加退出循环的条件，可以用其来实现简单的死循环。LOOP可以配合一下两个语句使用。</p><ul><li>LEAVE:配合循环使用，退出循环。</li><li>ITERATE:必须用在循环中，作用是跳过当前循环剩下的语句，直接进入下一次循环。</li></ul><p>② 语法：</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><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[<span class="keyword">begin</span> label:] LOOP</span><br><span class="line">  <span class="keyword">SQL</span>逻辑..</span><br><span class="line"><span class="keyword">END</span> LOOP [<span class="keyword">end</span> label];</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 退出指定标记的循环体</span></span><br><span class="line">LEAVE label; </span><br><span class="line"><span class="comment">-- 直接进入下一次循环</span></span><br><span class="line">ITERATE label;</span><br></pre></td></tr></table></figure><p>③ 案例：</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- loop 计算从1到n之间的偶数累加的值，n为传入的参数值。</span></span><br><span class="line"><span class="comment">-- A.定义局部变量，记录累加之后的值;</span></span><br><span class="line"><span class="comment">-- B.每循环一次，就会劝进行-1，如果n减到0，则退出循环。------&gt; leave xx</span></span><br><span class="line"><span class="comment">-- C.如果当次累加的数据是奇数，则直接进入下一次循坏。-------&gt; iterate xx</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p10(<span class="keyword">in</span> n <span class="type">int</span>)</span><br><span class="line"><span class="keyword">begin</span> </span><br><span class="line">  <span class="keyword">declare</span> total <span class="type">int</span> defatult <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  sum: loop</span><br><span class="line">    if n <span class="operator">&lt;=</span> <span class="number">10</span> <span class="keyword">then</span></span><br><span class="line">      leave sum;</span><br><span class="line">    <span class="keyword">end</span> if;</span><br><span class="line"></span><br><span class="line">    if n<span class="operator">%</span><span class="number">2</span> <span class="operator">=</span> <span class="number">1</span> <span class="keyword">then</span></span><br><span class="line">      <span class="keyword">set</span> n :<span class="operator">=</span> n <span class="operator">-</span> <span class="number">1</span>;</span><br><span class="line">      iterate sum;</span><br><span class="line">    <span class="keyword">end</span> if;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">set</span> total :<span class="operator">=</span> total <span class="operator">+</span> n;</span><br><span class="line">    <span class="keyword">set</span> n :<span class="operator">=</span> n <span class="operator">-</span> <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">end</span> loop sum;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">select</span> total;</span><br><span class="line"><span class="keyword">end</span>;</span><br></pre></td></tr></table></figure><h4 id="游标-cursor"><a href="#游标-cursor" class="headerlink" title="游标-cursor"></a>游标-cursor</h4><h5 id="介绍-2"><a href="#介绍-2" class="headerlink" title="介绍"></a>介绍</h5><p>游标(CURSOR)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH和 CLOSE，其语法分别如下。</p><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">DECLARE</span> 游标名称 <span class="keyword">CURSOR</span> <span class="keyword">FOR</span> 查询语句;</span><br></pre></td></tr></table></figure><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">OPEN</span> 游标名称;</span><br></pre></td></tr></table></figure><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">FETCH</span> 游标名称 <span class="keyword">INTO</span> 变量[,变量];</span><br></pre></td></tr></table></figure><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">CLOSE</span> 游标名称;</span><br></pre></td></tr></table></figure><h5 id="案例：-6"><a href="#案例：-6" class="headerlink" title="案例："></a>案例：</h5><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 游标</span></span><br><span class="line"><span class="comment">-- 根据传入的参数uage，来查询用户表tb_user 中， 所有的用户年龄小于uage的用户姓名（name)和专业（profession），</span></span><br><span class="line"><span class="comment">-- 并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中。</span></span><br><span class="line"><span class="comment">-- 逻辑:</span></span><br><span class="line"><span class="comment">-- A.声明游标，存储查询结果集</span></span><br><span class="line"><span class="comment">-- B.准备:创建表结构</span></span><br><span class="line"><span class="comment">-- C.开启游标</span></span><br><span class="line"><span class="comment">-- D.获取游标中的记录</span></span><br><span class="line"><span class="comment">-- E.插入数据到新表中</span></span><br><span class="line"><span class="comment">-- F.关闭游标</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p11(<span class="keyword">in</span> uage <span class="type">int</span>)</span><br><span class="line"><span class="keyword">begin</span> </span><br><span class="line">  <span class="keyword">declare</span> uname <span class="type">varchar</span>(<span class="number">100</span>);</span><br><span class="line">  <span class="keyword">declare</span> upro <span class="type">varchar</span>(<span class="number">100</span>);</span><br><span class="line">  <span class="keyword">declare</span> u_cursor <span class="keyword">cursor</span> <span class="keyword">for</span> <span class="keyword">select</span> name, profession <span class="keyword">from</span> tb_user <span class="keyword">where</span> age <span class="operator">&lt;=</span> uage;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">drop</span> <span class="keyword">table</span> if <span class="keyword">exists</span> tb_user_pro;</span><br><span class="line">  <span class="keyword">create table</span> if <span class="keyword">not</span> <span class="keyword">exists</span> tb_user_pro(</span><br><span class="line">    id <span class="type">int</span> <span class="keyword">primary key</span> auto_increment,</span><br><span class="line">    name <span class="type">varchar</span>(<span class="number">100</span>),</span><br><span class="line">    profession <span class="type">varchar</span>(<span class="number">100</span>)</span><br><span class="line">  );</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">open</span> u_cursor;</span><br><span class="line">  while <span class="literal">true</span> do</span><br><span class="line">    <span class="keyword">fetch</span> u_cursor <span class="keyword">into</span> uname,upro;</span><br><span class="line">    <span class="keyword">insert into</span> tb_user_pro <span class="keyword">values</span>(<span class="keyword">null</span>, uname, upro);</span><br><span class="line">  <span class="keyword">end</span> while;</span><br><span class="line">  <span class="keyword">close</span> u_cursor;</span><br><span class="line"><span class="keyword">end</span>;</span><br></pre></td></tr></table></figure><h4 id="条件处理程序-handler"><a href="#条件处理程序-handler" class="headerlink" title="条件处理程序-handler"></a>条件处理程序-handler</h4><p>① 条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。</p><p>② 语法：</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><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DECLARE</span> handler action HANDLERFOR <span class="keyword">condition</span> <span class="keyword">value</span> l, <span class="keyword">condition</span> value.... statement;</span><br><span class="line">handler action</span><br><span class="line">  CONTINUE: 继续执行当前程序</span><br><span class="line">  EXIT: 终止执行当前程序</span><br><span class="line"><span class="keyword">condition</span> <span class="keyword">value</span></span><br><span class="line">  SOLSTATE sqlstate_value:状态码，如 <span class="number">02000</span></span><br><span class="line">  <span class="keyword">SQLWARNING</span>:所有以<span class="number">01</span>开头的<span class="keyword">SQLSTATE</span>代码的简写</span><br><span class="line">  <span class="keyword">NOT</span> FOUND:所有以<span class="number">02</span>开头的SOLSTATE代码的简写</span><br><span class="line">  SOLEXCEPTION:所有没有被SOLWARNING 或 <span class="keyword">NOT</span> FOUND捕获的SOLSTATE代码的简写</span><br></pre></td></tr></table></figure><p>③ 案例：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> p11(<span class="keyword">in</span> uage <span class="type">int</span>)</span><br><span class="line"><span class="keyword">begin</span> </span><br><span class="line">  <span class="keyword">declare</span> uname <span class="type">varchar</span>(<span class="number">100</span>);</span><br><span class="line">  <span class="keyword">declare</span> upro <span class="type">varchar</span>(<span class="number">100</span>);</span><br><span class="line">  <span class="keyword">declare</span> u_cursor <span class="keyword">cursor</span> <span class="keyword">for</span> <span class="keyword">select</span> name, profession <span class="keyword">from</span> tb_user <span class="keyword">where</span> age <span class="operator">&lt;=</span> uage;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">-- 监控到02000的状态码后，关闭游标后执行exit退出操作。</span></span><br><span class="line">  <span class="keyword">declare</span> exit handler <span class="keyword">for</span> <span class="keyword">not</span> found <span class="keyword">close</span> u_cursor; </span><br><span class="line"></span><br><span class="line">  <span class="keyword">drop</span> <span class="keyword">table</span> if <span class="keyword">exists</span> tb_user_pro;</span><br><span class="line">  <span class="keyword">create table</span> if <span class="keyword">not</span> <span class="keyword">exists</span> tb_user_pro(</span><br><span class="line">    id <span class="type">int</span> <span class="keyword">primary key</span> auto_increment,</span><br><span class="line">    name <span class="type">varchar</span>(<span class="number">100</span>),</span><br><span class="line">    profession <span class="type">varchar</span>(<span class="number">100</span>)</span><br><span class="line">  );</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">open</span> u_cursor;</span><br><span class="line">  while <span class="literal">true</span> do</span><br><span class="line">    <span class="keyword">fetch</span> u_cursor <span class="keyword">into</span> uname,upro;</span><br><span class="line">    <span class="keyword">insert into</span> tb_user_pro <span class="keyword">values</span>(<span class="keyword">null</span>, uname, upro);</span><br><span class="line">  <span class="keyword">end</span> while;</span><br><span class="line">  <span class="keyword">close</span> u_cursor;</span><br><span class="line"><span class="keyword">end</span>;</span><br></pre></td></tr></table></figure><h3 id="存储函数："><a href="#存储函数：" class="headerlink" title="存储函数："></a>存储函数：</h3><h4 id="1-介绍："><a href="#1-介绍：" class="headerlink" title="1. 介绍："></a>1. 介绍：</h4><p>存储函数是有返回值的存储过程，存储函数的参数只能是IN类型的。存储函数用的较少，能够使用存储函数的地方都可以用存储过程替换。</p><h4 id="2-语法："><a href="#2-语法：" class="headerlink" title="2. 语法："></a>2. 语法：</h4><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><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="keyword">CREATE</span> <span class="keyword">FUNCTION</span> 存储函数名称([ 参数列表 ])</span><br><span class="line"><span class="keyword">RETURNS</span> type [characteristic ...]</span><br><span class="line"><span class="keyword">BEGIN</span></span><br><span class="line">  <span class="comment">-- SQL语句</span></span><br><span class="line">  <span class="keyword">RETURN</span> ...;</span><br><span class="line"><span class="keyword">END</span> ;</span><br><span class="line">characteristic说明:</span><br><span class="line">· <span class="keyword">DETERMINISTIC</span>:相同的输入参数总是产生相同的结果</span><br><span class="line">· <span class="keyword">NO</span> <span class="keyword">SQL</span>:不包含 <span class="keyword">SQL</span>语句。</span><br><span class="line">· <span class="keyword">READS</span> SOL DATA:包含读取数据的语句，但不包含写入数据的语句,</span><br></pre></td></tr></table></figure><h4 id="3-案例："><a href="#3-案例：" class="headerlink" title="3. 案例："></a>3. <strong>案例：</strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">function</span> fun1(n <span class="type">int</span>)</span><br><span class="line"><span class="keyword">returns</span> <span class="type">int</span> <span class="keyword">deterministic</span></span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">    <span class="keyword">declare</span> total <span class="type">int</span> <span class="keyword">default</span> <span class="number">0</span>;</span><br><span class="line">     while n<span class="operator">&gt;</span><span class="number">0</span> do</span><br><span class="line">        <span class="keyword">set</span> total :<span class="operator">=</span> total <span class="operator">+</span> n;</span><br><span class="line">        <span class="keyword">set</span> n:<span class="operator">=</span> n <span class="operator">-</span> <span class="number">1</span>;</span><br><span class="line">     <span class="keyword">end</span> while;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> total;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> fun1(<span class="number">100</span>);</span><br></pre></td></tr></table></figure><h3 id="触发器"><a href="#触发器" class="headerlink" title="触发器"></a>触发器</h3><h4 id="1-介绍"><a href="#1-介绍" class="headerlink" title="1. 介绍"></a>1. 介绍</h4><p>(1) 触发器是与表有关的数据库对象，指在 insert&#x2F;update&#x2F;delete 之前或之后，触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性，日志记录，数据校验等操作。</p><p>(2) 使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容，这与其他的数据库是相似的。现在触发器还只支持行级触发，不支持语句级触发。</p><table><thead><tr><th>触发器类型</th><th>NEW 和 OLD</th></tr></thead><tbody><tr><td>insert 型触发器</td><td>NEW 表示将要或者已经新增的数据</td></tr><tr><td>update 型触发器</td><td>OLD 表示修改之前的数据，NEW 表示将要或已经修改后的数据</td></tr><tr><td>delete 型触发器</td><td>OLD 表示将要或者已经删除的数据</td></tr></tbody></table><h4 id="2-语法：-1"><a href="#2-语法：-1" class="headerlink" title="2. 语法："></a>2. <strong>语法：</strong></h4><p><strong>创建：</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TRIGGER</span> <span class="keyword">trigger</span> name</span><br><span class="line">BEFORE<span class="operator">/</span>AFTER <span class="keyword">INSERT</span><span class="operator">/</span><span class="keyword">UPDATE</span><span class="operator">/</span><span class="keyword">DELETE</span></span><br><span class="line"><span class="keyword">ON</span> tbl name <span class="keyword">FOR</span> <span class="keyword">EACH</span> <span class="type">ROW</span> <span class="comment">--行级触发器BEGIN</span></span><br><span class="line">  trigger_stmt;</span><br><span class="line"><span class="keyword">END</span>;</span><br></pre></td></tr></table></figure><p><strong>查看：</strong></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">SHOW</span> TRIGGERS;</span><br></pre></td></tr></table></figure><p><strong>删除：</strong></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">DROP</span> <span class="keyword">TRIGGER</span> [schema_name.]trigger_name; <span class="comment">--如果没有指定 schema name，默认为当前数据库</span></span><br></pre></td></tr></table></figure><p><strong>案例：</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 插入数据触发器</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">trigger</span> tb_user_insert_trigger</span><br><span class="line">  after <span class="keyword">insert</span> <span class="keyword">on</span> tb_user <span class="keyword">for</span> <span class="keyword">each</span> <span class="type">row</span></span><br><span class="line">  <span class="keyword">begin</span> </span><br><span class="line">  <span class="keyword">insert into</span> user_logs(id, operation, operate_time, operate_id, operate_params)<span class="keyword">values</span></span><br><span class="line">  (<span class="keyword">null</span>, <span class="string">&#x27;insert&#x27;</span>, now(), new.id, concat(<span class="string">&#x27;插入的数据内容为：id=&#x27;</span>, new.id, <span class="string">&#x27;,name=&#x27;</span>, new.name, <span class="string">&#x27;, phone=&#x27;</span>, new.phone, <span class="string">&#x27;, email=&#x27;</span>, new.email, <span class="string">&#x27;, profession=&#x27;</span>, new.profession));</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看</span></span><br><span class="line"><span class="keyword">show</span> triggers;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除</span></span><br><span class="line"><span class="keyword">drop</span> <span class="keyword">trigger</span> tb_user_insert_trigger;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入数据tb_user</span></span><br><span class="line"><span class="keyword">insert into</span> tb_user(id, name, phtone, email, profession, age, gender, status, createtime) <span class="keyword">values</span>(<span class="number">25</span>, <span class="string">&#x27;二皇子&#x27;</span>, <span class="string">&#x27;1880901212&#x27;</span>, <span class="string">&#x27;erhuangzi@163.com&#x27;</span>, <span class="string">&#x27;软件工程&#x27;</span>, <span class="number">23</span>, <span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;1&#x27;</span><span class="number">1</span>, now());</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 修改数据触发器</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">trigger</span> tb_user_update_trigger</span><br><span class="line">  after <span class="keyword">update</span> <span class="keyword">on</span> tb_user <span class="keyword">for</span> <span class="keyword">each</span> <span class="type">row</span></span><br><span class="line">  <span class="keyword">begin</span> </span><br><span class="line">  <span class="keyword">insert into</span> user_logs(id, operation, operate_time, operate_id, operate_params)<span class="keyword">values</span></span><br><span class="line">  (<span class="keyword">null</span>, <span class="string">&#x27;update&#x27;</span>, now(), new.id, </span><br><span class="line">   concat(<span class="string">&#x27;更新之前的数据：id=&#x27;</span>, old.id, <span class="string">&#x27;,name=&#x27;</span>, old.name, <span class="string">&#x27;, phone=&#x27;</span>, old.phone, <span class="string">&#x27;, email=&#x27;</span>, old.email, <span class="string">&#x27;, profession=&#x27;</span>, old.profession,</span><br><span class="line">    <span class="string">&#x27;更新之后的数据：id=&#x27;</span>, new.id, <span class="string">&#x27;,name=&#x27;</span>, new.name, <span class="string">&#x27;, phone=&#x27;</span>, new.phone, <span class="string">&#x27;, email=&#x27;</span>, new.email, <span class="string">&#x27;, profession=&#x27;</span>, new.profession));</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">update</span> tb_user <span class="keyword">set</span> age <span class="operator">=</span> <span class="number">32</span> <span class="keyword">where</span> id <span class="operator">=</span> <span class="number">23</span>;</span><br><span class="line"><span class="keyword">update</span> tb_user <span class="keyword">set</span> age <span class="operator">=</span> <span class="number">32</span> <span class="keyword">where</span> id <span class="operator">&lt;=</span> <span class="number">5</span>; <span class="comment">-- 触发器为行级触发器，所以更改几行数据则触发几次，该语句触发5次</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除数据触发器</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">trigger</span> tb_user_delete_trigger</span><br><span class="line">  after <span class="keyword">delete</span> <span class="keyword">on</span> tb_user <span class="keyword">for</span> <span class="keyword">each</span> <span class="type">row</span></span><br><span class="line">  <span class="keyword">begin</span> </span><br><span class="line">  <span class="keyword">insert into</span> user_logs(id, operation, operate_time, operate_id, operate_params)<span class="keyword">values</span></span><br><span class="line">  (<span class="keyword">null</span>, <span class="string">&#x27;insert&#x27;</span>, now(), old.id, </span><br><span class="line">   concat(<span class="string">&#x27;删除之前的数据：id=&#x27;</span>, new.id, <span class="string">&#x27;,name=&#x27;</span>, old.name, <span class="string">&#x27;, phone=&#x27;</span>, old.phone, <span class="string">&#x27;, email=&#x27;</span>, old.email, <span class="string">&#x27;, profession=&#x27;</span>, old.profession));</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">delete</span> <span class="keyword">from</span> tb_user <span class="keyword">where</span> id <span class="operator">=</span> <span class="number">26</span>;</span><br></pre></td></tr></table></figure><h3 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h3><h4 id="介绍-3"><a href="#介绍-3" class="headerlink" title="介绍"></a>介绍</h4><p>锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中，除传统的计算资源(CPU、RAM、I&#x2F;0)的争用以外，数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题，锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说，锁对数据库而言显得尤其重要，也更加复杂。</p><h4 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h4><p>MySQL中的锁，按照锁的粒度分，分为一下三类：</p><ul><li>全局锁：锁定数据库中的所有表。</li><li>表级锁：每次操作锁住整张表。</li><li>行级锁：每次操作锁住对应的行数据。</li></ul><h3 id="全局锁"><a href="#全局锁" class="headerlink" title="全局锁"></a>全局锁</h3><h4 id="介绍-4"><a href="#介绍-4" class="headerlink" title="介绍"></a>介绍</h4><p>① 全局锁就是对整个数据库实例加锁，加锁后整个实例就处于只读状态，后续的DML的写语句，DDL语句，已经更新操作的事务提交语句都将被阻塞。</p><p>② 其典型的使用场景是做全库的逻辑备份，对所有的表进行锁定，从而获取一致性视图，保证数据的完整性。</p><h4 id="演示图"><a href="#演示图" class="headerlink" title="演示图"></a>演示图</h4><img src="https://i-blog.csdnimg.cn/direct/263d8ef5e1364dbb84d1068f3b89b0f7.png" alt="img" style="zoom: 67%;" /><img src="https://i-blog.csdnimg.cn/direct/e8ba5de5b7ce4b26bdfcc9a4423b8ccc.png" alt="img" style="zoom:67%;" /><h4 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h4><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">flush tables <span class="keyword">with</span> read lock</span><br></pre></td></tr></table></figure><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">unlock tables</span><br></pre></td></tr></table></figure><h4 id="特点：-1"><a href="#特点：-1" class="headerlink" title="特点："></a>特点：</h4><p>数据库中加全局锁，是一个比较重的操作，存在以下问题:</p><ul><li>如果在主库上备份，那么在备份期间都不能执行更新，业务基本上就得停摆</li><li>如果在从库上备份，那么在备份期间从库不能执行主库同步过来的二进制日志(binlog)，会导致主从延迟。（该结构会在后续主从复制讲解）</li></ul><h4 id="解决方法："><a href="#解决方法：" class="headerlink" title="解决方法："></a>解决方法：</h4><p>在InnoDB引擎中，我们可以在备份时加上参数 –single-transaction 参数来完成不加锁的一致性数据备份。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysqldump --single-transaction -uroot -p123456 itcast &gt; itcast.sql</span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><p>这个方法<strong>只对 InnoDB 引擎有用</strong>（因为只有 InnoDB 支持事务）；如果是 MyISAM 引擎，备份还是会锁表（MyISAM 不支持事务，没法搞 “快照”）。</p><h3 id="表级锁"><a href="#表级锁" class="headerlink" title="表级锁"></a>表级锁</h3><h4 id="介绍-5"><a href="#介绍-5" class="headerlink" title="介绍"></a>介绍</h4><p>每次操作锁住整张表。锁定粒度大，发生锁的冲突的概率最高，并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。对于表级锁，主要分为一下三类：</p><ul><li>表锁</li><li>元数据锁（meta data lock，MDL）</li><li>意向锁</li></ul><h4 id="表锁"><a href="#表锁" class="headerlink" title="表锁"></a>表锁</h4><p>① 对于表锁，分为两类：</p><ul><li>表共享读锁（read lock）</li><li>表独占写锁（write lock）</li></ul><p><strong>读锁不会阻塞其他客户端的读，但是会阻塞写。写锁既会阻塞其他客户端的读，又会阻塞其他客户端的写。</strong></p><p>② 语法：</p><figure class="highlight bash"><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="comment">#表级别的共享锁，也就是读锁；</span></span><br><span class="line"><span class="comment">#允许当前会话读取被锁定的表，但阻止当前会话和其他会话对这些表进行写操作。</span></span><br><span class="line">lock tebles t_student <span class="built_in">read</span>;</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">lock tables t_stuent write;</span><br><span class="line"></span><br><span class="line"><span class="comment">#会话退出，也会释放所有锁</span></span><br><span class="line">unlock tables</span><br></pre></td></tr></table></figure><h4 id="元数据锁"><a href="#元数据锁" class="headerlink" title="元数据锁"></a>元数据锁</h4><p>① MDL加锁过程是系统自动控制，无需显式使用，在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性，在表上有活动事务的时候，不可以对元数据进行写入操作。<strong>为了避免DML与DDL冲突，保证读写的正确性。</strong></p><ul><li>对一张表进行 CRUD（增删改查）操作时，加的是 <strong>MDL 读锁</strong>；</li><li>对一张表做结构变更操作的时候，加的是 <strong>MDL 写锁</strong>；</li></ul><table><thead><tr><th>对应SQL</th><th>锁类型</th><th>说明</th></tr></thead><tbody><tr><td>lock tables xxx read &#x2F;write</td><td>SHARED_READ_ONLY&#x2F;SHARED_NO_READ_WRITE</td><td></td></tr><tr><td>select 、 select … lock in share mode</td><td>SHARED_READ</td><td>与SHARED_READ、SHARED_WRITE兼容，与EXCLUSIVE互斥</td></tr><tr><td>insert 、update、delete、select …for update</td><td>SHARED_WRITE</td><td>与SHARED_READ、SHARED_WRITE兼容，与EXCLUSIVE互斥</td></tr><tr><td>alter table …</td><td>EXCLYSIVE</td><td>与其他的MDL都互斥</td></tr></tbody></table><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">select</span> object_type,object_schema,object_name,lock_type,lock_duration <span class="keyword">from</span> performance_schema.metadata_locks;</span><br></pre></td></tr></table></figure><h4 id="意向锁"><a href="#意向锁" class="headerlink" title="意向锁"></a>意向锁</h4><p>(1) 为什么需要意向锁？</p><p>若没有意向锁，加<strong>表锁</strong>时需要<strong>逐行检查表中所有记录是否有行锁</strong>（比如表有 10 万行，需检查 10 万次），效率极低。</p><p>(2) 核心类比</p><p>把数据库表比作「教室」，表中每一行比作「座位」：</p><ul><li>行锁 &#x3D; 占某个座位；</li><li>意向锁 &#x3D; 教室门口贴的纸条（提示 “教室内已有座位被占用”）；</li><li>表锁 &#x3D; 锁整个教室（要求教室内无任何占座）。</li></ul><p>(3) 意向共享锁和意向独占锁是表级锁，不会和行级的共享锁和独占锁发生冲突，而且意向锁之间也不会发生冲突，只会和**共享表锁（lock tables … read)和独占表锁（lock tables … write）**发生冲突</p><p>① 意向共享锁（IS）：与表锁共享锁（read）兼容，与表锁排它锁（write）互斥。</p><p>② 意向排他锁（IX）：与表锁共享锁（read）及排它锁（write）都互斥。</p><p>(4) 加锁方式：</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><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 意向共享锁：（先在表上加上意向共享锁，然后对读取的记录加共享锁）</span></span><br><span class="line"><span class="keyword">select</span> ... lock <span class="keyword">in</span> share mode</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 意向独占锁：（先表上加上意向独占锁，然后对读取的记录加独占锁）</span></span><br><span class="line"><span class="keyword">insert</span>、<span class="keyword">update</span>、<span class="keyword">delete</span>、<span class="keyword">select</span> ... <span class="keyword">for</span> <span class="keyword">update</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 可以通过以下SQL，查看意向锁及行锁的枷锁情况</span></span><br><span class="line"><span class="keyword">select</span> object_schema,object_name,index_name,lock_type,lock_mode,lock_data <span class="keyword">from</span> performance_schema.data_locks;</span><br></pre></td></tr></table></figure><h3 id="行级锁"><a href="#行级锁" class="headerlink" title="行级锁"></a>行级锁</h3><p>行级锁，每次操作锁住对应的行数据。锁定粒度最小，发生锁冲突的概率最低，并发度最高。应用在InnoDB存储引擎中。</p><ul><li>行锁(Record Lock):锁定单个行记录的锁，防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。</li><li>间隙锁(GapLock):锁定索引记录间隙(不含该记录)，确保索引记录间隙不变，防止其他事务在这个间隙进行insert，产生幻读。在RR隔离级别下都支持。</li><li>临键锁(Next-Key Lock):行锁和间隙锁组合，同时锁住数据，并锁住数据前面的间隙Gap。在RR隔离级别下支持。</li></ul><h4 id="Record-Lock（行锁）"><a href="#Record-Lock（行锁）" class="headerlink" title="Record Lock（行锁）"></a>Record Lock（行锁）</h4><p>Record Lock 称为记录锁，锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分。</p><p>(1) InnoDB实现了以下两种类型的行锁：</p><ul><li>共享锁(S)：允许一个事务去读一行，阻止其他事务获得相同数据集的排它锁。</li><li>排他锁(X)：允许获取排他锁的事务更新数据，阻止其他事务获得相同数据集的共享锁和排他锁。</li></ul><table><thead><tr><th></th><th>S (共享锁)</th><th>X (排他锁)</th></tr></thead><tbody><tr><td>S (共享锁)</td><td>兼容</td><td>冲突</td></tr><tr><td>X (排他锁)</td><td>冲突</td><td>冲突</td></tr></tbody></table><p>(2) 行锁类型：</p><table><thead><tr><th>SQL</th><th>行锁类型</th><th>说明</th></tr></thead><tbody><tr><td>insert，update，delete …</td><td>排他锁</td><td>自动加锁</td></tr><tr><td>select</td><td>不加任何锁</td><td></td></tr><tr><td>select … lock in share mode</td><td>共享锁</td><td>需要手动select之后加上lock in share mode</td></tr><tr><td>select … for update</td><td>排他锁</td><td>需要手动在select之后for update</td></tr></tbody></table><p>(3) 默认情况下，InnoDB在 REPEATABLE READ事务隔离级别运行，InnoDB使用 next-key锁进行搜索和索引扫描，以防止幻读。</p><ul><li>针对唯一索引进行检索时，对已存在的记录进行等值匹配时，将会自动优化为行锁。</li><li>InnoDB的行锁是针对于索引加的锁，不通过索引条件检索数据，那么!nnoDB将对表中的所有记录加锁，此时 <strong>就会升级为表锁</strong>。</li></ul><p>(4) 查看意向锁及行锁的加锁情况：</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> object_schema,object_name,index_name,lock_type,lock_mode,lock_data <span class="keyword">from</span> performance_schema.data_locks;</span><br></pre></td></tr></table></figure><h4 id="Gap-Lock（间隙锁）"><a href="#Gap-Lock（间隙锁）" class="headerlink" title="Gap Lock（间隙锁）"></a>Gap Lock（间隙锁）</h4><p>(1) Gap Lock 称为间隙锁，只存在于可重复读隔离级别，目的是为了解决可重复读隔离级别下幻读的现象。</p><p>(2) 假设，表中有一个范围 id 为（3，5）间隙锁，那么其他事务就无法插入 id &#x3D; 4 这条记录了，这样就有效的防止幻读现象的发生。</p><img src="https://i-blog.csdnimg.cn/direct/afdb685a0d8f40b6b4c276eeda5cffda.png" alt="img" style="zoom:67%;" /><p>间隙锁虽然存在 X 型间隙锁和 S 型间隙锁，但是并没有什么区别，间隙锁之间是兼容的，即两个事务可以同时持有包含共同间隙范围的间隙锁，并不存在互斥关系，因为间隙锁的目的是防止插入幻影记录而提出的。</p><h4 id="Next-Key-Lock（临键锁）"><a href="#Next-Key-Lock（临键锁）" class="headerlink" title="Next-Key Lock（临键锁）"></a>Next-Key Lock（临键锁）</h4><p>(1) Next-Key Lock 称为临键锁，是 Record Lock + Gap Lock 的组合，锁定一个范围，并且锁定记录本身。</p><p>(2) 假设，表中有一个范围 id 为（3，5] 的 next-key lock，那么其他事务即不能插入 id &#x3D; 4 记录，也不能修改 id &#x3D; 5 这条记录。</p><img src="https://i-blog.csdnimg.cn/direct/3b2b07bb049943ecb3cc2b16df5d36af.png" alt="img" style="zoom:67%;" /><p>(3) 所以，next-key lock 既能保护该记录，又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。</p><h4 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h4><p>默认情况下，InnODB在 REPEATABLE READ事务隔离级别运行，InnoDB使用 next-key 锁进行搜索和索引扫描，以防止幻读。</p><ul><li>索引上的等值查询(唯一索引)，给不存在的记录加锁时,优化为间隙锁 。</li><li>索引上的等值查询(普通索引)，向右遍历时最后一个值不满足查询需求时，next-keylock退化为间隙锁。</li><li>索引上的范围查询(唯一索引)–会访问到不满足条件的第一个值为止。</li></ul><h3 id="InnoDB引擎"><a href="#InnoDB引擎" class="headerlink" title="InnoDB引擎"></a>InnoDB引擎</h3><h4 id="逻辑存储结构"><a href="#逻辑存储结构" class="headerlink" title="逻辑存储结构"></a>逻辑存储结构</h4><img src="https://i-blog.csdnimg.cn/direct/8ad74d01b3e9492e8784ad3d340aac53.png" alt="img" style="zoom: 67%;" /><p>(1) 表空间（对应 ibd 文件）</p><p>MySQL 实例级的存储容器，一个 MySQL 实例可以对应多个表空间，用于存储表的记录、索引等数据。</p><p>(2) 段（Segment）</p><p>① 是表空间的下一级结构，用于管理<strong>多个区</strong>；</p><p>② 分类：</p><ul><li>数据段（Leaf node segment）：对应 B + 树的<strong>叶子节点</strong></li><li>索引段（Non-leaf node segment）：对应 B + 树的<strong>非叶子节点</strong>；</li><li>回滚段（Rollback segment）：用于事务回滚相关的版本管理。</li></ul><p>(3) 区（Extent）</p><p>表空间的单元结构，每个区的大小为 1MB，默认 InnoDB 页大小为 16KB，因此 1 个区包含 <code>1MB ÷ 16KB = 64个连续的页</code>。</p><p>(4) 页（Page）</p><p>① InnoDB 存储引擎**磁盘管理的最小单元，**每个页的大小默认 16KB；</p><p>② 为了保证页的物理连续性，InnoDB 存储引擎每次会从磁盘<strong>一次性申请 4-5 个区</strong>。</p><p>(5) 行</p><p>① InnoDB 的数据是按 “行” 为单位存放的；</p><p>② 行的隐藏列（事务相关）：</p><ul><li><code>trx_id</code>：事务 ID，每次修改记录时，会将对应事务的 ID 存入<code>trx_id</code>隐藏列；</li><li><code>roll_pointer</code>（回滚指针）：修改记录时，会把数据的旧版本写入 undo 日志；回滚指针相当于 “指针”，可通过它找到记录修改前的信息。</li></ul><h4 id="整体架构"><a href="#整体架构" class="headerlink" title="整体架构"></a>整体架构</h4><img src="https://i-blog.csdnimg.cn/direct/fe3c6b967dd34815b4492a7135522248.png" alt="img" style="zoom:67%;" /><h4 id="内存架构"><a href="#内存架构" class="headerlink" title="内存架构"></a>内存架构</h4><img src="https://i-blog.csdnimg.cn/direct/4d3dc91fb6e541f28842497290f98fcf.png" alt="img" style="zoom:67%;" /><p>(1) Buffer Pool：</p><p>① 缓冲池是内存中的一个区域，它可以缓存磁盘上经常操作的真实数据，在执行增删改查操作时，先操作缓冲中的数据（若缓冲池没有数据，则从磁盘加载并缓存），然后以一定频率刷新到磁盘，从而减少磁盘 IO，加快处理速度。</p><p>② 缓冲池以 Page 为单位，采用链表数据结构管理 Page。根据状态，将 Page 分为三种类型：</p><ul><li>free page：空的 page，未被使用。</li><li>clean page：被使用 page，数据没有被修改过。</li><li>dirty page：脏页，被使用 page，数据被修改过，也就是缓冲与磁盘的数据产生了不一致。</li></ul><p>(2) Change Buffer：</p><p>① 更改缓存（针对于非唯一二级索引使用），在执行 DML 语句时，如果该数据 Page 没有在 Buffer Pool 里，不会直接把磁盘进行操作，而会将变更暂存在更改缓存区 Change Buffer 中，在未来数据被读取时，再将数据合并并更新到 Buffer Pool，再将合并后的数据刷新到磁盘中。</p><p>② 与主键索引不同，二级索引通常是非唯一的，并且以相对随机的顺序插入二级索引，同样，删除和更新可能会影响索引中不相邻的二级索引页，如果每一次都操作磁盘，会造成大量的随机 IO，有了 ChangeBuffer 之后，我们可以在缓存池中进行合并处理，减少磁盘 IO。</p><p>(3) Adaptive Hash Index：</p><p>① 自适应 hash 索引，用于优化对 Buffer Pool 数据的查询。InnoDB 存储引擎会监控对表上各索引页的查询，如果观察到 hash 索引可以提升速度，则建立 hash 索引，称之为自适应 hash 索引。</p><p>② 自适应哈希索引，无需人工干预，是系统根据情况自动完成。</p><p>(4) Log Buffer：</p><p>① 日志缓冲区，用来保存要写入到磁盘中的 log 日志数据（redo log、undo log）。默认大小为 16MB，日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务，增加日志缓冲区的大小可以节省磁盘 I&#x2F;O。</p><p>② 参数：</p><ul><li>Innodb_log_buffer_size：缓冲区大小</li><li>innodb_flush_log_at_trx_commit：日志刷新到磁盘时机</li></ul><p>1: 日志在每次事务提交时写入并刷新到磁盘</p><p>0: 每秒将日志写入并刷新到磁盘一次</p><p>2: 日志在每次事务提交后写入，并每秒刷新到磁盘一次</p><h4 id="磁盘结构"><a href="#磁盘结构" class="headerlink" title="磁盘结构"></a>磁盘结构</h4><img src="https://i-blog.csdnimg.cn/direct/d075202d7ad24e87a49ccb16583a8ef6.png" alt="img" style="zoom: 67%;" /><p>(1) System Tablespace：</p><p>① 是更改缓冲区的存储区域；若表创建在该空间（而非单表 &#x2F; 通用表空间），还会包含表、索引数据。(在 MySQL5.x 版本中还包含 InnoDB 数据字典、undolog 等)</p><p>② 参数：innodb_data_file_path</p><p>(2) File-Per-Table Tablespaces：</p><p>① 每个表的文件表空间包含单个 InnoDB 表的数据和索引，并存储在文件系统上的单个数据文件中。</p><p>② 参数：innodb_file_per_table</p><p>(3) General Tablespaces：通用表空间</p><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">CREATE</span> TABLESPACE xxx <span class="keyword">ADD</span> DATAFILE <span class="string">&#x27;file_name&#x27;</span> ENGINE <span class="operator">=</span> engine_name;</span><br></pre></td></tr></table></figure><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">CREATE TABLE</span> xxx ... TABLESPACE ts_name;</span><br></pre></td></tr></table></figure><p>(4) Undo Tablespaces：</p><p>撤销表空间，MySQL 在实例初始化时会自动创建两个默认的 undo 表空间（初始大小 16MB），用于存储 undo log 日志。</p><p>(5) Temporary Tablespaces：</p><p>InnoDB 使用设备临时表空间和全局临时表空间，存储用户创建的临时表等数据。</p><p>(6) Doublewrite Buffer Files：</p><p>① 双写缓冲，InnoDB 引擎将数据页从 Buffer Pool 刷新到磁盘前，先将数据页写入双写缓冲文件中，便于系统异常时恢复数据。</p><p>② 示例文件：</p><ul><li><code>#ib_16384.0.dblwr</code></li><li><code>#ib_16384.1.dblwr</code></li></ul><p>(7) Redo Log：</p><p>① 重做日志，是用来实现事务的持久性。该日志文件由两部分组成：重做日志缓冲（redo log buffer）以及重做日志文件（redo log）。前者是在内存中，后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志中，用于在数据页写到磁盘时发生错误时，进行数据恢复使用。</p><p>② 以循环方式写入重做日志文件，涉及两个文件：</p><ul><li><code>ib_logfile0</code></li><li><code>ib_logfile1</code></li></ul><h4 id="后台线程"><a href="#后台线程" class="headerlink" title="后台线程"></a>后台线程</h4><p><img src="https://i-blog.csdnimg.cn/direct/c5ec1a62d0b34c06b01475e53ad76645.png" alt="img"></p><h4 id="事务原理"><a href="#事务原理" class="headerlink" title="事务原理"></a>事务原理</h4><p>(1) 事务：一组操作的集合，它是一个不可分割的工作单位，事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败。</p><p>(2) 特征：</p><ul><li>原子性(Atomicity)：事务是不可分割的最小操作单元，要么全部成功，要么全部失败。</li><li>一致性(Consistency) ：事务完成时，必须使所有的数据都保持一致状态。</li><li>隔离性(lsolation)：数据库系统提供的隔离机制，保证事务在不受外部并发操作影响的独立环境下运行。</li><li>持久性(Durability)：事务一旦提交或回滚，它对数据库中的数据的改变就是永久的。</li></ul><p>(3) 特性原理分类图：</p><img src="https://i-blog.csdnimg.cn/direct/f7df936e48484d639d06ca933668e7f0.png" alt="img" style="zoom:67%;" /><h4 id="redo-log"><a href="#redo-log" class="headerlink" title="redo log"></a>redo log</h4><p>(1) 重做日志，记录的是事务提交时数据页的物理修改，是用来实现事务的<strong>持久性</strong>。</p><p>(2) 该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中，后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。</p><p>(3) Buffer Pool在产生脏页数据的时候，会先将数据存储到 redo log buffer 再存储到 redo log 中进行磁盘持久化存储，在内存出现异常（比如突然断电）时，通过redo log中持久化的数据进行回滚。过程如下图：</p><p><img src="https://i-blog.csdnimg.cn/direct/062527b6023940f0b108036edadb9159.png" alt="img"></p><p>(4) redo log 分为 2 部分：</p><ul><li>内存里的「redo log 缓冲」：临时存一下修改记录（相当于 “草稿纸”）；</li><li>磁盘里的「redo log 文件」：长期存修改记录（相当于 “正式笔记本”）。</li></ul><p>(5) 工作流程像这样：</p><p>比如你修改了一条数据 → 先把 “我改了啥、怎么改的” 记到「redo log 缓冲」里 → 事务提交时，把缓冲里的记录写到「redo log 文件」里 → 之后数据库再慢慢把修改好的数据（存在内存缓存里的 “脏数据”）刷到磁盘。</p><p>要是中途突然断电：重启后，数据库会看「redo log 文件」里的记录，把没来得及刷到磁盘的修改，重新恢复出来。</p><p>Q：redo log 要写到磁盘，数据也要写磁盘，为什么要多此一举?</p><p>A：redo log 是 ”顺序写“，**磁盘处理起来特别快；**而直接写数据是 ”随机写“，得先找到数据在磁盘里的位置，再修改，<strong>速度很慢。</strong></p><h4 id="undo-log"><a href="#undo-log" class="headerlink" title="undo log"></a>undo log</h4><p>(1) 回滚日志，用于记录数据被修改前的信息，作用包含两个：提供回滚 和 MVCC(多版本并发控制)。</p><p>(2) undo log 和 redo log 记录物理日志不一样，它是逻辑日志。可以认为当 delete 一条记录时，undo log中会记录一条对应的insert记录，反之亦然，当 update 一条记录时，它记录一条对应相反的 update 记录。当执行 rollback 时，就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。</p><p>(3) Undo log 销毁：undo log 在事务执行时产生，事务提交时，并不会立即删除undol0g，因为这些日志可能还用于 MVCC。</p><p>(4) Undo log 存储：undo log 采用段的方式进行管理和记录，存放在前面介绍的 rollback segment 回滚段中，内部包含1024个 undo log segment.</p><h3 id="MVCC"><a href="#MVCC" class="headerlink" title="MVCC"></a>MVCC</h3><h4 id="当前读："><a href="#当前读：" class="headerlink" title="当前读："></a>当前读：</h4><p>读取的是记录的最新版本，读取时还要保证其他并发事务不能修改当前记录，会对读取的记录进行加锁。对于我们日常的操作，如：select…lock in share mode(共享锁)，select… for update、update、insert、delete(排他锁)都是一种当前读。</p><h4 id="快照读："><a href="#快照读：" class="headerlink" title="快照读："></a>快照读：</h4><p>简单的select(不加锁)就是快照读，快照读，读取的是记录数据的可见版本，有可能是历史数据，不加锁，是非阻塞读。</p><ul><li>Read committed:每次select，都生成一个快照读。</li><li>Repeatable Read:开启事务后第一个select语句才是快照读的地方。</li><li>Serializable:快照读会退化为当前读。</li></ul><h4 id="MVCC："><a href="#MVCC：" class="headerlink" title="MVCC："></a>MVCC：</h4><p>全称 Multi-Version Concurrency Control，多版本并发控制。指维护一个数据的多个版本，使得读写操作没有冲突，快照读为 MVSQL 实现MVCC提供了一个非阻塞读功能。MVCC的具体实现，还需要依赖于数据库记录中的三个隐式字段、undo log日志、read View。</p><h4 id="三个隐藏字段"><a href="#三个隐藏字段" class="headerlink" title="三个隐藏字段"></a>三个隐藏字段</h4><p><img src="https://i-blog.csdnimg.cn/direct/e7dccccd8d3449919946f93ec08cb8e0.png" alt="img"></p><h4 id="undo-log-1"><a href="#undo-log-1" class="headerlink" title="undo log"></a>undo log</h4><p>(1) 回滚日志，在insert、update、delete的时候产生的便于数据回滚的日志。</p><p>(2) 当insert的时候，产生的undolog日志只在回滚时需要，在事务提交后，可被立即删除。</p><p>(3) 而update、delete的时候，产生的undo log日志不仅在回滚时需要，在快照读时也需要，不会立即被删除。</p><p>(4) 那么何时删除？</p><p><strong>① 事务提交后</strong>：</p><ul><li>对于<code>INSERT</code>操作，事务提交后，undo log可以被立即删除，因为不再需要用于回滚。例如：你新增了一条 “订单记录”，事务提交后，既不需要回滚（回滚是删这条记录，但提交了就不用回滚了），也不会有其他事务需要读这条记录的 “旧版本”（因为它是刚新增的，没有更早的版本），所以 undo log 没用了，直接删。</li></ul><p>**Q：**为什么说没有旧版本？旧版本不是没有插入之前的吗？</p><p>**A：**这里的旧版本指的是 **“这条新插入记录本身的旧版本” ，**不是 ”表在插入前的状态“，可以理解成行锁和表锁的关系。</p><ul><li>对于<code>UPDATE</code>和<code>DELETE</code>操作，undo log不会立即被删除，因为它们可能在后续的快照读取中被使用。例如：你把 “订单金额从 100 改成 200”，事务提交后，可能有其他事务需要看 “金额 100” 这个旧版本（比如查 5 分钟前的订单状态），这时候得靠 undo log 调出旧数据，所以它还能用，得留着。</li></ul><p><strong>② 快照读取结束</strong>：</p><ul><li>当所有依赖于该undo log的快照读取操作结束后，undo log才会被删除。这意味着如果有一个事务正在进行快照读取，并且依赖于某个undo log，那么这个undo log会一直保留直到该事务结束。</li></ul><p>(5) undo log 版本链</p><p><img src="https://i-blog.csdnimg.cn/direct/882379e7ae654122b9f2c62ccfc3b7f6.png" alt="img"></p><h4 id="readview"><a href="#readview" class="headerlink" title="readview"></a>readview</h4><p>(1) ReadView(读视图)是 快照读 SOL执行时MVCC提取数据的依据，记录并维护系统当前活跃的事务(未提交的)id。</p><p>(2) ReadView中包含了四个核心字段:</p><table><thead><tr><th>字段</th><th>含义</th></tr></thead><tbody><tr><td>m_ids</td><td>当前活跃的事务ID集合</td></tr><tr><td>min_trx_id</td><td>最小活跃事务ID</td></tr><tr><td>max_trx_id</td><td>预分配事务ID，当前最大事务ID+1（因为事务ID是自增的）</td></tr><tr><td>creator_trx_id</td><td>ReadView创建者的事务ID（即 “当前要读数据的那个事务” 的 ID）</td></tr></tbody></table><p>(3) 版本链数据访问规则</p><p><img src="https://i-blog.csdnimg.cn/direct/9c1af48f75304a2a943b15bd678e7518.png" alt="img"></p><p>① 规则 1：（trx_id &#x3D; creator_trx_id → 能访问）：</p><p>这里的<code>creator_trx_id</code>就是表格里的 “ReadView 创建者的事务 ID”—— 如果某数据版本的修改者 ID，和 “当前读数据的事务 ID” 一致，说明是自己改的，能访问。</p><p>② 规则 2：（trx_id &lt; min_trx_id → 能访问）：</p><p><code>min_trx_id</code>是 “最小活跃事务 ID”—— 如果某数据版本的修改者 ID，比 “当前最小编号的活跃事务” 还小，说明这个修改事务<strong>早于所有活跃事务完成提交</strong>，能访问。</p><p>③ 规则 3：（trx_id &gt; max_trx_id → 不能访问）：</p><p><code>max_trx_id</code>是 “预分配事务 ID（当前最大 ID+1）”—— 如果某数据版本的修改者 ID，比这个预分配 ID 还大，说明这个修改事务是<strong>在 ReadView 创建之后才启动的</strong>（事务 ID 自增），还没完成，不能访问。</p><p>④ 规则 4：（min_trx_id ≤ trx_id ≤ max_trx_id 且 trx_id 不在 m_ids 里 → 能访问）：</p><ul><li><code>min_trx_id</code>&#x2F;<code>max_trx_id</code>限定了 “当前活跃事务的 ID 范围”；</li><li><code>m_ids</code>是 “当前活跃的事务 ID 集合”—— 如果某数据版本的修改者 ID 在这个范围内，但<strong>不在活跃集合里</strong>，说明这个修改事务<strong>已经提交了</strong>（从活跃状态变成已完成），能访问。</li></ul><p>(4) 原理分析（RC 级别）</p><p><img src="https://i-blog.csdnimg.cn/direct/9c42739188d747f5bb20152ecd89a033.png" alt="img"></p><p>之所以事务3，事务4都是活跃事务，是因为这个表格执行顺序是从上至下的，在事务5 第一次查询 id 为 30 的记录时，事务3 和 事务4 都没有结束。在事务5 第二次查询 id 为 30 的记录时，事务3 已经结束了</p><p>(5) 原理分析（RR 级别）</p><p><img src="https://i-blog.csdnimg.cn/direct/3299357e62ec49019f4ea81f204354a9.png" alt="img"></p><h3 id="MySQL-管理"><a href="#MySQL-管理" class="headerlink" title="MySQL 管理"></a>MySQL 管理</h3><h4 id="系统数据库介绍"><a href="#系统数据库介绍" class="headerlink" title="系统数据库介绍"></a>系统数据库介绍</h4><p>Mysql数据库安装完成后，自带了一下四个数据库，具体作用如下：</p><table><thead><tr><th>数据库</th><th>含义</th></tr></thead><tbody><tr><td>mysql</td><td>存储MySQL服务器正常运行所需要的各种信息(时区、主从、用户、权限等)</td></tr><tr><td>information_schema</td><td>提供了访问数据库元数据的各种表和视图，包含数据库、表、字段类型及访问权限等</td></tr><tr><td>performance_schema</td><td>为MySQL服务器运行时状态提供了一个底层监控功能，主要用于收集数据库服务器性能参数</td></tr><tr><td>sys</td><td>包含了一系列方便 DBA和开发人员利用 performance_schema性能数据库进行性能调优和诊断的视图</td></tr></tbody></table><h4 id="常用工具"><a href="#常用工具" class="headerlink" title="常用工具"></a>常用工具</h4><p>(1) mysql </p><p><img src="https://i-blog.csdnimg.cn/direct/c4af0fe5796b42438e2ffb2ad455c2b8.png" alt="img"></p><p>(2) mysqladmin</p><img src="https://i-blog.csdnimg.cn/direct/af2f98362d6143c39eb902929cfd8788.png" alt="img" style="zoom: 50%;" /><p>(3) mysqlbinlog</p><img src="https://i-blog.csdnimg.cn/direct/e245ad00b24943978098f2d0a2db2ebd.png" alt="img" style="zoom:50%;" /><p>(4) mysqlshow</p><img src="https://i-blog.csdnimg.cn/direct/364094db1aed4712a8b621c9d448f80c.png" alt="img" style="zoom:50%;" /><p>(5) mysqldump</p><p><img src="https://i-blog.csdnimg.cn/direct/d2b62139795f498cafc2f31406cb8191.png" alt="img"></p><p>(6) mysqlimport &#x2F; source</p><p><img src="C:\Users\24539\AppData\Roaming\Typora\typora-user-images\image-20260329115110609.png" alt="image-20260329115110609"></p>]]></content>
    
    
    <summary type="html">MySQL 存储引擎、索引、SQL 优化、事务锁机制与性能调优实战</summary>
    
    
    
    <category term="MySQL" scheme="https://kon-forever.cloud/categories/MySQL/"/>
    
    
    <category term="MySQL" scheme="https://kon-forever.cloud/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>MySQL学习笔记之基础篇</title>
    <link href="https://kon-forever.cloud/2026/03/28/MySQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E5%9F%BA%E7%A1%80%E7%AF%87/"/>
    <id>https://kon-forever.cloud/2026/03/28/MySQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8B%E5%9F%BA%E7%A1%80%E7%AF%87/</id>
    <published>2026-03-28T02:00:00.000Z</published>
    <updated>2026-10-02T04:03:07.783Z</updated>
    
    <content type="html"><![CDATA[<h2 id="MySQL基础篇"><a href="#MySQL基础篇" class="headerlink" title="MySQL基础篇"></a>MySQL基础篇</h2><h3 id="DDL（数据定义语言）"><a href="#DDL（数据定义语言）" class="headerlink" title="DDL（数据定义语言）"></a>DDL（数据定义语言）</h3><h4 id="作用："><a href="#作用：" class="headerlink" title="作用："></a><strong>作用：</strong></h4><p>用来定义数据库对象（数据库、表、字段）</p><h4 id="数据库操作"><a href="#数据库操作" class="headerlink" title="数据库操作"></a>数据库操作</h4><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">SHOW</span> DATABASES;</span><br></pre></td></tr></table></figure><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">SELECT</span> DATABASE();</span><br></pre></td></tr></table></figure><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">CREATE</span> DATABASE [ IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> ] 数据库名 [ <span class="keyword">DEFAULT</span> CHARSET 字符集] [<span class="keyword">COLLATE</span> 排序规则 ];</span><br></pre></td></tr></table></figure><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">DROP</span> DATABASE [ IF <span class="keyword">EXISTS</span> ] 数据库名;</span><br></pre></td></tr></table></figure><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">USE 数据库名;</span><br></pre></td></tr></table></figure><p>注意：UTF8字符集长度为3字节，有些符号占4字节，所以推荐用utf8mb4字符集</p><h4 id="表操作"><a href="#表操作" class="headerlink" title="表操作"></a>表操作</h4><p>(1) 查询当前数据库所有表：</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">SHOW</span> TABLES;</span><br></pre></td></tr></table></figure><p>(2) 查询表结构：</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">DESC</span> 表名;</span><br></pre></td></tr></table></figure><p>(3) 查询指定表的建表语句：</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">SHOW</span> <span class="keyword">CREATE TABLE</span> 表名;</span><br></pre></td></tr></table></figure><p>(4) 创建表：</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 TABLE</span> 表名(</span><br><span class="line">字段<span class="number">1</span> 字段<span class="number">1</span>类型 [COMMENT 字段<span class="number">1</span>注释],</span><br><span class="line">字段<span class="number">2</span> 字段<span class="number">2</span>类型 [COMMENT 字段<span class="number">2</span>注释],</span><br><span class="line">字段<span class="number">3</span> 字段<span class="number">3</span>类型 [COMMENT 字段<span class="number">3</span>注释],</span><br><span class="line">...</span><br><span class="line">字段n 字段n类型 [COMMENT 字段n注释]</span><br><span class="line">)[ COMMENT 表注释 ];</span><br></pre></td></tr></table></figure><p><strong>注意：最后一个字段后面没有逗号</strong></p><p>(5) 添加字段：</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">ALTER TABLE</span> 表名 <span class="keyword">ADD</span> 字段名 类型(长度) [COMMENT 注释] [约束];</span><br></pre></td></tr></table></figure><p>例：<code>ALTER TABLE emp ADD nickname varchar(20) COMMENT &#39;昵称&#39;;</code></p><p>(6) 修改数据类型：</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">ALTER TABLE</span> 表名 MODIFY 字段名 新数据类型(长度);</span><br></pre></td></tr></table></figure><p>(7) 修改字段名和字段类型：</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">ALTER TABLE</span> 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释] [约束];</span><br></pre></td></tr></table></figure><p>例：将emp表的nickname字段修改为username，类型为varchar(30)<br> <code>ALTER TABLE emp CHANGE nickname username varchar(30) COMMENT &#39;昵称&#39;;</code></p><p>(8) 删除字段：</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">ALTER TABLE</span> 表名 <span class="keyword">DROP</span> 字段名;</span><br></pre></td></tr></table></figure><p>(9) 修改表名：</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">ALTER TABLE</span> 表名 RENAME <span class="keyword">TO</span> 新表名</span><br></pre></td></tr></table></figure><p>(10) 删除表：</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">DROP</span> <span class="keyword">TABLE</span> [IF <span class="keyword">EXISTS</span>] 表名;</span><br></pre></td></tr></table></figure><h4 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h4><p>用于存储数字，分整数、浮点、定点数：</p><h5 id="整数类型（按范围从小到大）"><a href="#整数类型（按范围从小到大）" class="headerlink" title="整数类型（按范围从小到大）"></a>整数类型（按范围从小到大）</h5><table><thead><tr><th>类型</th><th>大小</th><th>有符号范围</th><th>无符号范围</th><th>说明</th></tr></thead><tbody><tr><td>TINYINT</td><td>1byte</td><td>(-128, 127)</td><td>(0, 255)</td><td>小整数</td></tr><tr><td>SMALLINT</td><td>2bytes</td><td>(-32768, 32767)</td><td>(0, 65535)</td><td>中整数</td></tr><tr><td>MEDIUMINT</td><td>3bytes</td><td>(-8388608, 8388607)</td><td>(0, 16777215)</td><td>大整数</td></tr><tr><td>INT&#x2F;INTEGER</td><td>4bytes</td><td>(-2147483648, 2147483647)</td><td>(0, 4294967295)</td><td>常用大整数</td></tr><tr><td>BIGINT</td><td>8bytes</td><td>(-2^63, 2^63-1)</td><td>(0, 2^64-1)</td><td>极大整数</td></tr></tbody></table><p>✅ 备注：无符号整数写法（如无符号 int）：<code>INT UNSIGNED</code></p><h5 id="浮点-定点数"><a href="#浮点-定点数" class="headerlink" title="浮点 &#x2F; 定点数"></a>浮点 &#x2F; 定点数</h5><table><thead><tr><th>类型</th><th>大小</th><th>说明</th></tr></thead><tbody><tr><td>FLOAT</td><td>4bytes</td><td>单精度浮点（范围约 ±3.4E+38）</td></tr><tr><td>DOUBLE</td><td>8bytes</td><td>双精度浮点（范围约 ±1.8E+308）</td></tr><tr><td>DECIMAL</td><td>依赖 M&#x2F;D</td><td>精确定点数（适合金额等场景）</td></tr></tbody></table><p>✅ 备注：double 可指定 “总长 + 小数位”，格式：<code>double(总长, 小数位)</code>（如<code>double(4,1)</code>）</p><h4 id="字符类型"><a href="#字符类型" class="headerlink" title="字符类型"></a>字符类型</h4><p>用于存储字符串 &#x2F; 二进制数据，分定长、变长、二进制、文本：</p><table><thead><tr><th>类型</th><th>大小范围</th><th>描述</th></tr></thead><tbody><tr><td>CHAR</td><td>0-255bytes</td><td>定长字符串（性能高）</td></tr><tr><td>VARCHAR</td><td>0-65535bytes</td><td>变长字符串（性能略低）</td></tr><tr><td>TINYBLOB</td><td>0-255bytes</td><td>小二进制数据（≤255 字符）</td></tr><tr><td>TINYTEXT</td><td>0-255bytes</td><td>短文本字符串</td></tr><tr><td>BLOB</td><td>0-65535bytes</td><td>长二进制数据</td></tr><tr><td>TEXT</td><td>0-65535bytes</td><td>长文本数据</td></tr><tr><td>MEDIUMBLOB</td><td>0-16777215bytes</td><td>中等长二进制数据</td></tr><tr><td>MEDIUMTEXT</td><td>0-16777215bytes</td><td>中等长文本数据</td></tr><tr><td>LONGBLOB</td><td>0-4294967295bytes</td><td>极大二进制数据</td></tr><tr><td>LONGTEXT</td><td>0-4294967295bytes</td><td>极大文本数据</td></tr></tbody></table><p>✅ 性能提示：同长度下，<code>char(n)</code>（如<code>char(10)</code>）性能优于<code>varchar(n)</code>（定长存储更高效）</p><h4 id="日期时间类型"><a href="#日期时间类型" class="headerlink" title="日期时间类型"></a>日期时间类型</h4><p>用于存储日期、时间相关数据：</p><table><thead><tr><th>类型</th><th>大小</th><th>范围</th><th>格式</th><th>描述</th></tr></thead><tbody><tr><td>DATE</td><td>3bytes</td><td>1000-01-01 ~ 9999-12-31</td><td>YYYY-MM-DD</td><td>纯日期值</td></tr><tr><td>TIME</td><td>3bytes</td><td>-838:59:59 ~ 838:59:59</td><td>HH:MM:SS</td><td>时间间隔 &#x2F; 持续时间</td></tr><tr><td>YEAR</td><td>1byte</td><td>1901 ~ 2155</td><td>YYYY</td><td>纯年份值</td></tr><tr><td>DATETIME</td><td>8bytes</td><td>1000-01-01 00:00:00 ~ 9999-12-31 23:59:59</td><td>YYYY-MM-DD HH:MM:SS</td><td>混合日期 + 时间</td></tr><tr><td>TIMESTAMP</td><td>4bytes</td><td>1970-01-01 00:00:01 ~ 2038-01-19 03:14:07</td><td>YYYY-MM-DD HH:MM:SS</td><td>带时间戳特性的日期时间</td></tr></tbody></table><h3 id="DML（数据操作语言）"><a href="#DML（数据操作语言）" class="headerlink" title="DML（数据操作语言）"></a>DML（数据操作语言）</h3><h4 id="添加数据"><a href="#添加数据" class="headerlink" title="添加数据"></a>添加数据</h4><p>(1) 指定字段：</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">INSERT INTO</span> 表名 (字段名<span class="number">1</span>, 字段名<span class="number">2</span>, ...) <span class="keyword">VALUES</span> (值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br></pre></td></tr></table></figure><p>(2) 全部字段：</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">INSERT INTO</span> 表名 <span class="keyword">VALUES</span> (值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br></pre></td></tr></table></figure><p>(3) 批量添加数据：</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">INSERT INTO</span> 表名 (字段名<span class="number">1</span>, 字段名<span class="number">2</span>, ...) <span class="keyword">VALUES</span> (值<span class="number">1</span>, 值<span class="number">2</span>, ...), (值<span class="number">1</span>, 值<span class="number">2</span>, ...), (值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br><span class="line"><span class="keyword">INSERT INTO</span> 表名 <span class="keyword">VALUES</span> (值<span class="number">1</span>, 值<span class="number">2</span>, ...), (值<span class="number">1</span>, 值<span class="number">2</span>, ...), (值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br></pre></td></tr></table></figure><p>注意：</p><ul><li>字符串和日期类型数据应该包含在引号中</li><li>插入的数据大小应该在字段的规定范围内</li></ul><h4 id="更新和删除数据"><a href="#更新和删除数据" class="headerlink" title="更新和删除数据"></a>更新和删除数据</h4><p>(1) 修改数据：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> 表名 <span class="keyword">SET</span> 字段名<span class="number">1</span> <span class="operator">=</span> 值<span class="number">1</span>, 字段名<span class="number">2</span> <span class="operator">=</span> 值<span class="number">2</span>, ... [ <span class="keyword">WHERE</span> 条件 ];</span><br><span class="line">例：</span><br><span class="line"><span class="keyword">UPDATE</span> emp <span class="keyword">SET</span> name <span class="operator">=</span> <span class="string">&#x27;Jack&#x27;</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>(2) 删除数据：</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">DELETE FROM 表名 [ WHERE 条件 ];</span><br></pre></td></tr></table></figure><h3 id="DQL（数据查询语言）"><a href="#DQL（数据查询语言）" class="headerlink" title="DQL（数据查询语言）"></a>DQL（数据查询语言）</h3><h4 id="语法："><a href="#语法：" class="headerlink" title="语法："></a>语法：</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">字段列表</span><br><span class="line"><span class="keyword">FROM</span></span><br><span class="line">表名字段</span><br><span class="line"><span class="keyword">WHERE</span></span><br><span class="line">条件列表</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span></span><br><span class="line">分组字段列表</span><br><span class="line"><span class="keyword">HAVING</span></span><br><span class="line">分组后的条件列表</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span></span><br><span class="line">排序字段列表</span><br><span class="line">LIMIT</span><br><span class="line">分页参数</span><br></pre></td></tr></table></figure><h4 id="基础查询"><a href="#基础查询" class="headerlink" title="基础查询"></a>基础查询</h4><p>(1) 查询多个字段：</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="number">1</span>, 字段<span class="number">2</span>, 字段<span class="number">3</span>, ... <span class="keyword">FROM</span> 表名;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><p>(2) 设置别名：</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="number">1</span> [ <span class="keyword">AS</span> 别名<span class="number">1</span> ], 字段<span class="number">2</span> [ <span class="keyword">AS</span> 别名<span class="number">2</span> ], 字段<span class="number">3</span> [ <span class="keyword">AS</span> 别名<span class="number">3</span> ], ... <span class="keyword">FROM</span> 表名;</span><br><span class="line"><span class="keyword">SELECT</span> 字段<span class="number">1</span> [ 别名<span class="number">1</span> ], 字段<span class="number">2</span> [ 别名<span class="number">2</span> ], 字段<span class="number">3</span> [ 别名<span class="number">3</span> ], ... <span class="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><p>(3) 去除重复记录：</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="keyword">DISTINCT</span> 字段列表 <span class="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><h4 id="条件查询"><a href="#条件查询" class="headerlink" title="条件查询"></a>条件查询</h4><p>(1) 语法：</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="keyword">FROM</span> 表名 <span class="keyword">WHERE</span> 条件列表;</span><br></pre></td></tr></table></figure><p>(2) 条件：</p><table><thead><tr><th>比较运算符</th><th>功能</th></tr></thead><tbody><tr><td>&gt;</td><td>大于</td></tr><tr><td>&gt;&#x3D;</td><td>大于等于</td></tr><tr><td>&lt;</td><td>小于</td></tr><tr><td>&lt;&#x3D;</td><td>小于等于</td></tr><tr><td>&#x3D;</td><td>等于</td></tr><tr><td>&lt;&gt; 或 !&#x3D;</td><td>不等于</td></tr><tr><td>BETWEEN … AND …</td><td>在某个范围内（含最小、最大值）</td></tr><tr><td>IN(…)</td><td>在in之后的列表中的值，多选一</td></tr><tr><td>LIKE 占位符</td><td>模糊匹配（_匹配单个字符，%匹配任意个字符）</td></tr><tr><td>IS NULL</td><td>是NULL</td></tr></tbody></table><table><thead><tr><th>逻辑运算符</th><th>功能</th></tr></thead><tbody><tr><td>AND 或 &amp;&amp;</td><td>并且（多个条件同时成立）</td></tr><tr><td>OR 或 ||</td><td>或者（多个条件任意一个成立）</td></tr><tr><td>NOT 或 !</td><td>非，不是</td></tr></tbody></table><p>(3) 例子：</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 1. 查询年龄等于 88 的员工</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="operator">=</span> <span class="number">88</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2. 查询年龄小于 20 的员工</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="operator">&lt;</span> <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3. 查询没有身份证号的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> idCard <span class="keyword">is</span> <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 4. 查询所有身份证号的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> idCard <span class="keyword">is</span> <span class="keyword">not null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 5. 查询年龄不等于 88 的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="operator">!=</span> <span class="number">88</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 6. 查询年龄在15岁（包含）到20岁（包含）之间的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="operator">&gt;=</span> <span class="number">15</span> <span class="keyword">and</span> age <span class="operator">&lt;=</span> <span class="number">20</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="keyword">between</span> <span class="number">15</span> <span class="keyword">and</span> <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 7. 查询性别为 女 且年龄小于 25 岁的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> gender <span class="operator">=</span> <span class="string">&#x27;女&#x27;</span> <span class="keyword">and</span> age <span class="operator">&lt;</span> <span class="number">25</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 8. 查询年龄等于 18 或 20 或 40 的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="keyword">in</span>(<span class="number">18</span>,<span class="number">20</span>,<span class="number">40</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 9. 查询姓名为两个字的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> name <span class="keyword">like</span> <span class="string">&#x27;__&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 10. 查询身份证号最后一位是X的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> idCard <span class="keyword">like</span> <span class="string">&#x27;%X&#x27;</span>;</span><br></pre></td></tr></table></figure><p>注意：MySQL 中查询 idCard 为 NULL 的员工时，不能用 idCard &#x3D; null 或 idCard &#x3D;&#x3D; null，在 MySQL 中，<code>NULL</code>表示「未知值」，不是一个具体的数值 &#x2F; 字符串，因此<strong>普通的比较运算符（&#x3D;、&#x3D;&#x3D;、!&#x3D;、&lt;&gt;）无法和 NULL 进行有效比较</strong>—— 用这些运算符和 NULL 比较的结果既不是<code>TRUE</code>也不是<code>FALSE</code>，而是<code>NULL</code>（不成立），最终查不到任何数据。</p><h4 id="聚合查询（聚合函数）"><a href="#聚合查询（聚合函数）" class="headerlink" title="聚合查询（聚合函数）"></a>聚合查询（聚合函数）</h4><p><strong>(1) 常见聚合函数：</strong></p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>count</td><td>统计数量</td></tr><tr><td>max</td><td>最大值</td></tr><tr><td>min</td><td>最小值</td></tr><tr><td>avg</td><td>平均值</td></tr><tr><td>sum</td><td>求和</td></tr></tbody></table><p>(2) 语法：</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="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><p>(3) 代码示例：</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><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="comment">-- 1.统计该企业员工数量</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> emp;</span><br><span class="line"><span class="keyword">select</span> <span class="built_in">count</span>(id) <span class="keyword">from</span> emp;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2.统计该企业员工的平均年龄</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">avg</span>(age) <span class="keyword">from</span> emp;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3.统计该企业员工的最大年龄</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">max</span>(age) <span class="keyword">from</span> emp;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 4.统计西安地区员工的年龄之和</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">sum</span>(age) <span class="keyword">from</span> emp <span class="keyword">where</span> workaddress <span class="operator">=</span> <span class="string">&#x27;西安&#x27;</span>;</span><br></pre></td></tr></table></figure><p>注意：null 值不参与所有聚合函数运算</p><p><strong>关键逻辑：<code>count(\*)</code></strong></p><p>统计<strong>所有符合条件的行数</strong>（不管字段是否为 NULL），只要这一行存在，就会被计数，适合直接统计 “员工总数”。</p><h4 id="分组查询"><a href="#分组查询" class="headerlink" title="分组查询"></a>分组查询</h4><p>(1) 语法：</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="keyword">FROM</span> 表名 [ <span class="keyword">WHERE</span> 条件 ] <span class="keyword">GROUP</span> <span class="keyword">BY</span> 分组字段名 [ <span class="keyword">HAVING</span> 分组后的过滤条件 ];</span><br></pre></td></tr></table></figure><p>(2) where 和 having 的区别：</p><ul><li>执行时机不同：where是分组之前进行过滤，不满足where条件不参与分组；having是分组后对结果进行过滤。</li><li>判断条件不同：where不能对聚合函数进行判断，而having可以。</li></ul><p>(3) 代码示例：</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><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 1.根据性别分组，统计男性员工和女性员工的数量</span></span><br><span class="line"><span class="keyword">select</span> gender,<span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> emp <span class="keyword">group</span> <span class="keyword">by</span> gender;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2.根据性别分组，统计男性员工和女性员工的平均年龄</span></span><br><span class="line"><span class="keyword">select</span> gender,<span class="built_in">avg</span>(age) <span class="keyword">from</span> emp <span class="keyword">group</span> <span class="keyword">by</span> gender;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3.查询年龄小于45的员工，并根据工作地址分组，获取员工数量大于等于3的工作地址</span></span><br><span class="line"><span class="keyword">select</span> workaddress,<span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="operator">&lt;</span> <span class="number">45</span> <span class="keyword">group</span> <span class="keyword">by</span> workaddress <span class="keyword">having</span> <span class="built_in">count</span>(<span class="operator">*</span>) <span class="operator">&gt;=</span> <span class="number">3</span>;</span><br></pre></td></tr></table></figure><p>关键逻辑：</p><p>① <code>HAVING COUNT(*) &gt;= 3</code></p><p><strong>分组后的筛选</strong>：只保留 “统计人数 ≥ 3” 的分组 —— 比如西安分组只有 2 人，就会被筛掉；北京有 6 人、上海 4 人、江苏 3 人，都会保留。</p><p>→ 记住：<code>HAVING</code>只管 “分组后的统计结果”（比如 COUNT (*)），不管 “单个员工的字段”。</p><p>② <code>SELECT workaddress,count(*)</code></p><p><strong>最终展示</strong>：把筛选后的分组结果展示出来，列是 “工作地址” 和 “对应人数”。</p><p>(4) 注意事项</p><ul><li>执行顺序：where &gt; 聚合函数 &gt; having，因为 <code>WHERE</code>先筛行→<code>GROUP BY</code>分组→聚合函数统计→<code>HAVING</code>筛组</li><li>分组之后，查询的字段一般为聚合函数和分组字段，查询其他字段无任何意义（上述例子中，workaddress 是 分组标识，count(*) 是聚合函数）</li></ul><h4 id="排序查询"><a href="#排序查询" class="headerlink" title="排序查询"></a>排序查询</h4><p>(1) 语法：</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="keyword">FROM</span> 表名 <span class="keyword">ORDER</span> <span class="keyword">BY</span> 字段<span class="number">1</span> 排序方式<span class="number">1</span>, 字段<span class="number">2</span> 排序方式<span class="number">2</span>;</span><br></pre></td></tr></table></figure><p>(2) 排序方式：</p><ul><li>ASC: 升序（默认）</li><li>DESC: 降序</li></ul><p>(3) 代码示例：</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"><span class="comment">-- 1.根据年龄对公司的员工进行升序排序</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">order</span> <span class="keyword">by</span> age <span class="keyword">asc</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">order</span> <span class="keyword">by</span> age;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2.根据年龄对公司的员工进行升序排序，年龄相同，再按照入职时间进行降序排序</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">order</span> <span class="keyword">by</span> age <span class="keyword">asc</span>, entrydate <span class="keyword">desc</span>;</span><br></pre></td></tr></table></figure><p>(4) 注意：如果是多字段排序，当第一个字段值相同时，才会根据第二个字段进行排序</p><h4 id="分页查询"><a href="#分页查询" class="headerlink" title="分页查询"></a>分页查询</h4><p>(1) 语法：</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="keyword">FROM</span> 表名 LIMIT 起始索引, 查询记录数;</span><br></pre></td></tr></table></figure><p>(2) 注意：</p><ul><li>起始索引从0开始，起始索引 &#x3D; （查询页码 - 1） * 每页显示记录数</li><li>分页查询是数据库的方言，不同数据库有不同实现，MySQL是LIMIT</li><li>如果查询的是第一页数据，起始索引可以省略，直接简写 LIMIT 10</li></ul><p>(3) 代码示例：</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"><span class="comment">-- 1.查询第1页员工数据，每页展示10条记录</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp limit <span class="number">0</span>,<span class="number">10</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp limit <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2.查询第2条</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp limit <span class="number">10</span>,<span class="number">10</span>;</span><br></pre></td></tr></table></figure><h4 id="综合练习"><a href="#综合练习" class="headerlink" title="综合练习"></a>综合练习</h4><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><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="comment">-- 1.查询年龄为20，21，22，23岁的女性员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> gender <span class="operator">=</span> <span class="string">&#x27;女&#x27;</span> <span class="keyword">and</span> age <span class="keyword">in</span> (<span class="number">20</span>,<span class="number">21</span>,<span class="number">22</span>,<span class="number">23</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2.统计员工表中，年龄小于60岁的，男性员工和女性员工的人数</span></span><br><span class="line"><span class="keyword">select</span> gender, <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="operator">&lt;</span> <span class="number">60</span> <span class="keyword">group</span> <span class="keyword">by</span> gender</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3.查询所有年龄小于等于35岁员工的姓名和年龄，并对查询结果按年龄升序排序，如果年龄相同按入职时间降序排序</span></span><br><span class="line"><span class="keyword">select</span> name,age <span class="keyword">from</span> emp <span class="keyword">where</span> age <span class="operator">&lt;=</span> <span class="number">35</span> <span class="keyword">order</span> <span class="keyword">by</span> age <span class="keyword">asc</span>, entrydate <span class="keyword">desc</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 4.查询性别为男，且年龄在20-40岁（含）以内的前5个员工信息，对查询的结果按年龄升序排序，年龄相同按入职时间升序时间</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> gender <span class="operator">=</span> <span class="string">&#x27;男&#x27;</span> <span class="keyword">and</span> age <span class="keyword">between</span> <span class="number">20</span> <span class="keyword">and</span> <span class="number">40</span> <span class="keyword">order</span> <span class="keyword">by</span> age <span class="keyword">asc</span>, entrydate <span class="keyword">desc</span> limit <span class="number">5</span>;</span><br></pre></td></tr></table></figure><h4 id="DQL-编写顺序"><a href="#DQL-编写顺序" class="headerlink" title="DQL 编写顺序"></a>DQL 编写顺序</h4><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="number">1.</span> <span class="keyword">FROM</span> → <span class="number">2.</span> <span class="keyword">WHERE</span> → <span class="number">3.</span> <span class="keyword">GROUP</span> <span class="keyword">BY</span> → <span class="number">4.</span> <span class="keyword">HAVING</span> → <span class="number">5.</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> → <span class="number">6.</span> LIMIT</span><br></pre></td></tr></table></figure><h4 id="DQL执行顺序"><a href="#DQL执行顺序" class="headerlink" title="DQL执行顺序"></a>DQL执行顺序</h4><p>FROM -&gt; WHERE -&gt; GROUP BY、HAVING -&gt; SELECT -&gt; ORDER BY -&gt; LIMIT</p><h3 id="DCL"><a href="#DCL" class="headerlink" title="DCL"></a>DCL</h3><h4 id="管理用户"><a href="#管理用户" class="headerlink" title="管理用户"></a>管理用户</h4><p>(1) 查询用户：</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">USE mysql;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span>;</span><br></pre></td></tr></table></figure><p>(2) 创建用户:</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="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span> IDENTIFIED <span class="keyword">BY</span> <span class="string">&#x27;密码&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(3) 修改用户密码：</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">ALTER</span> <span class="keyword">USER</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span> IDENTIFIED <span class="keyword">WITH</span> mysql_native_password <span class="keyword">BY</span> <span class="string">&#x27;新密码&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(4) 删除用户：</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">DROP</span> <span class="keyword">USER</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(5) 代码示例：</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><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建用户test，只能在当前主机localhost访问</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">user</span> <span class="string">&#x27;test&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span> identified <span class="keyword">by</span> <span class="string">&#x27;123456&#x27;</span>;</span><br><span class="line"><span class="comment">-- 创建用户test，能在任意主机访问</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">user</span> <span class="string">&#x27;test&#x27;</span>@<span class="string">&#x27;%&#x27;</span> identified <span class="keyword">by</span> <span class="string">&#x27;123456&#x27;</span>;</span><br><span class="line"><span class="keyword">create</span> <span class="keyword">user</span> <span class="string">&#x27;test&#x27;</span> identified <span class="keyword">by</span> <span class="string">&#x27;123456&#x27;</span>;</span><br><span class="line"><span class="comment">-- 修改密码</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">user</span> <span class="string">&#x27;test&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span> identified <span class="keyword">with</span> mysql_native_password <span class="keyword">by</span> <span class="string">&#x27;1234&#x27;</span>;</span><br><span class="line"><span class="comment">-- 删除用户</span></span><br><span class="line"><span class="keyword">drop</span> <span class="keyword">user</span> <span class="string">&#x27;test&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span>;</span><br></pre></td></tr></table></figure><h4 id="权限控制"><a href="#权限控制" class="headerlink" title="权限控制"></a>权限控制</h4><p>(1) 常用权限：</p><table><thead><tr><th>权限</th><th>说明</th></tr></thead><tbody><tr><td>ALL, ALL PRIVILEGES</td><td>所有权限</td></tr><tr><td>SELECT</td><td>查询数据</td></tr><tr><td>INSERT</td><td>插入数据</td></tr><tr><td>UPDATE</td><td>修改数据</td></tr><tr><td>DELETE</td><td>删除数据</td></tr><tr><td>ALTER</td><td>修改表</td></tr><tr><td>DROP</td><td>删除数据库&#x2F;表&#x2F;视图</td></tr><tr><td>CREATE</td><td>创建数据库&#x2F;表</td></tr></tbody></table><p>(2) 查询权限：</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">SHOW</span> GRANTS <span class="keyword">FOR</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(3) 授予权限：</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">GRANT</span> 权限列表 <span class="keyword">ON</span> 数据库名.表名 <span class="keyword">TO</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(4) 撤销权限：</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">REVOKE</span> 权限列表 <span class="keyword">ON</span> 数据库名.表名 <span class="keyword">FROM</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span>;</span><br></pre></td></tr></table></figure><p>(5) 注意事项</p><ul><li>多个权限用逗号分隔</li><li>授权时，数据库名和表名可以用 * 进行通配，代表所有</li></ul><h3 id="实用技巧"><a href="#实用技巧" class="headerlink" title="实用技巧"></a>实用技巧</h3><h4 id="DataGrip-快速执行单行-SQL-的方法"><a href="#DataGrip-快速执行单行-SQL-的方法" class="headerlink" title="DataGrip 快速执行单行 SQL 的方法"></a>DataGrip 快速执行单行 SQL 的方法</h4><p>使用 <strong>Ctrl+Enter</strong>（Windows&#x2F;Linux），<strong>无需选中整行</strong>，只需将<strong>光标定位在目标行</strong>，按下此快捷键，DataGrip 会自动执行该行完整 SQL 语句。</p><h4 id="换行技巧（无需移到行尾）"><a href="#换行技巧（无需移到行尾）" class="headerlink" title="换行技巧（无需移到行尾）"></a>换行技巧（无需移到行尾）</h4><p>Windows&#x2F;Linux：<code>Shift + Enter</code></p><h4 id="单行注释（最常用）"><a href="#单行注释（最常用）" class="headerlink" title="单行注释（最常用）"></a>单行注释（最常用）</h4><p>用 <code>--</code>（两个减号）开头，后面跟注释内容，注释范围从<code>--</code>到行尾。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 这是单行注释（整行注释）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> employee; <span class="comment">-- 这是行尾注释（注释该行后面的内容）</span></span><br><span class="line"><span class="comment">-- INSERT INTO employee (id, name) VALUES (2, &#x27;李四&#x27;); -- 注释掉整行执行语句</span></span><br></pre></td></tr></table></figure><h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><ul><li>字符串函数</li><li>数值函数</li><li>日期函数</li><li>流程函数</li></ul><h4 id="字符串函数"><a href="#字符串函数" class="headerlink" title="字符串函数"></a>字符串函数</h4><p>(1) 常用函数：</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>CONCAT(s1, s2, …, sn)</td><td>字符串拼接，将s1, s2, …, sn拼接成一个字符串</td></tr><tr><td>LOWER(str)</td><td>将字符串全部转为小写</td></tr><tr><td>UPPER(str)</td><td>将字符串全部转为大写</td></tr><tr><td>LPAD(str, n, pad)</td><td>左填充，用字符串pad对str的左边进行填充，达到n个字符串长度</td></tr><tr><td>RPAD(str, n, pad)</td><td>右填充，用字符串pad对str的右边进行填充，达到n个字符串长度</td></tr><tr><td>TRIM(str)</td><td>去掉字符串头部和尾部的空格</td></tr><tr><td>SUBSTRING(str, start, len)</td><td>返回从字符串str从start位置起的len个长度的字符串</td></tr><tr><td>REPLACE(column, source, replace)</td><td>替换字符串</td></tr></tbody></table><p>(2) 使用示例：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> concat(<span class="string">&#x27;Hello&#x27;</span>,<span class="string">&#x27;MySQL&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">lower</span>(<span class="string">&#x27;Hello&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">upper</span>(<span class="string">&#x27;Hello&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> lpad(<span class="string">&#x27;01&#x27;</span>,<span class="number">5</span>,<span class="string">&#x27;-&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> rpad(<span class="string">&#x27;01&#x27;</span>,<span class="number">5</span>,<span class="string">&#x27;-&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">trim</span>(<span class="string">&#x27; Hello MySQL &#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">substring</span>(<span class="string">&#x27;Hello MySQL&#x27;</span>,<span class="number">1</span>,<span class="number">5</span>);</span><br></pre></td></tr></table></figure><h4 id="数值函数"><a href="#数值函数" class="headerlink" title="数值函数"></a>数值函数</h4><p>(1) 常见函数：</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>CEIL(x)</td><td>向上取整</td></tr><tr><td>FLOOR(x)</td><td>向下取整</td></tr><tr><td>MOD(x, y)</td><td>返回x&#x2F;y的模</td></tr><tr><td>RAND()</td><td>返回0~1内的随机数</td></tr><tr><td>ROUND(x, y)</td><td>求参数x的四舍五入值，保留y位小数</td></tr></tbody></table><p>(2) 代码示例：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="built_in">ceil</span>(<span class="number">1.5</span>);</span><br><span class="line"><span class="keyword">select</span> <span class="built_in">ceil</span>(<span class="number">1.1</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">floor</span> (<span class="number">1.9</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">mod</span>(<span class="number">3</span>,<span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> rand();</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> round(<span class="number">2.345</span>,<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 案例：通过数据库的函数，生成一个六位数的随机验证码</span></span><br><span class="line"><span class="keyword">select</span> lpad(round(rand() <span class="operator">*</span> <span class="number">1000000</span>,<span class="number">0</span>),<span class="number">6</span>,<span class="string">&#x27;0&#x27;</span>);</span><br></pre></td></tr></table></figure><h4 id="日期函数"><a href="#日期函数" class="headerlink" title="日期函数"></a>日期函数</h4><p>(1) 常用函数：</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>CURDATE()</td><td>返回当前日期</td></tr><tr><td>CURTIME()</td><td>返回当前时间</td></tr><tr><td>NOW()</td><td>返回当前日期和时间</td></tr><tr><td>YEAR(date)</td><td>获取指定date的年份</td></tr><tr><td>MONTH(date)</td><td>获取指定date的月份</td></tr><tr><td>DAY(date)</td><td>获取指定date的日期</td></tr><tr><td>DATE_ADD(date, INTERVAL expr type)</td><td>返回一个日期&#x2F;时间值加上一个时间间隔expr后的时间值</td></tr><tr><td>DATEDIFF(date1, date2)</td><td>返回起始时间date1和结束时间date2之间的天数</td></tr></tbody></table><p>(2) 代码示例：</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><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="keyword">select</span> curdate();</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> curtime();</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> now();</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">YEAR</span>(now());</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">MONTH</span>(now());</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">DAY</span>(now());</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> date_add(now(),<span class="type">INTERVAL</span> <span class="number">70</span> <span class="keyword">DAY</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> datediff(<span class="string">&#x27;2021-12-21&#x27;</span>,<span class="string">&#x27;2021-12-02&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 案例：查询所有员工的入职天数，并根据入职天数倒序排序</span></span><br><span class="line"><span class="keyword">select</span> name, datediff(curdate(), entrydate) <span class="keyword">as</span> <span class="string">&#x27;entrydays&#x27;</span> <span class="keyword">from</span> emp <span class="keyword">order</span> <span class="keyword">by</span> entrydays <span class="keyword">desc</span>;</span><br></pre></td></tr></table></figure><h4 id="流程函数"><a href="#流程函数" class="headerlink" title="流程函数"></a>流程函数</h4><p>(1) 常用函数：</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>IF(value, t, f)</td><td>如果value为true，则返回t，否则返回f</td></tr><tr><td>IFNULL(value1, value2)</td><td>如果value1不为空，返回value1，否则返回value2</td></tr><tr><td>CASE WHEN [ val1 ] THEN [ res1 ] … ELSE [ default ] END</td><td>如果val1为true，返回res1，… 否则返回default默认值</td></tr><tr><td>CASE [ expr ] WHEN [ val1 ] THEN [ res1 ] … ELSE [ default ] END</td><td>如果expr的值等于val1，返回res1，… 否则返回default默认值</td></tr></tbody></table><p>(2) 代码示例：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> if(<span class="literal">true</span>,<span class="string">&#x27;OK&#x27;</span>,<span class="string">&#x27;Error&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> ifnull(<span class="string">&#x27;OK&#x27;</span>,<span class="string">&#x27;Default&#x27;</span>);</span><br><span class="line"><span class="keyword">select</span> ifnull(<span class="keyword">null</span>,<span class="string">&#x27;Default&#x27;</span>);</span><br><span class="line"><span class="keyword">select</span> ifnull(<span class="string">&#x27;&#x27;</span>,<span class="string">&#x27;Default&#x27;</span>);</span><br></pre></td></tr></table></figure><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">select</span></span><br><span class="line">    id,</span><br><span class="line">    name,</span><br><span class="line">    (<span class="keyword">case</span> <span class="keyword">when</span> math <span class="operator">&gt;=</span> <span class="number">85</span> <span class="keyword">then</span> <span class="string">&#x27;优秀&#x27;</span> <span class="keyword">when</span> math <span class="operator">&gt;=</span> <span class="number">60</span> <span class="keyword">then</span> <span class="string">&#x27;及格&#x27;</span> <span class="keyword">else</span> <span class="string">&#x27;不及格&#x27;</span> <span class="keyword">end</span>) <span class="keyword">as</span> <span class="string">&#x27;数学&#x27;</span>,</span><br><span class="line">    (<span class="keyword">case</span> english <span class="keyword">when</span> english <span class="operator">&gt;=</span> <span class="number">85</span> <span class="keyword">then</span> <span class="string">&#x27;优秀&#x27;</span> <span class="keyword">when</span> english <span class="operator">&gt;=</span> <span class="number">60</span> <span class="keyword">then</span> <span class="string">&#x27;及格&#x27;</span> <span class="keyword">else</span> <span class="string">&#x27;不及格&#x27;</span> <span class="keyword">end</span>) <span class="keyword">as</span> <span class="string">&#x27;英语&#x27;</span>,</span><br><span class="line">    (<span class="keyword">case</span> chinese <span class="keyword">when</span> chinese <span class="operator">&gt;=</span><span class="number">85</span> <span class="keyword">then</span> <span class="string">&#x27;优秀&#x27;</span> <span class="keyword">when</span> chinese <span class="operator">&gt;=</span> <span class="number">60</span> <span class="keyword">then</span> <span class="string">&#x27;及格&#x27;</span> <span class="keyword">else</span> <span class="string">&#x27;不及格&#x27;</span> <span class="keyword">end</span>) <span class="keyword">as</span> <span class="string">&#x27;语文&#x27;</span></span><br><span class="line"><span class="keyword">from</span> score;</span><br></pre></td></tr></table></figure><h3 id="约束"><a href="#约束" class="headerlink" title="约束"></a>约束</h3><h4 id="分类："><a href="#分类：" class="headerlink" title="分类："></a>分类：</h4><table><thead><tr><th>约束</th><th>描述</th><th>关键字</th></tr></thead><tbody><tr><td>非空约束</td><td>限制该字段的数据不能为null</td><td>NOT NULL</td></tr><tr><td>唯一约束</td><td>保证该字段的所有数据都是唯一、不重复的</td><td>UNIQUE</td></tr><tr><td>主键约束</td><td>主键是一行数据的唯一标识，要求非空且唯一</td><td>PRIMARY KEY</td></tr><tr><td>默认约束</td><td>保存数据时，如果未指定该字段的值，则采用默认值</td><td>DEFAULT</td></tr><tr><td>检查约束（8.0.1版本后）</td><td>保证字段值满足某一个条件</td><td>CHECK</td></tr><tr><td>外键约束</td><td>用来让两张图的数据之间建立连接，保证数据的一致性和完整性</td><td>FOREIGN KEY</td></tr></tbody></table><p>约束是作用于表中字段上的，可以再创建表&#x2F;修改表的时候添加约束。</p><h4 id="常用约束"><a href="#常用约束" class="headerlink" title="常用约束"></a>常用约束</h4><table><thead><tr><th>约束条件</th><th>关键字</th></tr></thead><tbody><tr><td>主键</td><td>PRIMARY KEY</td></tr><tr><td>自动增长</td><td>AUTO_INCREMENT</td></tr><tr><td>不为空</td><td>NOT NULL</td></tr><tr><td>唯一</td><td>UNIQUE</td></tr><tr><td>逻辑条件</td><td>CHECK</td></tr><tr><td>默认值</td><td>DEFAULT</td></tr></tbody></table><p>代码示例：</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 table</span> <span class="keyword">user</span>(</span><br><span class="line">id <span class="type">int</span> <span class="keyword">primary key</span> auto_increment,</span><br><span class="line">name <span class="type">varchar</span>(<span class="number">10</span>) <span class="keyword">not null</span> <span class="keyword">unique</span>,</span><br><span class="line">age <span class="type">int</span> <span class="keyword">check</span>(age <span class="operator">&gt;</span> <span class="number">0</span> <span class="keyword">and</span> age <span class="operator">&lt;</span> <span class="number">120</span>),</span><br><span class="line">status <span class="type">char</span>(<span class="number">1</span>) <span class="keyword">default</span> <span class="string">&#x27;1&#x27;</span>,</span><br><span class="line">gender <span class="type">char</span>(<span class="number">1</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h4 id="外键约束"><a href="#外键约束" class="headerlink" title="外键约束"></a>外键约束</h4><p>(1) 添加外键</p><p>方法一：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> 表名(</span><br><span class="line">字段名 字段类型,</span><br><span class="line">...</span><br><span class="line">[<span class="keyword">CONSTRAINT</span>] [外键名称] <span class="keyword">FOREIGN KEY</span>(外键字段名) <span class="keyword">REFERENCES</span> 主表(主表列名)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>方法二：</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">ALTER TABLE</span> 表名 <span class="keyword">ADD CONSTRAINT</span> 外键名称 <span class="keyword">FOREIGN KEY</span> (外键字段名) <span class="keyword">REFERENCES</span> 主表(主表列名);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 例子</span></span><br><span class="line"><span class="keyword">alter table</span> emp <span class="keyword">add constraint</span> fk_emp_dept_id <span class="keyword">foreign key</span>(dept_id) <span class="keyword">references</span> dept(id);</span><br></pre></td></tr></table></figure><p>(2) 删除外键：</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">ALTER TABLE</span> 表名 <span class="keyword">DROP</span> <span class="keyword">FOREIGN KEY</span> 外键名;</span><br></pre></td></tr></table></figure><p>(2) 删除&#x2F;更新行为</p><table><thead><tr><th>行为</th><th>说明</th></tr></thead><tbody><tr><td>NO ACTION</td><td>当在父表中删除&#x2F;更新对应记录时，首先检查该记录是否有对应外键，如果有则不允许删除&#x2F;更新（与RESTRICT一致）</td></tr><tr><td>RESTRICT</td><td>当在父表中删除&#x2F;更新对应记录时，首先检查该记录是否有对应外键，如果有则不允许删除&#x2F;更新（与NO ACTION一致）</td></tr><tr><td>CASCADE</td><td>当在父表中删除&#x2F;更新对应记录时，首先检查该记录是否有对应外键，如果有则也删除&#x2F;更新外键在子表中的记录</td></tr><tr><td>SET NULL</td><td>当在父表中删除&#x2F;更新对应记录时，首先检查该记录是否有对应外键，如果有则设置子表中该外键值为null（要求该外键允许为null）</td></tr><tr><td>SET DEFAULT（不重要）</td><td>父表有变更时，子表将外键设为一个默认值（Innodb不支持）</td></tr></tbody></table><p>(3) 更改删除&#x2F;更新行为：</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">ALTER TABLE</span> 表名 <span class="keyword">ADD CONSTRAINT</span> 外键名称 <span class="keyword">FOREIGN KEY</span> (外键字段) <span class="keyword">REFERENCES</span> 主表名(主表字段名) <span class="keyword">ON</span> <span class="keyword">UPDATE</span> 行为 <span class="keyword">ON</span> <span class="keyword">DELETE</span> 行为;</span><br></pre></td></tr></table></figure><h3 id="多表查询"><a href="#多表查询" class="headerlink" title="多表查询"></a>多表查询</h3><h4 id="多表关系"><a href="#多表关系" class="headerlink" title="多表关系"></a>多表关系</h4><ul><li>一对多（多对一）</li><li>多对多</li><li>一对一</li></ul><p>(1) 一对多</p><p>案例：部门与员工<br> 关系：一个部门对应多个员工，一个员工对应一个部门<br> 实现：在多的一方建立外键，指向一的一方的主键</p><p>(2) 多对多</p><p>案例：学生与课程<br> 关系：一个学生可以选多门课程，一门课程也可以供多个学生选修<br> 实现：建立第三张中间表，中间表至少包含两个外键，分别关联两方主键</p><p>(3) 一对一</p><p>案例：用户与用户详情<br> 关系：一对一关系，多用于单表拆分，将一张表的基础字段放在一张表中，其他详情字段放在另一张表中，以提升操作效率<br> 实现：在任意一方加入外键，关联另外一方的主键，并且设置外键为唯一的（UNIQUE）</p><h4 id="查询"><a href="#查询" class="headerlink" title="查询"></a>查询</h4><p>(1) 合并查询（笛卡尔积，会展示所有组合结果）：</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> employee, dept;</span><br></pre></td></tr></table></figure><ul><li>笛卡尔积：两个集合A集合和B集合的所有组合情况（在多表查询时，需要消除无效的笛卡尔积）</li></ul><p>(2) 消除无效笛卡尔积：</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> emp, dept <span class="keyword">where</span> dept_id <span class="operator">=</span> dept.id;</span><br></pre></td></tr></table></figure><p>(3) 内连接查询</p><p>① 内连接查询的是两张表交集的部分，<strong>即如图的绿色部分</strong></p><img src="https://i-blog.csdnimg.cn/direct/c68b466aed214bc39ff94a54c716c554.png" alt="img" style="zoom:67%;" /><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">SELECT</span> 字段列表 <span class="keyword">FROM</span> 表<span class="number">1</span>, 表<span class="number">2</span> <span class="keyword">WHERE</span> 条件 ...;</span><br></pre></td></tr></table></figure><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">SELECT</span> 字段列表 <span class="keyword">FROM</span> 表<span class="number">1</span> [ <span class="keyword">INNER</span> ] <span class="keyword">JOIN</span> 表<span class="number">2</span> <span class="keyword">ON</span> 连接条件 ...;</span><br></pre></td></tr></table></figure><p>④ 代码示例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 隐式内连接</span></span><br><span class="line"><span class="keyword">select</span> e.name, d.name <span class="keyword">from</span> emp e, dept d <span class="keyword">where</span> e.dept_id <span class="operator">=</span> d.id;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 显式内连接</span></span><br><span class="line"><span class="keyword">select</span> e.name, d.name <span class="keyword">from</span> emp e <span class="keyword">inner</span> <span class="keyword">join</span> dept d <span class="keyword">on</span> e.dept_id <span class="operator">=</span> d.id;</span><br></pre></td></tr></table></figure><p>(4) 外连接查询</p><p>① 左外连接：查询左表所有数据，以及两张表交集部分数据（相当于蓝色部分 + 绿色部分），相当于查询表1的所有数据，包含表1和表2交集部分数据</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="keyword">FROM</span> 表<span class="number">1</span> <span class="keyword">LEFT</span> [ <span class="keyword">OUTER</span> ] <span class="keyword">JOIN</span> 表<span class="number">2</span> <span class="keyword">ON</span> 条件 ...;</span><br></pre></td></tr></table></figure><img src="https://i-blog.csdnimg.cn/direct/c68b466aed214bc39ff94a54c716c554.png" alt="img" style="zoom: 67%;" /><p>② 右外连接：查询右表所有数据，以及两张表交集部分数据，相当于查询表 2 的所有数据，同时包含表 1 与表 2 交集部分数据</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="keyword">FROM</span> 表<span class="number">1</span> <span class="keyword">RIGHT</span> [ <span class="keyword">OUTER</span> ] <span class="keyword">JOIN</span> 表<span class="number">2</span> <span class="keyword">ON</span> 条件 ...;</span><br></pre></td></tr></table></figure><p>③ 代码示例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 左</span></span><br><span class="line"><span class="keyword">select</span> e.<span class="operator">*</span>, d.name <span class="keyword">from</span> employee <span class="keyword">as</span> e <span class="keyword">left</span> <span class="keyword">outer</span> <span class="keyword">join</span> dept <span class="keyword">as</span> d <span class="keyword">on</span> e.dept <span class="operator">=</span> d.id;</span><br><span class="line"><span class="keyword">select</span> d.name, e.<span class="operator">*</span> <span class="keyword">from</span> dept d <span class="keyword">left</span> <span class="keyword">outer</span> <span class="keyword">join</span> emp e <span class="keyword">on</span> e.dept <span class="operator">=</span> d.id;  <span class="comment">-- 这条语句与下面的语句效果一样</span></span><br><span class="line"><span class="comment">-- 右</span></span><br><span class="line"><span class="keyword">select</span> d.name, e.<span class="operator">*</span> <span class="keyword">from</span> employee <span class="keyword">as</span> e <span class="keyword">right</span> <span class="keyword">outer</span> <span class="keyword">join</span> dept <span class="keyword">as</span> d <span class="keyword">on</span> e.dept <span class="operator">=</span> d.id;</span><br></pre></td></tr></table></figure><p><strong>关键逻辑：</strong></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> d.name, e.<span class="operator">*</span> <span class="keyword">from</span> dept d <span class="keyword">left</span> <span class="keyword">outer</span> <span class="keyword">join</span> emp e <span class="keyword">on</span> e.dept <span class="operator">=</span> d.id;</span><br></pre></td></tr></table></figure><ul><li>表顺序：<code>dept d</code>（左表） ← LEFT JOIN → <code>emp e</code>（右表）</li><li>连接类型：左连接 → 保 “左表（dept）” 的所有行</li><li>逻辑：返回<code>dept</code>的所有部门（哪怕这个部门没有员工），同时匹配<code>emp</code>中该部门的员工数据</li></ul><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> d.name, e.<span class="operator">*</span> <span class="keyword">from</span> employee <span class="keyword">as</span> e <span class="keyword">right</span> <span class="keyword">outer</span> <span class="keyword">join</span> dept <span class="keyword">as</span> d <span class="keyword">on</span> e.dept <span class="operator">=</span> d.id;</span><br></pre></td></tr></table></figure><ul><li>表顺序：<code>employee e</code>（左表） ← RIGHT JOIN → <code>dept d</code>（右表）</li><li>连接类型：右连接 → 保 “右表（dept）” 的所有行</li><li>逻辑：返回<code>dept</code>的所有部门（哪怕这个部门没有员工），同时匹配<code>emp</code>中该部门的员工数据</li></ul><p>(5) 自连接查询</p><p>① 当前表与自身的连接查询，自连接必须使用表别名</p><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">SELECT</span> 字段列表 <span class="keyword">FROM</span> 表A 别名A <span class="keyword">JOIN</span> 表A 别名B <span class="keyword">ON</span> 条件 ...;</span><br></pre></td></tr></table></figure><p>自连接查询，可以是内连接查询，也可以是外连接查询</p><p>③ 代码示例：</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="comment">-- 查询员工及其所属领导的名字</span></span><br><span class="line"><span class="keyword">select</span> a.name, b.name <span class="keyword">from</span> employee a, employee b <span class="keyword">where</span> a.manager <span class="operator">=</span> b.id;</span><br><span class="line"><span class="comment">-- 没有领导的也查询出来</span></span><br><span class="line"><span class="keyword">select</span> a.name, b.name <span class="keyword">from</span> employee a <span class="keyword">left</span> <span class="keyword">join</span> employee b <span class="keyword">on</span> a.manager <span class="operator">=</span> b.id;</span><br></pre></td></tr></table></figure><p>(6) 联合查询 union, union all</p><p>① 把多次查询的结果合并，形成一个新的查询集</p><p>② 语法：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> 字段列表 <span class="keyword">FROM</span> 表A ...</span><br><span class="line"><span class="keyword">UNION</span> [<span class="keyword">ALL</span>]</span><br><span class="line"><span class="keyword">SELECT</span> 字段列表 <span class="keyword">FROM</span> 表B ...</span><br></pre></td></tr></table></figure><p>③ 注意事项</p><ul><li>UNION ALL 会有重复结果，UNION 不会</li><li>联合查询比使用or效率高，不会使索引失效</li></ul><h4 id="子查询"><a href="#子查询" class="headerlink" title="子查询"></a>子查询</h4><p>(1) SQL语句中嵌套SELECT语句，称谓嵌套查询，又称子查询。</p><p>(2) 语法：</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> t1 <span class="keyword">WHERE</span> column1 <span class="operator">=</span> ( <span class="keyword">SELECT</span> column1 <span class="keyword">FROM</span> t2);</span><br></pre></td></tr></table></figure><p><strong>子查询外部的语句可以是 INSERT &#x2F; UPDATE &#x2F; DELETE &#x2F; SELECT 的任何一个</strong></p><p>③ 根据子查询结果可以分为：</p><ul><li>标量子查询（子查询结果为单个值）</li><li>列子查询（子查询结果为一列）</li><li>行子查询（子查询结果为一行）</li><li>表子查询（子查询结果为多行多列）</li></ul><p>④ 根据子查询位置可分为：</p><ul><li>WHERE 之后</li><li>FROM 之后</li><li>SELECT 之后</li></ul><p>(3) 标量子查询</p><p>① 子查询返回的结果是单个值（数字、字符串、日期等）。</p><p>② 常用操作符：-、&lt;&gt;、&gt;、&gt;&#x3D;、&lt;、&lt;&#x3D;</p><p>**注意：**在 MySQL 中，<code>&lt;&gt;</code>是 “不等于” 的比较操作符，用于判断两个值是否不相等，等价于<code>!=</code>（二者是完全相同的含义）</p><p>③ 代码示例：</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"><span class="keyword">select</span> id <span class="keyword">from</span> dept <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;销售部&#x27;</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> dept_id <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> dept_id <span class="operator">=</span> (<span class="keyword">select</span> id <span class="keyword">from</span> dept <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;销售部&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询在 “方东白” 入职之后的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> entrydate <span class="operator">&gt;</span> (<span class="keyword">select</span> entrydate <span class="keyword">from</span> emp <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;方东白&#x27;</span>);</span><br></pre></td></tr></table></figure><p>(4) 列子查询</p><p>① 返回的结果是一列（可以是多行）。</p><p>② 常用操作符：</p><table><thead><tr><th>操作符</th><th>描述</th></tr></thead><tbody><tr><td>IN</td><td>在指定的集合范围内，多选一</td></tr><tr><td>NOT IN</td><td>不在指定的集合范围内</td></tr><tr><td>ANY</td><td>子查询返回列表中，有任意一个满足即可</td></tr><tr><td>SOME</td><td>与ANY等同，使用SOME的地方都可以使用ANY</td></tr><tr><td>ALL</td><td>子查询返回列表的所有值都必须满足</td></tr></tbody></table><p>③ 代码示例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查询财务部所有人的工资</span></span><br><span class="line"><span class="keyword">select</span> salary <span class="keyword">from</span> emp <span class="keyword">where</span> dept_id <span class="operator">=</span> (<span class="keyword">select</span> id <span class="keyword">from</span> dept <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;财务部&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询比财务部所有人工资都高的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> salary <span class="operator">&gt;</span> <span class="keyword">all</span> (<span class="keyword">select</span> salary <span class="keyword">from</span> emp <span class="keyword">where</span> dept_id <span class="operator">=</span> (<span class="keyword">select</span> id <span class="keyword">from</span> dept <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;财务部&#x27;</span>));</span><br></pre></td></tr></table></figure><p>(5) 行子查询</p><p>① 返回的结果是一行（可以是多列）。</p><p>② 常用操作符：&#x3D;, &lt;, &gt;, IN, NOT IN</p><p>③ 代码示例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查询与xxx的薪资及直属领导相同的员工信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> employee <span class="keyword">where</span> (salary, manager) <span class="operator">=</span> (<span class="number">12500</span>, <span class="number">1</span>);</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> employee <span class="keyword">where</span> (salary, manager) <span class="operator">=</span> (<span class="keyword">select</span> salary, manager <span class="keyword">from</span> employee <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;xxx&#x27;</span>);</span><br></pre></td></tr></table></figure><p>(6) 表子查询</p><p>① 返回的结果是多行多列</p><p>② 常用操作符：IN</p><p>③ 代码示例：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查询与 “鹿杖客”，“宋远桥” 的职位和薪资相同的员工信息</span></span><br><span class="line"><span class="keyword">select</span> job, salary <span class="keyword">from</span> emp <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;鹿杖客&#x27;</span> <span class="keyword">or</span> name <span class="operator">=</span> <span class="string">&#x27;宋远桥&#x27;</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> (job, salary) <span class="keyword">in</span> (<span class="keyword">select</span> job, salary <span class="keyword">from</span> emp <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;鹿杖客&#x27;</span> <span class="keyword">or</span> name <span class="operator">=</span> <span class="string">&#x27;宋远桥&#x27;</span>);</span><br></pre></td></tr></table></figure><p><strong>关键逻辑：重点理解外面的「主查询」</strong></p><p>遍历<code>emp</code>表的每一个员工，取出他的<code>job</code>和<code>salary</code>组成一个 “组合对”，看这个组合对是否在子查询的 “模板列表” 里（也就是是否是<code>(职员,3750)</code>或<code>(销售,4600)</code>），如果在，就把这个员工的所有信息查出来。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查询入职日期是 “2006-01-01” 之后的员工信息及其部门信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> entrydate <span class="operator">&gt;</span> <span class="string">&#x27;2006-01-01&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询这部分员工，对应的部门信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> (<span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> emp <span class="keyword">where</span> entrydate <span class="operator">&gt;</span> <span class="string">&#x27;2006-01-01&#x27;</span>) e <span class="keyword">left</span> <span class="keyword">join</span> dept d <span class="keyword">on</span> e.dept_id <span class="operator">=</span> d.id;</span><br></pre></td></tr></table></figure><h4 id="综合练习-1"><a href="#综合练习-1" class="headerlink" title="综合练习"></a>综合练习</h4><p>(1) 查询年龄小于30岁的员工的姓名、年龄、职位、部门信息（显式内连接）</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> e.name, e.age, e.job, d.name <span class="keyword">from</span> emp e <span class="keyword">inner</span> <span class="keyword">join</span> dept d <span class="keyword">on</span> e.dept_id <span class="operator">=</span> d.id <span class="keyword">where</span> e.age <span class="operator">&lt;</span> <span class="number">30</span>;</span><br></pre></td></tr></table></figure><p>关键逻辑：显式内连接（<code>INNER JOIN</code>）的<strong>标准语法结构</strong>是：</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><br><span class="line"><span class="keyword">FROM</span> 表<span class="number">1</span> 别名<span class="number">1</span></span><br><span class="line"><span class="keyword">JOIN</span> 表<span class="number">2</span> 别名<span class="number">2</span> <span class="keyword">ON</span> 表<span class="number">1</span>和表<span class="number">2</span>的连接条件  <span class="comment">-- ON是JOIN子句的核心部分，必须紧跟JOIN</span></span><br><span class="line"><span class="keyword">WHERE</span> 筛选条件;  <span class="comment">-- WHERE是全局筛选，在JOIN（含ON）之后</span></span><br></pre></td></tr></table></figure><p>① <code>ON</code>是<code>JOIN</code>的 “配套条件”—— 数据库要求，只要写<code>JOIN</code>关联两张表，就必须用<code>ON</code>指定 “两张表怎么关联”，所以<code>ON</code>必须紧跟在<code>JOIN 表2</code>之后；</p><p>② <code>WHERE</code>是对 “关联后的结果集” 做筛选，只能放在最后。</p><p>(2) 查询拥有员工的部门ID、部门名称</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="keyword">distinct</span> d.id, d.name <span class="keyword">from</span> emp e, dept d <span class="keyword">where</span> e.dept_id <span class="operator">=</span> d.id;</span><br></pre></td></tr></table></figure><p>易错点：忘记加 distinct</p><p>(3) 查询低于本部门平均工资的员工信息</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> emp e2 <span class="keyword">where</span> e2.salary <span class="operator">&lt;</span> (<span class="keyword">select</span> <span class="built_in">avg</span>(e1.salary) <span class="keyword">from</span> emp e1 <span class="keyword">where</span> e1.dept_id <span class="operator">=</span> e2.dept_id);</span><br></pre></td></tr></table></figure><p><strong>关键逻辑：</strong></p><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">select</span> <span class="built_in">avg</span>(e1.salary) <span class="keyword">from</span> emp e1 <span class="keyword">where</span> e1.dept_id <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><h3 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h3><p>事务是一组操作的集合，事务会把所有操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败。</p><h4 id="基本操作："><a href="#基本操作：" class="headerlink" title="基本操作："></a>基本操作：</h4><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><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="comment">-- 1. 查询张三账户余额</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> account <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;张三&#x27;</span>;</span><br><span class="line"><span class="comment">-- 2. 将张三账户余额-1000</span></span><br><span class="line"><span class="keyword">update</span> account <span class="keyword">set</span> money <span class="operator">=</span> money <span class="operator">-</span> <span class="number">1000</span> <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;张三&#x27;</span>;</span><br><span class="line"><span class="comment">-- 此语句出错后张三钱减少但是李四钱没有增加</span></span><br><span class="line">模拟<span class="keyword">sql</span>语句错误</span><br><span class="line"><span class="comment">-- 3. 将李四账户余额+1000</span></span><br><span class="line"><span class="keyword">update</span> account <span class="keyword">set</span> money <span class="operator">=</span> money <span class="operator">+</span> <span class="number">1000</span> <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;李四&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看事务提交方式</span></span><br><span class="line"><span class="keyword">SELECT</span> @<span class="variable">@AUTOCOMMIT</span>;</span><br><span class="line"><span class="comment">-- 设置事务提交方式，1为自动提交，0为手动提交，该设置只对当前会话有效</span></span><br><span class="line"><span class="keyword">SET</span> @<span class="variable">@AUTOCOMMIT</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="comment">-- 提交事务</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br><span class="line"><span class="comment">-- 回滚事务</span></span><br><span class="line"><span class="keyword">ROLLBACK</span>;</span><br></pre></td></tr></table></figure><h4 id="操作方式二"><a href="#操作方式二" class="headerlink" title="操作方式二"></a>操作方式二</h4><p>(1) 开启事务：</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">START</span> TRANSACTION 或 <span class="keyword">BEGIN</span> TRANSACTION;</span><br></pre></td></tr></table></figure><p>(2) 提交事务：</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">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>(3) 回滚事务：</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">ROLLBACK</span>;</span><br></pre></td></tr></table></figure><p>(4) 操作实例：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">start</span> transaction;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> account <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;张三&#x27;</span>;</span><br><span class="line"><span class="keyword">update</span> account <span class="keyword">set</span> money <span class="operator">=</span> money <span class="operator">-</span> <span class="number">1000</span> <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;张三&#x27;</span>;</span><br><span class="line"><span class="keyword">update</span> account <span class="keyword">set</span> money <span class="operator">=</span> money <span class="operator">+</span> <span class="number">1000</span> <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;李四&#x27;</span>;</span><br><span class="line"><span class="keyword">commit</span>;</span><br></pre></td></tr></table></figure><h4 id="四大特性ACID"><a href="#四大特性ACID" class="headerlink" title="四大特性ACID"></a>四大特性ACID</h4><ul><li>原子性(Atomicity)：事务是不可分割的最小操作单元，要么全部成功，要么全部失败</li><li>一致性(Consistency)：事务完成时，必须使所有数据都保持一致状态</li><li>隔离性(Isolation)：数据库系统提供的隔离机制，保证事务在不受外部并发操作影响的独立环境下运行</li><li>持久性(Durability)：事务一旦提交或回滚，它对数据库中的数据的改变就是永久的</li></ul><h4 id="并发事务"><a href="#并发事务" class="headerlink" title="并发事务"></a>并发事务</h4><p>(1) 并发事务问题</p><table><thead><tr><th>问题</th><th>描述</th></tr></thead><tbody><tr><td>脏读</td><td>一个事务读到另一个事务还没提交的数据</td></tr><tr><td>不可重复读</td><td>一个事务先后读取同一条记录，但两次读取的数据不同</td></tr><tr><td>幻读</td><td>一个事务按照条件查询数据时，没有对应的数据行，但是再插入数据时，又发现这行数据已经存在</td></tr></tbody></table><p>(2) 并发事务隔离级别：</p><table><thead><tr><th>隔离级别</th><th>脏读</th><th>不可重复读</th><th>幻读</th></tr></thead><tbody><tr><td>Read uncommitted（读未提交）</td><td>√</td><td>√</td><td>√</td></tr><tr><td>Read committed（读已提交）</td><td>×</td><td>√</td><td>√</td></tr><tr><td>Repeatable Read(默认)</td><td>×</td><td>×</td><td>√</td></tr><tr><td>Serializable（串行化）</td><td>×</td><td>×</td><td>×</td></tr></tbody></table><ul><li>√表示在当前隔离级别下该问题会出现</li><li>Serializable 性能最低；Read uncommitted 性能最高，数据安全性最差</li></ul><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">SELECT</span> @<span class="variable">@TRANSACTION_ISOLATION</span>;</span><br></pre></td></tr></table></figure><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">SET</span> [ SESSION <span class="operator">|</span> <span class="keyword">GLOBAL</span> ] TRANSACTION ISOLATION LEVEL &#123;READ UNCOMMITTED <span class="operator">|</span> READ COMMITTED <span class="operator">|</span> REPEATABLE READ <span class="operator">|</span> SERIALIZABLE &#125;;</span><br></pre></td></tr></table></figure><p>SESSION 是会话级别，表示只针对当前会话有效，GLOBAL 表示对所有会话有效</p><h3 id="创建数据库的流程"><a href="#创建数据库的流程" class="headerlink" title="创建数据库的流程"></a>创建数据库的流程</h3><ol><li>点击左侧界面 **“Create data source”，**在弹出的菜单中，选择 <strong>“MySQL”</strong></li></ol><img src="https://i-blog.csdnimg.cn/direct/a8409626a96145bda3f2368a41341b54.png" alt="img" style="zoom: 50%;" /><ol start="2"><li>填写 MySQL 连接信息，输入User、Password</li></ol><img src="https://i-blog.csdnimg.cn/direct/8bbf2feded9f4479b44580d144f540fb.png" alt="img" style="zoom: 50%;" /><ol start="3"><li>下载 MySQL 驱动（首次连接需要），界面会提示 “Download missing driver files”（缺少驱动文件），点击这个提示 → 选择自动下载驱动</li><li>填写完信息 + 下载驱动后，点击窗口右下角的 **“Test Connection”，**最后点击 “Apply”→“OK”</li></ol>]]></content>
    
    
    <summary type="html">MySQL 基础语法入门：DDL 数据定义、DML 数据操作、DQL 多表查询与 DCL 权限控制</summary>
    
    
    
    <category term="MySQL" scheme="https://kon-forever.cloud/categories/MySQL/"/>
    
    
    <category term="MySQL" scheme="https://kon-forever.cloud/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>SSM学习笔记之 MyBatis-Plus</title>
    <link href="https://kon-forever.cloud/2026/03/20/SSM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BMyBatisPlus/"/>
    <id>https://kon-forever.cloud/2026/03/20/SSM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BMyBatisPlus/</id>
    <published>2026-03-20T04:00:00.000Z</published>
    <updated>2026-03-20T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="MyBatisPlus入门案例与简介"><a href="#MyBatisPlus入门案例与简介" class="headerlink" title="MyBatisPlus入门案例与简介"></a>MyBatisPlus入门案例与简介</h3><h4 id="入门案例"><a href="#入门案例" class="headerlink" title="入门案例"></a>入门案例</h4><h4 id="创建Dao接口"><a href="#创建Dao接口" class="headerlink" title="创建Dao接口"></a>创建Dao接口</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserDao</span> <span class="keyword">extends</span> <span class="title class_">BaseMapper</span>&lt;User&gt;&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="编写引导类"><a href="#编写引导类" class="headerlink" title="编写引导类"></a>编写引导类</h4><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">@SpringBootApplication</span></span><br><span class="line"><span class="comment">//@MapperScan(&quot;com.itheima.dao&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Mybatisplus01QuickstartApplication</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        SpringApplication.run(Mybatisplus01QuickstartApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="标准数据层开发"><a href="#标准数据层开发" class="headerlink" title="标准数据层开发"></a>标准数据层开发</h3><h4 id="标准CRUD使用"><a href="#标准CRUD使用" class="headerlink" title="标准CRUD使用"></a>标准CRUD使用</h4><h4 id="方法大全"><a href="#方法大全" class="headerlink" title="方法大全"></a>方法大全</h4><img src="https://i-blog.csdnimg.cn/direct/96acc94ecfa44510b13dcf36074d6aa0.png" alt="img" style="zoom:67%;" /><h4 id="新增"><a href="#新增" class="headerlink" title="新增"></a>新增</h4><h4 id="语法格式"><a href="#语法格式" class="headerlink" title="语法格式"></a>语法格式</h4><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="type">int</span> <span class="title function_">insert</span> <span class="params">(T t)</span></span><br></pre></td></tr></table></figure><ul><li>T：泛型，新增用来保存新增数据</li><li>int：返回值，新增成功后返回1，没有新增成功返回的是0</li></ul><h4 id="代码示例"><a href="#代码示例" class="headerlink" title="代码示例"></a>代码示例</h4><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="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus01QuickstartApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testSave</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user.setName(<span class="string">&quot;黑马程序员&quot;</span>);</span><br><span class="line">        user.setPassword(<span class="string">&quot;itheima&quot;</span>);</span><br><span class="line">        user.setAge(<span class="number">12</span>);</span><br><span class="line">        user.setTel(<span class="string">&quot;4006184000&quot;</span>);</span><br><span class="line">        userDao.insert(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h4><h4 id="语法格式-1"><a href="#语法格式-1" class="headerlink" title="语法格式"></a>语法格式</h4><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="type">int</span> <span class="title function_">deleteById</span> <span class="params">(Serializable id)</span></span><br></pre></td></tr></table></figure><h4 id="代码示例-1"><a href="#代码示例-1" class="headerlink" title="代码示例"></a>代码示例</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus01QuickstartApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testDelete</span><span class="params">()</span> &#123;</span><br><span class="line">        userDao.deleteById(<span class="number">1401856123725713409L</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="修改"><a href="#修改" class="headerlink" title="修改"></a>修改</h4><h4 id="语法格式-2"><a href="#语法格式-2" class="headerlink" title="语法格式"></a>语法格式</h4><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="type">int</span> <span class="title function_">updateById</span><span class="params">(T t)</span>;</span><br></pre></td></tr></table></figure><h4 id="代码示例-2"><a href="#代码示例-2" class="headerlink" title="代码示例"></a>代码示例</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus01QuickstartApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testUpdate</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user.setId(<span class="number">1L</span>);</span><br><span class="line">        user.setName(<span class="string">&quot;Tom888&quot;</span>);</span><br><span class="line">        user.setPassword(<span class="string">&quot;tom888&quot;</span>);</span><br><span class="line">        userDao.updateById(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>在代码中通过 <code>user.setId(1L)</code> 明确指定了要更新的记录的主键值为 <code>1</code>。</p><h4 id="根据ID查询"><a href="#根据ID查询" class="headerlink" title="根据ID查询"></a>根据ID查询</h4><h4 id="语法格式-3"><a href="#语法格式-3" class="headerlink" title="语法格式"></a>语法格式</h4><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">T <span class="title function_">selectById</span> <span class="params">(Serializable id)</span></span><br></pre></td></tr></table></figure><h4 id="代码示例-3"><a href="#代码示例-3" class="headerlink" title="代码示例"></a>代码示例</h4><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="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus01QuickstartApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetById</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userDao.selectById(<span class="number">2L</span>);</span><br><span class="line">        System.out.println(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Lombok"><a href="#Lombok" class="headerlink" title="Lombok"></a>Lombok</h4><ul><li>@Data：是个组合注解，可自动为实体类生成所有属性的 <strong>get&#x2F;set 方法、toString 方法、equals 和 hashCode 方法。</strong></li><li>@NoArgsConstructor：提供一个无参构造函数</li><li>@AllArgsConstructor：提供一个包含所有参数的构造函数</li></ul><h4 id="分页功能"><a href="#分页功能" class="headerlink" title="分页功能"></a>分页功能</h4><h4 id="语法格式-4"><a href="#语法格式-4" class="headerlink" title="语法格式"></a>语法格式</h4><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">IPage&lt;T&gt; <span class="title function_">selectPage</span><span class="params">(IPage&lt;T&gt; page, Wrapper&lt;T&gt; queryWrapper)</span></span><br></pre></td></tr></table></figure><ul><li>IPage：用来构建分页查询条件</li><li>Wrapper：用来构建条件查询的条件，目前我们没有可直接传为Null</li><li>IPage：返回值，你会发现构建分页条件和方法的返回值都是IPage</li></ul><p>IPage是一个接口，我们需要找到它的实现类来构建它，具体的实现类，可以进入到IPage类中按ctrl+h，会找到其有一个实现类为<code>Page</code>。</p><h4 id="操作步骤"><a href="#操作步骤" class="headerlink" title="操作步骤"></a>操作步骤</h4><p>调用方法传入参数获取返回值</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="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus01QuickstartApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//分页查询</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testSelectPage</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">//1 创建IPage分页对象,设置分页参数,1为当前页码，3为每页显示的记录数</span></span><br><span class="line">        IPage&lt;User&gt; page=<span class="keyword">new</span> <span class="title class_">Page</span>&lt;&gt;(<span class="number">1</span>,<span class="number">3</span>);</span><br><span class="line">        <span class="comment">//2 执行分页查询</span></span><br><span class="line">        userDao.selectPage(page,<span class="literal">null</span>);</span><br><span class="line">        <span class="comment">//3 获取分页结果</span></span><br><span class="line">        System.out.println(<span class="string">&quot;当前页码值：&quot;</span>+page.getCurrent());</span><br><span class="line">        System.out.println(<span class="string">&quot;每页显示数：&quot;</span>+page.getSize());</span><br><span class="line">        System.out.println(<span class="string">&quot;一共多少页：&quot;</span>+page.getPages());</span><br><span class="line">        System.out.println(<span class="string">&quot;一共多少条数据：&quot;</span>+page.getTotal());</span><br><span class="line">        System.out.println(<span class="string">&quot;数据：&quot;</span>+page.getRecords());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>设置分页拦截器</p><p>这个拦截器MP已经为我们提供好了，我们只需要将其配置成Spring管理的bean对象即可。</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="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MybatisPlusConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MybatisPlusInterceptor <span class="title function_">mybatisPlusInterceptor</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">//1 创建MybatisPlusInterceptor拦截器对象</span></span><br><span class="line">        MybatisPlusInterceptor mpInterceptor=<span class="keyword">new</span> <span class="title class_">MybatisPlusInterceptor</span>();</span><br><span class="line">        <span class="comment">//2 添加分页拦截器</span></span><br><span class="line">        mpInterceptor.addInnerInterceptor(<span class="keyword">new</span> <span class="title class_">PaginationInnerInterceptor</span>());</span><br><span class="line">        <span class="keyword">return</span> mpInterceptor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="DQL编程控制"><a href="#DQL编程控制" class="headerlink" title="DQL编程控制"></a>DQL编程控制</h3><h4 id="条件查询"><a href="#条件查询" class="headerlink" title="条件查询"></a>条件查询</h4><p>在进行查询的时候，我们的入口是在Wrapper这个类上，因为它是一个接口，所以我们需要去找它对应的实现类，关于实现类也有很多，说明我们有多种构建查询条件对象的方式。</p><p><img src="https://i-blog.csdnimg.cn/direct/224ab7433b5c4b6a9100a967cf1b8d5c.png" alt="img"></p><h4 id="方式一：简单的-QueryWrapper"><a href="#方式一：简单的-QueryWrapper" class="headerlink" title="方式一：简单的 QueryWrapper"></a>方式一：简单的 QueryWrapper</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">QueryWrapper</span> <span class="variable">qw</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">QueryWrapper</span>();</span><br><span class="line">        qw.lt(<span class="string">&quot;age&quot;</span>,<span class="number">18</span>);</span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(qw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>lt: 小于(&lt;)，最终的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> id,name,password,age,tel <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> (age <span class="operator">&lt;</span> ?)</span><br></pre></td></tr></table></figure><h4 id="方式二：QueryWrapper的基础上使用lambda"><a href="#方式二：QueryWrapper的基础上使用lambda" class="headerlink" title="方式二：QueryWrapper的基础上使用lambda"></a>方式二：QueryWrapper的基础上使用lambda</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        QueryWrapper&lt;User&gt; qw = <span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        qw.lambda().lt(User::getAge, <span class="number">10</span>);<span class="comment">//添加条件</span></span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(qw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① User::getAge，为lambda表达式中的方法引用，类名::方法名，最终的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> id,name,password,age,tel <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> (age <span class="operator">&lt;</span> ?)</span><br></pre></td></tr></table></figure><p>1). 用 Lambda 表达式的写法（完整逻辑）：</p><figure class="highlight java"><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">// Lambda表达式：接收User对象，调用它的getAge()方法返回年龄</span></span><br><span class="line">qw.lambda().lt((User user) -&gt; user.getAge(), <span class="number">10</span>);</span><br></pre></td></tr></table></figure><p>2). 用方法引用的简化写法</p><figure class="highlight java"><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">// 方法引用：直接指向User类的getAge()方法，效果和上面的Lambda完全一样</span></span><br><span class="line">qw.lambda().lt(User::getAge, <span class="number">10</span>);</span><br></pre></td></tr></table></figure><p>② **注意：**构建LambdaQueryWrapper的时候泛型不能省。</p><h4 id="方式三：LambdaQueryWrapper（推荐）"><a href="#方式三：LambdaQueryWrapper（推荐）" class="headerlink" title="方式三：LambdaQueryWrapper（推荐）"></a>方式三：LambdaQueryWrapper（推荐）</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.lt(User::getAge, <span class="number">10</span>);</span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(lqw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="多条件构建"><a href="#多条件构建" class="headerlink" title="多条件构建"></a>多条件构建</h4><p>需求：查询数据库表中，年龄在10岁到30岁之间的用户信息</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.lt(User::getAge, <span class="number">30</span>);</span><br><span class="line">        lqw.gt(User::getAge, <span class="number">10</span>);</span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(lqw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>1). gt：大于(&gt;)，最终的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> id,name,password,age,tel <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> (age <span class="operator">&lt;</span> ? <span class="keyword">AND</span> age <span class="operator">&gt;</span> ?)</span><br></pre></td></tr></table></figure><p>2). 构建多条件的时候，可以支持链式编程</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></pre></td><td class="code"><pre><span class="line">LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">lqw.lt(User::getAge, <span class="number">30</span>).gt(User::getAge, <span class="number">10</span>);</span><br><span class="line">List&lt;User&gt; userList = userDao.selectList(lqw);</span><br><span class="line">System.out.println(userList);</span><br></pre></td></tr></table></figure><p>需求：查询数据库表中，年龄小于10或年龄大于30的数据</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.lt(User::getAge, <span class="number">10</span>).or().gt(User::getAge, <span class="number">30</span>);</span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(lqw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>or()就相当于我们sql语句中的<code>or</code>关键字，不加默认是<code>and</code>，最终的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> id,name,password,age,tel <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> (age <span class="operator">&lt;</span> ? <span class="keyword">OR</span> age <span class="operator">&gt;</span> ?)</span><br></pre></td></tr></table></figure><h4 id="null-判定"><a href="#null-判定" class="headerlink" title="null 判定"></a>null 判定</h4><h4 id="存在的问题"><a href="#存在的问题" class="headerlink" title="存在的问题"></a>存在的问题</h4><p>先来看一张图：</p><p><img src="https://i-blog.csdnimg.cn/direct/708daeff33354fd282fd892d2735c5ef.png" alt="img"></p><ul><li>我们在做条件查询的时候，一般会有很多条件可以供用户进行选择查询。</li><li>这些条件用户可以选择使用也可以选择不使用，比如我要查询价格在8000以上的手机</li><li>在输入条件的时候，价格有一个区间范围，按照需求只需要在第一个价格输入框中输入8000</li><li>后台在做价格查询的时候，一般会让 price&gt;值1 and price &lt;值2</li><li>因为前端没有输入值2，所以如果不处理的话，就会出现 price&gt;8000 and price &lt; null问题</li><li>这个时候查询的结果就会出问题，具体该如何解决?</li></ul><h4 id="操作步骤-1"><a href="#操作步骤-1" class="headerlink" title="操作步骤"></a>操作步骤</h4><p>1). 新建一个模型类，让其继承User类，并在其中添加age2属性，UserQuery在拥有User属性后同时添加了age2属性。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserQuery</span> <span class="keyword">extends</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer age2;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2). 查询方式</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">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">//模拟页面传递过来的查询数据</span></span><br><span class="line">        <span class="type">UserQuery</span> <span class="variable">uq</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserQuery</span>();</span><br><span class="line">        uq.setAge(<span class="number">10</span>);</span><br><span class="line">        uq.setAge2(<span class="number">30</span>);</span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.lt(<span class="literal">null</span>!=uq.getAge2(),User::getAge, uq.getAge2());</span><br><span class="line">        lqw.gt(<span class="literal">null</span>!=uq.getAge(),User::getAge, uq.getAge());</span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(lqw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>lt()方法</p><p><img src="https://i-blog.csdnimg.cn/direct/c28247095a9b463591bcb18b378eb645.png" alt="img"></p><p>condition为boolean类型，返回true，则添加条件，返回false则不添加条件</p><p>你代码里的<code>lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2())</code>，第一个参数<code>null!=uq.getAge2()</code>是 MyBatis-Plus 提供的<strong>条件过滤功能</strong>：</p><ul><li>如果<code>null!=uq.getAge2()</code>为<code>true</code>（即前端传了 age2 参数）：就把<code>age &lt; 30</code>这个条件加入 SQL；</li><li>如果<code>null!=uq.getAge2()</code>为<code>false</code>（即前端没传 age2）：就忽略这个条件，不会生成<code>age &lt; null</code>这种无效 SQL。</li></ul><h4 id="查询投影"><a href="#查询投影" class="headerlink" title="查询投影"></a>查询投影</h4><h4 id="查询指定字段"><a href="#查询指定字段" class="headerlink" title="查询指定字段"></a>查询指定字段</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.select(User::getId,User::getName,User::getAge);</span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(lqw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① select(…)方法用来设置查询的字段列，可以设置多个，最终的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> id,name,age <span class="keyword">FROM</span> <span class="keyword">user</span></span><br></pre></td></tr></table></figure><p>② <code>lqw.select(User::getId, User::getName, User::getAge)</code>：这是<strong>方法引用</strong>的应用，MyBatis-Plus 会根据<code>User</code>类的<code>getXxx()</code>方法，自动解析出数据库的字段名（<code>getId</code>→<code>id</code>，<code>getName</code>→<code>name</code>），避免手写字段名出错（比如把<code>name</code>写成<code>naem</code>）。</p><h4 id="聚合查询"><a href="#聚合查询" class="headerlink" title="聚合查询"></a>聚合查询</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        QueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        <span class="comment">//lqw.select(&quot;count(*) as count&quot;);</span></span><br><span class="line">        <span class="comment">//SELECT count(*) as count FROM user</span></span><br><span class="line">        <span class="comment">//lqw.select(&quot;max(age) as maxAge&quot;);</span></span><br><span class="line">        <span class="comment">//SELECT max(age) as maxAge FROM user</span></span><br><span class="line">        <span class="comment">//lqw.select(&quot;min(age) as minAge&quot;);</span></span><br><span class="line">        <span class="comment">//SELECT min(age) as minAge FROM user</span></span><br><span class="line">        <span class="comment">//lqw.select(&quot;sum(age) as sumAge&quot;);</span></span><br><span class="line">        <span class="comment">//SELECT sum(age) as sumAge FROM user</span></span><br><span class="line">        lqw.select(<span class="string">&quot;avg(age) as avgAge&quot;</span>);</span><br><span class="line">        <span class="comment">//SELECT avg(age) as avgAge FROM user</span></span><br><span class="line">        List&lt;Map&lt;String, Object&gt;&gt; userList = userDao.selectMaps(lqw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① 为什么用 <code>QueryWrapper</code> 而不是 <code>LambdaQueryWrapper</code>？</p><p>因为这里要直接写 SQL 片段（如 <code>avg(age)</code>），而不是引用实体类的方法。Lambda 方式更适合查询实体类字段，而直接写 SQL 片段时，普通 <code>QueryWrapper</code> 更灵活。</p><p>② 为什么用 <code>selectMaps</code> 而不是 <code>selectList</code>？</p><p><code>selectList</code> 要求返回的每一行数据都能映射为 <code>User</code> 对象，但聚合函数（如 <code>avg(age)</code>）的结果不是 <code>User</code> 类的字段，无法直接封装成 <code>User</code>。<code>selectMaps</code> 会把每一行结果封装成一个 <code>Map&lt;String, Object&gt;</code>，其中：</p><ul><li>Key 是列名（或别名，如 <code>avgAge</code>）</li><li>Value 是对应的值（如平均年龄 25.5）</li></ul><h4 id="分组查询"><a href="#分组查询" class="headerlink" title="分组查询"></a>分组查询</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        QueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.select(<span class="string">&quot;count(*) as count,tel&quot;</span>);</span><br><span class="line">        lqw.groupBy(<span class="string">&quot;tel&quot;</span>);</span><br><span class="line">        List&lt;Map&lt;String, Object&gt;&gt; list = userDao.selectMaps(lqw);</span><br><span class="line">        System.out.println(list);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="查询条件"><a href="#查询条件" class="headerlink" title="查询条件"></a>查询条件</h4><h4 id="等值查询"><a href="#等值查询" class="headerlink" title="等值查询"></a>等值查询</h4><p>需求</p><p>根据用户名和密码查询用户信息</p><h4 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.eq(User::getName, <span class="string">&quot;Jerry&quot;</span>).eq(User::getPassword, <span class="string">&quot;jerry&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">loginUser</span> <span class="operator">=</span> userDao.selectOne(lqw);</span><br><span class="line">        System.out.println(loginUser);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>eq()：相当于 <code>=</code>，对应的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> id,name,password,age,tel <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> (name <span class="operator">=</span> ? <span class="keyword">AND</span> password <span class="operator">=</span> ?)</span><br></pre></td></tr></table></figure><ul><li>selectList：查询结果为多个或者单个</li><li>selectOne：查询结果为单个</li></ul><h4 id="范围查询"><a href="#范围查询" class="headerlink" title="范围查询"></a>范围查询</h4><p>需求</p><p>对年龄进行范围查询，使用lt()、le()、gt()、ge()、between()进行范围查询</p><h4 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.between(User::getAge, <span class="number">10</span>, <span class="number">30</span>);</span><br><span class="line">        <span class="comment">//SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)</span></span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(lqw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>gt()：大于(&gt;)</li><li>ge()：大于等于(&gt;&#x3D;)</li><li>lt()：小于(&lt;)</li><li>le()：小于等于(&lt;&#x3D;)</li><li>between()：between ? and ?</li></ul><h4 id="模糊查询"><a href="#模糊查询" class="headerlink" title="模糊查询"></a>模糊查询</h4><p>需求</p><p>查询表中name属性的值以<code>J</code>开头的用户信息，使用like进行模糊查询</p><h4 id="代码实现-2"><a href="#代码实现-2" class="headerlink" title="代码实现"></a>代码实现</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus02DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        LambdaQueryWrapper&lt;User&gt; lqw = <span class="keyword">new</span> <span class="title class_">LambdaQueryWrapper</span>&lt;User&gt;();</span><br><span class="line">        lqw.likeLeft(User::getName, <span class="string">&quot;J&quot;</span>);</span><br><span class="line">        <span class="comment">//SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)</span></span><br><span class="line">        List&lt;User&gt; userList = userDao.selectList(lqw);</span><br><span class="line">        System.out.println(userList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li>like()：前后加百分号,如 %J%</li><li>likeLeft()：前面加百分号,如 %J</li><li>likeRight()：后面加百分号,如 J%</li></ul><h4 id="映射匹配兼容性"><a href="#映射匹配兼容性" class="headerlink" title="映射匹配兼容性"></a>映射匹配兼容性</h4><h4 id="存在的问题-1"><a href="#存在的问题-1" class="headerlink" title="存在的问题"></a>存在的问题</h4><h4 id="问题1：表字段与编码属性设计不同步"><a href="#问题1：表字段与编码属性设计不同步" class="headerlink" title="问题1：表字段与编码属性设计不同步"></a>问题1：表字段与编码属性设计不同步</h4><p>① 当表的列名和模型类的属性名发生不一致，就会导致数据封装不到模型对象，这个时候就需要其中一方做出修改，那如果前提是两边都不能改又该如何解决?</p><p>② MP给我们提供了一个注解<code>@TableField</code>，使用该注解可以实现模型类属性名和表的列名之间的映射关系</p><p><img src="https://i-blog.csdnimg.cn/direct/7395f10d81f9404c8e9b5ce8617311da.png" alt="img"></p><h4 id="问题2：编码中添加了数据库中未定义的属性"><a href="#问题2：编码中添加了数据库中未定义的属性" class="headerlink" title="问题2：编码中添加了数据库中未定义的属性"></a>问题2：编码中添加了数据库中未定义的属性</h4><p>① 当模型类中多了一个数据库表不存在的字段，就会导致生成的sql语句中在select的时候查询了数据库不存在的字段，程序运行就会报错，错误信息为：</p><p>Unknown column ‘多出来的字段名称’ in ‘field list’</p><p>② 具体的解决方案用到的还是<code>@TableField</code>注解，它有一个属性叫<code>exist</code>，设置该字段是否在数据库表中存在，如果设置为false则不存在，生成sql语句查询的时候，就不会再查询该字段了。</p><p><img src="https://i-blog.csdnimg.cn/direct/20e428452c5a448d96282ed6c16064d0.png" alt="img"></p><h4 id="问题3：采用默认查询开放了更多的字段查看权限"><a href="#问题3：采用默认查询开放了更多的字段查看权限" class="headerlink" title="问题3：采用默认查询开放了更多的字段查看权限"></a>问题3：采用默认查询开放了更多的字段查看权限</h4><p>① 查询表中所有的列的数据，就可能把一些敏感数据查询到返回给前端，这个时候我们就需要限制哪些字段默认不要进行查询。</p><p>② 解决方案是<code>@TableField</code>注解的一个属性叫<code>select</code>，该属性设置默认是否需要查询该字段的值，true(默认值)表示默认查询该字段，false表示默认不查询该字段。</p><p><img src="https://i-blog.csdnimg.cn/direct/2ab4ac7c0ada4317b037131636873ce6.png" alt="img"></p><h4 id="代码实现-3"><a href="#代码实现-3" class="headerlink" title="代码实现"></a>代码实现</h4><p>① 修改数据库表user为tbl_user</p><p>直接查询会报错，原因是MP默认情况下会使用模型类的类名首字母小写当表名使用。</p><p><img src="https://i-blog.csdnimg.cn/direct/f291a618c94f4435adead3256f5a1721.png" alt="img"></p><p>② 模型类添加@TableName注解</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="meta">@Data</span></span><br><span class="line"><span class="meta">@TableName(&quot;tbl_user&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>③ 将字段password修改成pwd</p><p>直接查询会报错，原因是MP默认情况下会使用模型类的属性名当做表的列名使用</p><p>④ 使用@TableField映射关系</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">@Data</span></span><br><span class="line"><span class="meta">@TableName(&quot;tbl_user&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="meta">@TableField(value=&quot;pwd&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="知识点"><a href="#知识点" class="headerlink" title="知识点"></a>知识点</h4><p>@TableField</p><table><thead><tr><th>名称</th><th>@TableField</th></tr></thead><tbody><tr><td>类型</td><td>属性注解</td></tr><tr><td>位置</td><td>模型类属性定义上方</td></tr><tr><td>作用</td><td>设置当前属性对应的数据库表中的字段关系</td></tr><tr><td>相关属性</td><td>value(默认)：设置数据库表字段名称 exist：设置属性在数据库表字段中是否存在，默认为true，此属性不能与value合并使用 select：设置属性是否参与查询，此属性与select()映射配置不冲突</td></tr></tbody></table><p>@TableName</p><table><thead><tr><th>名称</th><th>@TableName</th></tr></thead><tbody><tr><td>类型</td><td>类注解</td></tr><tr><td>位置</td><td>模型类定义上方</td></tr><tr><td>作用</td><td>设置当前类对应于数据库表关系</td></tr><tr><td>相关属性</td><td>value(默认)：设置数据库表名称</td></tr></tbody></table><h3 id="DML编程控制"><a href="#DML编程控制" class="headerlink" title="DML编程控制"></a>DML编程控制</h3><h4 id="生成策略"><a href="#生成策略" class="headerlink" title="生成策略"></a>生成策略</h4><ul><li>NONE：不设置id生成策略</li><li>INPUT：用户手工输入id</li><li>ASSIGN_ID：雪花算法生成id(可兼容数值型与字符串型)</li><li>ASSIGN_UUID：以UUID生成算法作为id生成策略</li><li>其他的几个策略均已过时，都将被ASSIGN_ID和ASSIGN_UUID代替掉。</li></ul><h4 id="代码实现-4"><a href="#代码实现-4" class="headerlink" title="代码实现"></a>代码实现</h4><h4 id="INPUT策略"><a href="#INPUT策略" class="headerlink" title="INPUT策略"></a>INPUT策略</h4><p>① 设置生成策略为INPUT</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@TableName(&quot;tbl_user&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="meta">@TableId(type = IdType.INPUT)</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="meta">@TableField(value=&quot;pwd&quot;,select=false)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">    <span class="meta">@TableField(exist=false)</span></span><br><span class="line">    <span class="keyword">private</span> Integer online;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② 添加数据手动设置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="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus03DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testSave</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        <span class="comment">//设置主键ID的值</span></span><br><span class="line">        user.setId(<span class="number">666L</span>);</span><br><span class="line">        user.setName(<span class="string">&quot;黑马程序员&quot;</span>);</span><br><span class="line">        user.setPassword(<span class="string">&quot;itheima&quot;</span>);</span><br><span class="line">        user.setAge(<span class="number">12</span>);</span><br><span class="line">        user.setTel(<span class="string">&quot;4006184000&quot;</span>);</span><br><span class="line">        userDao.insert(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="ASSIGN-ID策略（雪花算法）"><a href="#ASSIGN-ID策略（雪花算法）" class="headerlink" title="ASSIGN_ID策略（雪花算法）"></a>ASSIGN_ID策略（雪花算法）</h4><p>① 设置生成策略为ASSIGN_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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@TableName(&quot;tbl_user&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="meta">@TableId(type = IdType.ASSIGN_ID)</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="meta">@TableField(value=&quot;pwd&quot;,select=false)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">    <span class="meta">@TableField(exist=false)</span></span><br><span class="line">    <span class="keyword">private</span> Integer online;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② 添加数据不设置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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus03DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testSave</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user.setName(<span class="string">&quot;黑马程序员&quot;</span>);</span><br><span class="line">        user.setPassword(<span class="string">&quot;itheima&quot;</span>);</span><br><span class="line">        user.setAge(<span class="number">12</span>);</span><br><span class="line">        user.setTel(<span class="string">&quot;4006184000&quot;</span>);</span><br><span class="line">        userDao.insert(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="ASSIGN-UUID策略"><a href="#ASSIGN-UUID策略" class="headerlink" title="ASSIGN_UUID策略"></a>ASSIGN_UUID策略</h4><p>① 设置生成策略为ASSIGN_UUID</p><p>使用uuid需要注意的是，主键的类型不能是Long，而应该改成String类型</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@TableName(&quot;tbl_user&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="meta">@TableId(type = IdType.ASSIGN_UUID)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="meta">@TableField(value=&quot;pwd&quot;,select=false)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">    <span class="meta">@TableField(exist=false)</span></span><br><span class="line">    <span class="keyword">private</span> Integer online;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② 添加数据不设置ID</p><h4 id="知识点-1"><a href="#知识点-1" class="headerlink" title="知识点"></a>知识点</h4><table><thead><tr><th>名称</th><th>@TableId</th></tr></thead><tbody><tr><td>类型</td><td>属性注解</td></tr><tr><td>位置</td><td>模型类中用于表示主键的属性定义上方</td></tr><tr><td>作用</td><td>设置当前类中主键属性的生成策略</td></tr><tr><td>相关属性</td><td>value(默认)：设置数据库表主键名称 type：设置主键属性的生成策略，值查照IdType的枚举值</td></tr></tbody></table><h4 id="多记录操作"><a href="#多记录操作" class="headerlink" title="多记录操作"></a>多记录操作</h4><h4 id="批量删除"><a href="#批量删除" class="headerlink" title="批量删除"></a>批量删除</h4><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="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Mybatisplus03DqlApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testDelete</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">//删除指定多条数据</span></span><br><span class="line">        List&lt;Long&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        list.add(<span class="number">1402551342481838081L</span>);</span><br><span class="line">        list.add(<span class="number">1402553134049501186L</span>);</span><br><span class="line">        list.add(<span class="number">1402553619611430913L</span>);</span><br><span class="line">        userDao.deleteBatchIds(list);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="逻辑删除"><a href="#逻辑删除" class="headerlink" title="逻辑删除"></a>逻辑删除</h4><h4 id="实体类添加属性"><a href="#实体类添加属性" class="headerlink" title="实体类添加属性"></a>实体类添加属性</h4><p>标识新增的字段为逻辑删除字段，使用<code>@TableLogic</code></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="meta">@Data</span></span><br><span class="line"><span class="comment">//@TableName(&quot;tbl_user&quot;) 可以不写是因为配置了全局配置</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="meta">@TableId(type = IdType.ASSIGN_UUID)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="meta">@TableField(value=&quot;pwd&quot;,select=false)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">    <span class="meta">@TableField(exist=false)</span></span><br><span class="line">    <span class="keyword">private</span> Integer online;</span><br><span class="line">    <span class="meta">@TableLogic(value=&quot;0&quot;,delval=&quot;1&quot;)</span></span><br><span class="line">    <span class="comment">//value为正常数据的值，delval为删除数据的值</span></span><br><span class="line">    <span class="keyword">private</span> Integer deleted;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="知识点-2"><a href="#知识点-2" class="headerlink" title="知识点"></a>知识点</h4><table><thead><tr><th>名称</th><th>@TableLogic</th></tr></thead><tbody><tr><td>类型</td><td>属性注解</td></tr><tr><td>位置</td><td>模型类中用于表示删除字段的属性定义上方</td></tr><tr><td>作用</td><td>标识该字段为进行逻辑删除的字段</td></tr><tr><td>相关属性</td><td>value：逻辑未删除值，delval：逻辑删除值</td></tr></tbody></table><h4 id="乐观锁"><a href="#乐观锁" class="headerlink" title="乐观锁"></a>乐观锁</h4><h4 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h4><p>① 数据库表中添加 version 列，默认值给 1</p><ul><li>就像给每张票加一个 “版本号”，初始是 1。</li></ul><p>② 线程 1 修改前，取出记录，获取 version&#x3D;1</p><ul><li>你看到票的版本号是 1。</li></ul><p>③ 线程 2 修改前，取出记录，获取 version&#x3D;1</p><ul><li>你朋友也看到票的版本号是 1。</li></ul><p>④ 线程 1 执行更新：set version &#x3D; 2 where version &#x3D; 1</p><ul><li>你下单：“只有版本号还是 1，我才把它改成 2”。</li><li>如果成功，票的版本号就变成 2 了。</li></ul><p>⑤ 线程 2 执行更新：set version &#x3D; 2 where version &#x3D; 1</p><ul><li>你朋友下单：“只有版本号还是 1，我才把它改成 2”。</li><li>但此时版本号已经是 2 了，所以他的更新条件不满足，执行失败。</li></ul><h4 id="代码实现-5"><a href="#代码实现-5" class="headerlink" title="代码实现"></a>代码实现</h4><p><strong>① 在模型类中添加对应的属性</strong></p><p>根据添加的字段列名，在模型类中添加对应的属性值</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="meta">@Data</span></span><br><span class="line"><span class="comment">//@TableName(&quot;tbl_user&quot;) 可以不写是因为配置了全局配置</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="meta">@TableId(type = IdType.ASSIGN_UUID)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="meta">@TableField(value=&quot;pwd&quot;,select=false)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">    <span class="meta">@TableField(exist=false)</span></span><br><span class="line">    <span class="keyword">private</span> Integer online;</span><br><span class="line">    <span class="keyword">private</span> Integer deleted;</span><br><span class="line">    <span class="meta">@Version</span></span><br><span class="line">    <span class="keyword">private</span> Integer version;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 添加乐观锁的拦截器</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MpConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MybatisPlusInterceptor <span class="title function_">mpInterceptor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//1.定义Mp拦截器</span></span><br><span class="line">        <span class="type">MybatisPlusInterceptor</span> <span class="variable">mpInterceptor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MybatisPlusInterceptor</span>();</span><br><span class="line">        <span class="comment">//2.添加乐观锁拦截器</span></span><br><span class="line">        mpInterceptor.addInnerInterceptor(<span class="keyword">new</span> <span class="title class_">OptimisticLockerInnerInterceptor</span>());</span><br><span class="line">        <span class="keyword">return</span> mpInterceptor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="快速开发"><a href="#快速开发" class="headerlink" title="快速开发"></a>快速开发</h3><h4 id="代码生成器实现"><a href="#代码生成器实现" class="headerlink" title="代码生成器实现"></a>代码生成器实现</h4><h4 id="创建代码生成类"><a href="#创建代码生成类" class="headerlink" title="创建代码生成类"></a>创建代码生成类</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CodeGenerator</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">//1.获取代码生成器的对象</span></span><br><span class="line">        <span class="type">AutoGenerator</span> <span class="variable">autoGenerator</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AutoGenerator</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//设置数据库相关配置</span></span><br><span class="line">        <span class="type">DataSourceConfig</span> <span class="variable">dataSource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DataSourceConfig</span>();</span><br><span class="line">        dataSource.setDriverName(<span class="string">&quot;com.mysql.cj.jdbc.Driver&quot;</span>);</span><br><span class="line">        dataSource.setUrl(<span class="string">&quot;jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC&quot;</span>);</span><br><span class="line">        dataSource.setUsername(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        dataSource.setPassword(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        autoGenerator.setDataSource(dataSource);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//设置全局配置</span></span><br><span class="line">        <span class="type">GlobalConfig</span> <span class="variable">globalConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GlobalConfig</span>();</span><br><span class="line">        globalConfig.setOutputDir(System.getProperty(<span class="string">&quot;user.dir&quot;</span>)+<span class="string">&quot;/mybatisplus_04_generator/src/main/java&quot;</span>);    <span class="comment">//设置代码生成位置</span></span><br><span class="line">        globalConfig.setOpen(<span class="literal">false</span>);    <span class="comment">//设置生成完毕后是否打开生成代码所在的目录</span></span><br><span class="line">        globalConfig.setAuthor(<span class="string">&quot;黑马程序员&quot;</span>);    <span class="comment">//设置作者</span></span><br><span class="line">        globalConfig.setFileOverride(<span class="literal">true</span>);     <span class="comment">//设置是否覆盖原始生成的文件</span></span><br><span class="line">        globalConfig.setMapperName(<span class="string">&quot;%sDao&quot;</span>);    <span class="comment">//设置数据层接口名，%s为占位符，指代模块名称</span></span><br><span class="line">        globalConfig.setIdType(IdType.ASSIGN_ID);   <span class="comment">//设置Id生成策略</span></span><br><span class="line">        autoGenerator.setGlobalConfig(globalConfig);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//设置包名相关配置</span></span><br><span class="line">        <span class="type">PackageConfig</span> <span class="variable">packageInfo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PackageConfig</span>();</span><br><span class="line">        packageInfo.setParent(<span class="string">&quot;com.aaa&quot;</span>);   <span class="comment">//设置生成的包名，与代码所在位置不冲突，二者叠加组成完整路径</span></span><br><span class="line">        packageInfo.setEntity(<span class="string">&quot;domain&quot;</span>);    <span class="comment">//设置实体类包名</span></span><br><span class="line">        packageInfo.setMapper(<span class="string">&quot;dao&quot;</span>);   <span class="comment">//设置数据层包名</span></span><br><span class="line">        autoGenerator.setPackageInfo(packageInfo);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//策略设置</span></span><br><span class="line">        <span class="type">StrategyConfig</span> <span class="variable">strategyConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StrategyConfig</span>();</span><br><span class="line">        strategyConfig.setInclude(<span class="string">&quot;tbl_user&quot;</span>);  <span class="comment">//设置当前参与生成的表名，参数为可变参数</span></span><br><span class="line">        strategyConfig.setTablePrefix(<span class="string">&quot;tbl_&quot;</span>);  <span class="comment">//设置数据库表的前缀名称，模块名 = 数据库表名 - 前缀名  例如： User = tbl_user - tbl_</span></span><br><span class="line">        strategyConfig.setRestControllerStyle(<span class="literal">true</span>);    <span class="comment">//设置是否启用Rest风格</span></span><br><span class="line">        strategyConfig.setVersionFieldName(<span class="string">&quot;version&quot;</span>);  <span class="comment">//设置乐观锁字段名</span></span><br><span class="line">        strategyConfig.setLogicDeleteFieldName(<span class="string">&quot;deleted&quot;</span>);  <span class="comment">//设置逻辑删除字段名</span></span><br><span class="line">        strategyConfig.setEntityLombokModel(<span class="literal">true</span>);  <span class="comment">//设置是否启用lombok</span></span><br><span class="line">        autoGenerator.setStrategy(strategyConfig);</span><br><span class="line">        <span class="comment">//2.执行生成操作</span></span><br><span class="line">        autoGenerator.execute();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">MyBatis-Plus 入门、标准 CRUD 与分页</summary>
    
    
    
    <category term="SpringBoot" scheme="https://kon-forever.cloud/categories/SpringBoot/"/>
    
    
    <category term="MyBatis-Plus" scheme="https://kon-forever.cloud/tags/MyBatis-Plus/"/>
    
    <category term="MyBatis" scheme="https://kon-forever.cloud/tags/MyBatis/"/>
    
  </entry>
  
  <entry>
    <title>SSM学习笔记之 SpringBoot</title>
    <link href="https://kon-forever.cloud/2026/03/20/SSM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BSpringBoot/"/>
    <id>https://kon-forever.cloud/2026/03/20/SSM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BSpringBoot/</id>
    <published>2026-03-20T03:00:00.000Z</published>
    <updated>2026-03-20T03:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="SpringBoot简介"><a href="#SpringBoot简介" class="headerlink" title="SpringBoot简介"></a>SpringBoot简介</h3><h4 id="SpringBoot-快速入门"><a href="#SpringBoot-快速入门" class="headerlink" title="SpringBoot 快速入门"></a>SpringBoot 快速入门</h4><p><strong>对比</strong></p><img src="https://i-blog.csdnimg.cn/direct/c30d1742896d4669838e63281731f182.png" alt="img" style="zoom:67%;" /><ul><li><p><strong>坐标</strong></p><p><code>Spring</code> 程序中的坐标需要自己编写，而且坐标非常多</p><p><code>SpringBoot</code> 程序中的坐标是我们在创建工程时进行勾选自动生成的</p></li><li><p><strong>web3.0配置类</strong></p><p><code>Spring</code> 程序需要自己编写这个配置类。这个配置类之前编写过，很复杂</p><p><code>SpringBoot</code> 程序不需要我们自己书写</p></li><li><p><strong>配置类</strong></p><p><code>Spring/SpringMVC</code> 程序的配置类需要自己书写。而 <code>SpringBoot</code> 程序则不需要书写。</p></li></ul><h3 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h3><h4 id="配置文件格式"><a href="#配置文件格式" class="headerlink" title="配置文件格式"></a>配置文件格式</h4><h6 id="application-properties配置文件"><a href="#application-properties配置文件" class="headerlink" title="application.properties配置文件"></a><strong>application.properties配置文件</strong></h6><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">server.port=80</span><br></pre></td></tr></table></figure><h5 id="application-yml配置文件（推荐）"><a href="#application-yml配置文件（推荐）" class="headerlink" title="application.yml配置文件（推荐）"></a><strong>application.yml配置文件（推荐）</strong></h5><figure class="highlight xml"><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">server:</span><br><span class="line">port: 81</span><br></pre></td></tr></table></figure><p>注意： 在<code>:</code>后，数据前一定要加空格。</p><h5 id="application-yaml配置文件"><a href="#application-yaml配置文件" class="headerlink" title="application.yaml配置文件"></a>application.yaml配置文件</h5><figure class="highlight xml"><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">server:</span><br><span class="line">port: 83</span><br></pre></td></tr></table></figure><h4 id="三种配合文件的优先级"><a href="#三种配合文件的优先级" class="headerlink" title="三种配合文件的优先级"></a>三种配合文件的优先级</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">application.properties` &gt; `application.yml` &gt; `application.yaml</span><br></pre></td></tr></table></figure><h4 id="yaml-格式"><a href="#yaml-格式" class="headerlink" title="yaml 格式"></a>yaml 格式</h4><h5 id="语法规则"><a href="#语法规则" class="headerlink" title="语法规则"></a>语法规则</h5><ul><li><p>大小写敏感</p></li><li><p>属性层级关系使用多行描述，每行结尾使用冒号结束</p></li><li><p>使用缩进表示层级关系，同层级左侧对齐，只允许使用空格（不允许使用Tab键）</p><p>空格的个数并不重要，只要保证同层级的左侧对齐即可。</p></li><li><p><strong>属性值前面添加空格</strong>（属性名与属性值之间使用冒号+空格作为分隔）</p></li><li><p># 表示注释</p></li></ul><p>(2) 数组数据在数据书写位置的下方使用减号作为数据开始符号，每行书写一个数据，减号与数据间空格分隔，例如：（了解即可）</p><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></pre></td><td class="code"><pre><span class="line">enterprise:</span><br><span class="line">  name: itcast</span><br><span class="line">  age: 16</span><br><span class="line">  tel: 4006184000</span><br><span class="line">  subject:</span><br><span class="line">    - Java</span><br><span class="line">    - 前端</span><br><span class="line">    - 大数据</span><br></pre></td></tr></table></figure><h4 id="yaml配置文件数据读取"><a href="#yaml配置文件数据读取" class="headerlink" title="yaml配置文件数据读取"></a>yaml配置文件数据读取</h4><h5 id="使用-Value注解"><a href="#使用-Value注解" class="headerlink" title="使用 @Value注解"></a>使用 @Value注解</h5><p>使用 <code>@Value(&quot;表达式&quot;)</code> 注解可以从配置文件中读取数据，注解中用于读取属性名引用方式是：<code>${一级属性名.二级属性名……}</code></p><p>我们可以在 <code>BookController</code> 中使用 <code>@Value</code> 注解读取配置文件数据，如下</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">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/books&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;lesson&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String lesson;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;server.port&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Integer port;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;enterprise.subject[0]&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String subject_00;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span>&#123;</span><br><span class="line">        System.out.println(lesson);</span><br><span class="line">        System.out.println(port);</span><br><span class="line">        System.out.println(subject_00);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;hello , spring boot!&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="自定义对象（常用）"><a href="#自定义对象（常用）" class="headerlink" title="自定义对象（常用）"></a>自定义对象（常用）</h5><p>① <code>SpringBoot</code> 还提供了将配置文件中的数据封装到我们自定义的实体类对象中的方式。具体操作如下：</p><ul><li><p>将实体类 <code>bean</code> 的创建交给 <code>Spring</code> 管理。</p></li><li><p>在类上添加 <code>@Component</code> 注解</p></li><li><p>使用 <code>@ConfigurationProperties</code> 注解表示加载配置文件</p><p>在该注解中也可以使用 <code>prefix</code> 属性指定只加载指定前缀的数据</p></li><li><p>在 <code>BookController</code> 中进行注入</p></li></ul><p>② 代码示例</p><p><code>Enterprise</code> 实体类内容如下：</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">@Component</span></span><br><span class="line"><span class="meta">@ConfigurationProperties(prefix = &quot;enterprise&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Enterprise</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line">    <span class="keyword">private</span> String tel;</span><br><span class="line">    <span class="keyword">private</span> String[] subject;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getTel</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> tel;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setTel</span><span class="params">(String tel)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.tel = tel;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String[] getSubject() &#123;</span><br><span class="line">        <span class="keyword">return</span> subject;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSubject</span><span class="params">(String[] subject)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.subject = subject;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Enterprise&#123;&quot;</span> +</span><br><span class="line">                <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                <span class="string">&quot;, age=&quot;</span> + age +</span><br><span class="line">                <span class="string">&quot;, tel=&#x27;&quot;</span> + tel + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                <span class="string">&quot;, subject=&quot;</span> + Arrays.toString(subject) +</span><br><span class="line">                <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="多环境配置"><a href="#多环境配置" class="headerlink" title="多环境配置"></a>多环境配置</h4><h5 id="yaml文件"><a href="#yaml文件" class="headerlink" title="yaml文件"></a>yaml文件</h5><p>① 在 <code>application.yml</code> 中使用 <code>---</code> 来分割不同的配置，内容如下：</p><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><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><br><span class="line">spring:</span><br><span class="line">  profiles: dev #给开发环境起的名字</span><br><span class="line">server:</span><br><span class="line">  port: 80</span><br><span class="line">---</span><br><span class="line">#生产</span><br><span class="line">spring:</span><br><span class="line">  profiles: pro #给生产环境起的名字</span><br><span class="line">server:</span><br><span class="line">  port: 81</span><br><span class="line">---</span><br><span class="line">#测试</span><br><span class="line">spring:</span><br><span class="line">  profiles: test #给测试环境起的名字</span><br><span class="line">server:</span><br><span class="line">  port: 82</span><br><span class="line">---</span><br></pre></td></tr></table></figure><p>② 上面配置中 <code>spring.profiles</code> 是用来给不同的配置起名字的。而如何告知 <code>SpringBoot</code> 使用哪段配置呢？可以使用如下配置来启用那一段配置</p><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></pre></td><td class="code"><pre><span class="line">#设置启用的环境</span><br><span class="line">spring:</span><br><span class="line">  profiles:</span><br><span class="line">    active: dev  #表示使用的是开发环境的配置</span><br></pre></td></tr></table></figure><p>③ 综上所述，<code>application.yml</code> 配置文件内容如下</p><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><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></pre></td><td class="code"><pre><span class="line">#设置启用的环境</span><br><span class="line">spring:</span><br><span class="line">  profiles:</span><br><span class="line">    active: dev</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line">#开发</span><br><span class="line">spring:</span><br><span class="line">  profiles: dev</span><br><span class="line">server:</span><br><span class="line">  port: 80</span><br><span class="line">---</span><br><span class="line">#生产</span><br><span class="line">spring:</span><br><span class="line">  profiles: pro</span><br><span class="line">server:</span><br><span class="line">  port: 81</span><br><span class="line">---</span><br><span class="line">#测试</span><br><span class="line">spring:</span><br><span class="line">  profiles: test</span><br><span class="line">server:</span><br><span class="line">  port: 82</span><br><span class="line">---</span><br></pre></td></tr></table></figure><h5 id="命令行启动参数设置"><a href="#命令行启动参数设置" class="headerlink" title="命令行启动参数设置"></a>命令行启动参数设置</h5><p>① 这种方式显然有点麻烦，而 <code>SpringBoot</code> 提供了在运行 <code>jar</code> 时设置开启指定的环境的方式，如下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java –jar xxx.jar –-spring.profiles.active=<span class="built_in">test</span></span><br></pre></td></tr></table></figure><p>② 这种方式也可以临时修改端口号，通过如下方式</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java –jar xxx.jar –-server.port=88</span><br></pre></td></tr></table></figure><h3 id="SpringBoot整合第三方技术"><a href="#SpringBoot整合第三方技术" class="headerlink" title="SpringBoot整合第三方技术"></a>SpringBoot整合第三方技术</h3><h4 id="SpringBoot-整合-junit"><a href="#SpringBoot-整合-junit" class="headerlink" title="SpringBoot 整合 junit"></a>SpringBoot 整合 junit</h4><p>(1) 在 <code>com.itheima.service</code> 下创建 <code>BookService</code> 接口，内容如下</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(2) 在 <code>com.itheima.service.impl</code> 包写创建一个 <code>BookServiceImpl</code> 类，使其实现 <code>BookService</code> 接口，内容如下</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book service is running ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(3) 在 <code>test/java</code> 下创建 <code>com.itheima</code> 包，在该包下创建测试类，将 <code>BookService</code> 注入到该测试类中</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Springboot07TestApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> BookService bookService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        bookService.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="SpringBoot-整合-MyBatis"><a href="#SpringBoot-整合-MyBatis" class="headerlink" title="SpringBoot 整合 MyBatis"></a>SpringBoot 整合 MyBatis</h4><h5 id="操作步骤"><a href="#操作步骤" class="headerlink" title="操作步骤"></a>操作步骤</h5><p>① 定义实体类</p><p>在 <code>com.itheima.domain</code> 包下定义实体类 <code>Book</code>，内容如下</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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Book</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String type;</span><br><span class="line">    <span class="keyword">private</span> String description;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//setter and  getter</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">//toString</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② 定义dao接口</p><p>在 <code>com.itheima.dao</code> 包下定义 <code>BookDao</code> 接口，内容如下</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="meta">@Select(&quot;select * from tbl_book where id = #&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Book <span class="title function_">getById</span><span class="params">(Integer id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>1). 一定要加上 @Mapper，否则会显示在 <code>Spring</code> 容器中没有 <code>BookDao</code> 类型的 <code>bean</code>。</p><p>2). <code>@Mapper</code> 就是用来 “通知” MyBatis 的 ——<strong>给接口打上 <code>@Mapper</code> 注解，就告诉 MyBatis：“这是一个 DAO 接口，帮我生成代理实现类，交给 Spring 管理”</strong>。</p><p>③ 定义测试类</p><p>在 <code>test/java</code> 下定义包 <code>com.itheima</code> ，在该包下测试类，内容如下</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="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Springboot08MybatisApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> BookDao bookDao;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testGetById</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="type">Book</span> <span class="variable">book</span> <span class="operator">=</span> bookDao.getById(<span class="number">1</span>);</span><br><span class="line">System.out.println(book);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h3><h4 id="静态资源"><a href="#静态资源" class="headerlink" title="静态资源"></a>静态资源</h4><p>在 <code>SpringBoot</code> 程序中是没有 <code>webapp</code> 目录的，那么在 <code>SpringBoot</code> 程序中静态资源需要放在什么位置呢？</p><p>静态资源需要放在 <code>resources</code> 下的 <code>static</code> 下，如下图所示</p><p><img src="https://i-blog.csdnimg.cn/direct/ea84db26d274410bbcb908f4204c56cc.png" alt="img"></p>]]></content>
    
    
    <summary type="html">Spring Boot 简介、配置文件与 yaml</summary>
    
    
    
    <category term="SpringBoot" scheme="https://kon-forever.cloud/categories/SpringBoot/"/>
    
    
    <category term="Spring" scheme="https://kon-forever.cloud/tags/Spring/"/>
    
    <category term="Spring Boot" scheme="https://kon-forever.cloud/tags/Spring-Boot/"/>
    
    <category term="Java" scheme="https://kon-forever.cloud/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>SSM学习笔记之 SpringMVC</title>
    <link href="https://kon-forever.cloud/2026/03/20/SSM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BSpringMVC/"/>
    <id>https://kon-forever.cloud/2026/03/20/SSM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BSpringMVC/</id>
    <published>2026-03-20T02:00:00.000Z</published>
    <updated>2026-03-20T02:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="SpringMVC学习笔记"><a href="#SpringMVC学习笔记" class="headerlink" title="SpringMVC学习笔记"></a>SpringMVC学习笔记</h2><h3 id="SpringMVC入门案例"><a href="#SpringMVC入门案例" class="headerlink" title="SpringMVC入门案例"></a>SpringMVC入门案例</h3><h4 id="TomCat-的作用"><a href="#TomCat-的作用" class="headerlink" title="TomCat 的作用"></a>TomCat 的作用</h4><h5 id="先搞懂核心定位（通俗比喻）"><a href="#先搞懂核心定位（通俗比喻）" class="headerlink" title="先搞懂核心定位（通俗比喻）"></a>先搞懂核心定位（通俗比喻）</h5><p>把整个 Web 应用比作 “餐厅”</p><ul><li><strong>浏览器</strong> &#x3D; 顾客（发起点餐请求）；</li><li><strong>Tomcat</strong> &#x3D; 餐厅的「前台 + 传菜员 + 机房」：负责接待顾客、解析点餐需求、把需求传给后厨，再把做好的餐品送回顾客；</li><li><strong>SpringMVC</strong> &#x3D; 餐厅的「后厨（控制器 &#x2F; 服务 &#x2F; DAO）」：负责处理具体的 “点餐需求”（业务逻辑），但后厨不能直接面对顾客，必须通过前台（Tomcat）衔接。</li></ul><h5 id="Tomcat-在-SpringMVC-中的核心作用"><a href="#Tomcat-在-SpringMVC-中的核心作用" class="headerlink" title="Tomcat 在 SpringMVC 中的核心作用"></a>Tomcat 在 SpringMVC 中的核心作用</h5><p>Tomcat 首先是一个<strong>轻量级 Web 服务器</strong>，负责处理最底层的网络通信：</p><ul><li>监听指定端口（默认 8080），接收浏览器发来的 HTTP 请求（比如 GET&#x2F;POST）；</li><li>解析 HTTP 请求的格式（比如请求头、请求参数、请求体），把杂乱的 HTTP 字节流转换成 Java 能识别的对象 ——<code>HttpServletRequest</code>（请求对象）和<code>HttpServletResponse</code>（响应对象）；</li><li>处理完请求后，把 SpringMVC 返回的响应数据（比如页面、JSON）重新封装成 HTTP 格式，发送回浏览器。</li></ul><h4 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h4><h5 id="创建配置类"><a href="#创建配置类" class="headerlink" title="创建配置类"></a>创建配置类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima.controller&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringMvcConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="创建Controller类"><a href="#创建Controller类" class="headerlink" title="创建Controller类"></a>创建Controller类</h5><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">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/save&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;user save ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="使用配置类替换web-xml"><a href="#使用配置类替换web-xml" class="headerlink" title="使用配置类替换web.xml"></a>使用配置类替换web.xml</h5><p>将web.xml删除，换成ServletContainersInitConfig</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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ServletContainersInitConfig</span> <span class="keyword">extends</span> <span class="title class_">AbstractDispatcherServletInitializer</span> &#123;</span><br><span class="line">    <span class="comment">//加载springmvc配置类</span></span><br><span class="line">    <span class="keyword">protected</span> WebApplicationContext <span class="title function_">createServletApplicationContext</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//初始化WebApplicationContext对象</span></span><br><span class="line">        <span class="type">AnnotationConfigWebApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigWebApplicationContext</span>();</span><br><span class="line">        <span class="comment">//加载指定配置类</span></span><br><span class="line">        ctx.register(SpringMvcConfig.class);</span><br><span class="line">        <span class="keyword">return</span> ctx;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//设置由springmvc控制器处理的请求映射路径</span></span><br><span class="line">    <span class="keyword">protected</span> String[] getServletMappings() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;/&quot;</span>&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//加载spring配置类</span></span><br><span class="line">    <span class="keyword">protected</span> WebApplicationContext <span class="title function_">createRootApplicationContext</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>核心逻辑：</strong></p><p><strong>① createServletApplicationContext ()：创建 SpringMVC 专属的 IOC 容器</strong></p><ul><li><strong>作用</strong>：创建并返回一个<strong>Spring MVC 专属的 Web 应用上下文</strong>（IOC 容器）。</li><li>这个上下文专门用来加载 Spring MVC 相关的 Bean，比如控制器（<code>@Controller</code>）、视图解析器、处理器映射等。</li><li><code>AnnotationConfigWebApplicationContext</code> 表示这个容器是基于<strong>Java 注解</strong>来配置的，而不是 XML。</li></ul><p><strong>② getServletMappings ()：指定 SpringMVC 处理哪些请求</strong></p><ul><li><strong>作用</strong>：指定 <code>DispatcherServlet</code> 要处理哪些 URL 请求。</li><li>返回 <code>&quot;/&quot;</code> 意味着它会处理所有进入应用的请求。</li></ul><h4 id="bean加载控制"><a href="#bean加载控制" class="headerlink" title="bean加载控制"></a>bean加载控制</h4><h5 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h5><p>加载Spring控制的bean的时候排除掉SpringMVC控制的bean，具体该如何排除</p><p>方式一：Spring加载的bean设定扫描范围为精准范围，例如service包、dao包等</p><p>方式二：Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean</p><h5 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 方式一：修改Spring配置类，设定扫描范围为精准范围</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&#123;&quot;com.itheima.service&quot;,&quot;comitheima.dao&quot;&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>上述只是通过例子说明可以精确指定让Spring扫描对应的包结构，真正在做开发的时候，因为Dao最终是交给<code>MapperScannerConfigurer</code>对象来进行扫描处理的，我们只需要将其扫描到service包即可。</p><p><strong>② 方式二：修改Spring配置类，设定扫描范围为com.itheima，排除掉controller包中的bean</strong></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="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(value=&quot;com.itheima&quot;,</span></span><br><span class="line"><span class="meta">    excludeFilters=@ComponentScan.Filter(</span></span><br><span class="line"><span class="meta">    type = FilterType.ANNOTATION,</span></span><br><span class="line"><span class="meta">        classes = Controller.class</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>excludeFilters属性：设置扫描加载bean时，排除的过滤规则</li><li>type属性：设置排除规则，当前使用按照bean定义时的注解类型进行排除</li><li>classes属性：设置排除的具体注解类，当前设置排除@Controller定义的bean</li></ul><p><strong>③ 测试类</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">App</span>&#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span> <span class="params">(String[] args)</span>&#123;</span><br><span class="line">        <span class="type">AnnotationConfigApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(SpringConfig.class);</span><br><span class="line">        System.out.println(ctx.getBean(UserController.class));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>1). 注意：</strong></p><p>测试的时候，需要把SpringMvcConfig配置类上的@ComponentScan注解注释掉，否则不会报错。因为按照上述的要求，应该是SpringMvcConfig加载Controller，而现在注释掉了@ComponentScan，就会导致SpringMvcConfig无法指定Bean的扫描路径，导致Controller类无法被加载到IoC容器中。</p><p><strong>2). 流程如下：</strong></p><ul><li>**如果不注释 <code>SpringMvcConfig</code> 上的 <code>@ComponentScan</code>：**Spring 容器加载 <code>SpringConfig</code> → 扫描到 <code>SpringMvcConfig</code> → 执行 <code>SpringMvcConfig</code> 的扫描 → Controller 被加载到根容器 → <code>ctx.getBean(UserController.class)</code> 能找到 Bean → 测试失败，看不到 “被排除” 的效果。</li><li>**如果注释掉 <code>SpringMvcConfig</code> 上的 <code>@ComponentScan</code>：**Spring 容器加载 <code>SpringConfig</code> → 扫描到 <code>SpringMvcConfig</code>，但它没有扫描动作 → Controller 没有被加载 → <code>ctx.getBean(UserController.class)</code> 抛出 <code>NoSuchBeanDefinitionException</code> → 测试成功，演示了 “被排除” 的效果。</li></ul><h5 id="代码优化"><a href="#代码优化" class="headerlink" title="代码优化"></a>代码优化</h5><p><strong>ServletContainerInitConfig</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ServletContainersInitConfig</span> <span class="keyword">extends</span> <span class="title class_">AbstractAnnotationConfigDispatcherServletInitializer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt;[] getRootConfigClasses() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Class</span>[]&#123;SpringConfig.class&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt;[] getServletConfigClasses() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Class</span>[]&#123;SpringMvcConfig.class&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> String[] getServletMappings() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;/&quot;</span>&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="请求与响应"><a href="#请求与响应" class="headerlink" title="请求与响应"></a>请求与响应</h3><h4 id="请求参数"><a href="#请求参数" class="headerlink" title="请求参数"></a>请求参数</h4><h5 id="参数传递"><a href="#参数传递" class="headerlink" title="参数传递"></a>参数传递</h5><p><strong>① GET请求中文乱码</strong></p><p>如果我们传递的参数中有中文，你会发现接收到的参数会出现中文乱码问题。</p><p><strong>② 解决方案：配置过滤器</strong></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">class</span> <span class="title class_">ServletContainersInitConfig</span> <span class="keyword">extends</span> <span class="title class_">AbstractAnnotationConfigDispatcherServletInitializer</span> &#123;</span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt;[] getRootConfigClasses() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Class</span>[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt;[] getServletConfigClasses() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Class</span>[]&#123;SpringMvcConfig.class&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> String[] getServletMappings() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;/&quot;</span>&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></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="keyword">protected</span> Filter[] getServletFilters() &#123;</span><br><span class="line">        <span class="type">CharacterEncodingFilter</span> <span class="variable">filter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CharacterEncodingFilter</span>();</span><br><span class="line">        filter.setEncoding(<span class="string">&quot;UTF-8&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Filter</span>[]&#123;filter&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><strong><code>getServletFilters()</code></strong>：这是 <code>AbstractAnnotationConfigDispatcherServletInitializer</code> 提供的扩展点，用来注册 Servlet 过滤器。</li><li><strong><code>CharacterEncodingFilter</code></strong>：Spring 内置的过滤器，专门处理字符编码问题。</li><li><strong><code>setEncoding(&quot;UTF-8&quot;)</code></strong>：明确告诉过滤器，所有请求和响应都用 UTF-8 编码。</li></ul><h5 id="五种类型参数传递"><a href="#五种类型参数传递" class="headerlink" title="五种类型参数传递"></a>五种类型参数传递</h5><p><strong>① 如果形参与地址参数名不一致该如何解决?</strong></p><p>1). 发送请求与参数：</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">http:<span class="comment">//localhost/commonParamDifferentName?name=张三&amp;age=18</span></span><br></pre></td></tr></table></figure><p>2). 解决方案:使用@RequestParam注解</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(&quot;/commonParamDifferentName&quot;)</span></span><br><span class="line">    <span class="meta">@ResponseBody</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">commonParamDifferentName</span><span class="params">(<span class="meta">@RequestPaam(&quot;name&quot;)</span> String userName , <span class="type">int</span> age)</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;普通参数传递 userName &gt; &quot;</span>+userName);</span><br><span class="line">        System.out.println(<span class="string">&quot;普通参数传递 age &gt; &quot;</span>+age);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;common param different name&#x27;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><strong>② POJO数据类型</strong></p><p>1). POJO参数：请求参数名与形参对象属性名相同，定义POJO类型形参即可接收参数</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line">    <span class="comment">//setter...getter...略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2). 后台接收参数：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//POJO参数：请求参数与形参对象中的属性对应即可完成参数传递</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/pojoParam&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">pojoParam</span><span class="params">(User user)</span>&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;pojo参数传递 user &gt; &quot;</span>+user);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;pojo param&#x27;&#125;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>bb</p><p>1). 如果POJO对象中嵌套了其他的POJO类，如</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Address</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String province;</span><br><span class="line">    <span class="keyword">private</span> String city;</span><br><span class="line">    <span class="comment">//setter...getter...略</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line">    <span class="keyword">private</span> Address address;</span><br><span class="line">    <span class="comment">//setter...getter...略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>嵌套POJO参数：请求参数名与形参对象属性名相同，按照对象层次结构关系即可接收嵌套POJO属性参数</li></ul><p>2). 发送请求和参数</p><img src="https://i-blog.csdnimg.cn/direct/e65a855d212c4484a1a85aa1cee6cd70.png" alt="img" style="zoom:67%;" /><p><strong>④ 集合类型参数</strong></p><p><strong>1). 存在的问题</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">//集合参数：同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/listParam&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">listParam</span><span class="params">(List&lt;String&gt; likes)</span>&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;集合参数传递 likes &gt; &quot;</span>+ likes);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;list param&#x27;&#125;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>错误的原因是：SpringMVC将List看做是一个POJO对象来处理，将其创建一个对象并准备把前端的数据封装到对象中，但是List是一个接口无法创建对象，所以报错。</p><p><strong>2). 解决方案是使用<code>@RequestParam</code>注解</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">//集合参数：同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/listParam&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">listParam</span><span class="params">(<span class="meta">@RequestParam</span> List&lt;String&gt; likes)</span>&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;集合参数传递 likes &gt; &quot;</span>+ likes);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;list param&#x27;&#125;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>集合保存普通参数：请求参数名与形参集合对象名相同且请求参数为多个，@RequestParam绑定参数关系</li></ul><h4 id="JSON数据传输参数"><a href="#JSON数据传输参数" class="headerlink" title="JSON数据传输参数"></a>JSON数据传输参数</h4><h5 id="JSON普通数组"><a href="#JSON普通数组" class="headerlink" title="JSON普通数组"></a>JSON普通数组</h5><p><strong>① 开启SpringMVC注解支持</strong> </p><p>在SpringMVC的配置类中开启SpringMVC的注解支持，这里面就包含了将JSON转换成对象的功能。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima.controller&quot;)</span></span><br><span class="line"><span class="comment">//开启json数据类型自动转换</span></span><br><span class="line"><span class="meta">@EnableWebMvc</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringMvcConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 参数前添加@RequestBody</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">//使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/listParamForJson&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">listParamForJson</span><span class="params">(<span class="meta">@RequestBody</span> List&lt;String&gt; likes)</span>&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;list common(json)参数传递 list &gt; &quot;</span>+likes);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;list common for json param&#x27;&#125;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="知识点"><a href="#知识点" class="headerlink" title="知识点"></a>知识点</h5><p><strong>① @EnableWebMvc</strong></p><table><thead><tr><th>名称</th><th>@EnableWebMvc</th></tr></thead><tbody><tr><td>类型</td><td>配置类注解</td></tr><tr><td>位置</td><td>SpringMVC配置类定义上方</td></tr><tr><td>作用</td><td>开启SpringMVC多项辅助功能</td></tr></tbody></table><p>② @RequestBody</p><table><thead><tr><th>名称</th><th>@RequestBody</th></tr></thead><tbody><tr><td>类型</td><td>形参注解</td></tr><tr><td>位置</td><td>SpringMVC控制器方法形参定义前面</td></tr><tr><td>作用</td><td>将请求中请求体所包含的数据传递给请求参数，此注解一个处理器方法只能使用一次</td></tr></tbody></table><h4 id="日期类型参数传递"><a href="#日期类型参数传递" class="headerlink" title="日期类型参数传递"></a>日期类型参数传递</h4><h5 id="携带时间的日期"><a href="#携带时间的日期" class="headerlink" title="携带时间的日期"></a>携带时间的日期</h5><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">@RequestMapping(&quot;/dataParam&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">dataParam</span><span class="params">(Date date,</span></span><br><span class="line"><span class="params">                        <span class="meta">@DateTimeFormat(pattern=&quot;yyyy-MM-dd&quot;)</span> Date date1,</span></span><br><span class="line"><span class="params">                        <span class="meta">@DateTimeFormat(pattern=&quot;yyyy/MM/dd HH:mm:ss&quot;)</span> Date date2)</span></span><br><span class="line">    System.out.println(<span class="string">&quot;参数传递 date &gt; &quot;</span>+date);</span><br><span class="line">System.out.println(<span class="string">&quot;参数传递 date1(yyyy-MM-dd) &gt; &quot;</span>+date1);</span><br><span class="line">System.out.println(<span class="string">&quot;参数传递 date2(yyyy/MM/dd HH:mm:ss) &gt; &quot;</span>+date2);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;data param&#x27;&#125;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="知识点-1"><a href="#知识点-1" class="headerlink" title="知识点"></a>知识点</h5><p>① @DateTimeFormat</p><table><thead><tr><th>名称</th><th>@DateTimeFormat</th></tr></thead><tbody><tr><td>类型</td><td>形参注解</td></tr><tr><td>位置</td><td>SpringMVC控制器方法形参前面</td></tr><tr><td>作用</td><td>设定日期时间型数据格式</td></tr><tr><td>相关属性</td><td>pattern：指定日期时间格式字符串</td></tr></tbody></table><h4 id="响应"><a href="#响应" class="headerlink" title="响应"></a>响应</h4><h5 id="响应JSON数据"><a href="#响应JSON数据" class="headerlink" title="响应JSON数据"></a>响应JSON数据</h5><p><strong>① 响应POJO对象</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/toJsonPOJO&quot;)</span></span><br><span class="line">    <span class="meta">@ResponseBody</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">toJsonPOJO</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;返回json对象数据&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user.setName(<span class="string">&quot;itcast&quot;</span>);</span><br><span class="line">        user.setAge(<span class="number">15</span>);</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>返回值为实体类对象，设置返回值为实体类类型，即可实现返回对应对象的json数据，需要依赖@ResponseBody注解和@EnableWebMvc注解</p><p><strong>② 响应POJO集合对象</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/toJsonList&quot;)</span></span><br><span class="line">    <span class="meta">@ResponseBody</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;User&gt; <span class="title function_">toJsonList</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;返回json集合数据&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">user1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user1.setName(<span class="string">&quot;传智播客&quot;</span>);</span><br><span class="line">        user1.setAge(<span class="number">15</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">User</span> <span class="variable">user2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user2.setName(<span class="string">&quot;黑马程序员&quot;</span>);</span><br><span class="line">        user2.setAge(<span class="number">12</span>);</span><br><span class="line"></span><br><span class="line">        List&lt;User&gt; userList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;User&gt;();</span><br><span class="line">        userList.add(user1);</span><br><span class="line">        userList.add(user2);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> userList;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="知识点-2"><a href="#知识点-2" class="headerlink" title="知识点"></a>知识点</h5><table><thead><tr><th>名称</th><th>@ResponseBody</th></tr></thead><tbody><tr><td>类型</td><td>方法\类注解</td></tr><tr><td>位置</td><td>SpringMVC控制器方法定义上方和控制类上</td></tr><tr><td>作用</td><td>设置当前控制器返回值作为响应体, 写在类上，该类的所有方法都有该注解功能</td></tr><tr><td>相关属性</td><td>pattern：指定日期时间格式字符串</td></tr></tbody></table><p><strong>说明：</strong></p><p>当方法上有@ReponseBody注解后</p><ul><li><strong>方法的返回值为字符串，会将其作为文本内容直接响应给前端</strong></li><li><strong>方法的返回值为对象，会将对象转换成JSON响应给前端</strong></li></ul><h3 id="Rest-风格"><a href="#Rest-风格" class="headerlink" title="Rest 风格"></a>Rest 风格</h3><h4 id="请求方式"><a href="#请求方式" class="headerlink" title="请求方式"></a>请求方式</h4><p>按照不同的请求方式代表不同的操作类型：</p><ul><li>发送GET请求是用来做<strong>查询</strong></li><li>发送POST请求是用来做<strong>新增</strong></li><li>发送PUT请求是用来做<strong>修改</strong></li><li>发送DELETE请求是用来做<strong>删除</strong></li></ul><h4 id="RESTful-快速开发"><a href="#RESTful-快速开发" class="headerlink" title="RESTful 快速开发"></a>RESTful 快速开发</h4><h5 id="知识点-3"><a href="#知识点-3" class="headerlink" title="知识点"></a>知识点</h5><p>① @PathVariable</p><table><thead><tr><th>名称</th><th>@PathVariable</th></tr></thead><tbody><tr><td>类型</td><td>形参注解</td></tr><tr><td>位置</td><td>SpringMVC控制器方法形参定义前面</td></tr><tr><td>作用</td><td>绑定路径参数与处理器方法形参间的关系，要求路径参数名与形参名一一对应</td></tr></tbody></table><p>② @RestController</p><table><thead><tr><th>名称</th><th>@RestController</th></tr></thead><tbody><tr><td>类型</td><td>类注解</td></tr><tr><td>位置</td><td>基于SpringMVC的RESTful开发控制器类定义上方</td></tr><tr><td>作用</td><td>设置当前控制器类为RESTful风格， 等同于@Controller与@ResponseBody两个注解组合功能</td></tr></tbody></table><p>③ @GetMapping @PostMapping @PutMapping @DeleteMapping</p><table><thead><tr><th>名称</th><th>@GetMapping @PostMapping @PutMapping @DeleteMapping</th></tr></thead><tbody><tr><td>类型</td><td>方法注解</td></tr><tr><td>位置</td><td>基于SpringMVC的RESTful开发控制器方法定义上方</td></tr><tr><td>作用</td><td>设置当前控制器方法请求访问路径与请求动作，每种对应一个请求动作， 例如@GetMapping对应GET请求</td></tr><tr><td>相关属性</td><td>value（默认）：请求访问路径</td></tr></tbody></table><h5 id="代码实现-2"><a href="#代码实现-2" class="headerlink" title="代码实现"></a>代码实现</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span> <span class="comment">//@Controller + ReponseBody</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/books&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookController</span> &#123;</span><br><span class="line">    </span><br><span class="line"><span class="comment">//@RequestMapping(method = RequestMethod.POST)</span></span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">save</span><span class="params">(<span class="meta">@RequestBody</span> Book book)</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book save...&quot;</span> + book);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;book save&#x27;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//@RequestMapping(value = &quot;/&#123;id&#125;&quot;,method = RequestMethod.DELETE)</span></span><br><span class="line">    <span class="meta">@DeleteMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">delete</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book delete...&quot;</span> + id);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;book delete&#x27;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//@RequestMapping(method = RequestMethod.PUT)</span></span><br><span class="line">    <span class="meta">@PutMapping</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">update</span><span class="params">(<span class="meta">@RequestBody</span> Book book)</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book update...&quot;</span> + book);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;book update&#x27;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//@RequestMapping(value = &quot;/&#123;id&#125;&quot;,method = RequestMethod.GET)</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book getById...&quot;</span> + id);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;book getById&#x27;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//@RequestMapping(method = RequestMethod.GET)</span></span><br><span class="line">    <span class="meta">@GetMapping</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getAll</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book getAll...&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;book getAll&#x27;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="RESTful案例"><a href="#RESTful案例" class="headerlink" title="RESTful案例"></a>RESTful案例</h4><h5 id="代码实现-3"><a href="#代码实现-3" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 编写Controller类并使用RESTful进行配置</strong></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">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/books&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">save</span><span class="params">(<span class="meta">@RequestBody</span> Book book)</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book save &gt; &quot;</span>+ book);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&#123;&#x27;module&#x27;:&#x27;book save success&#x27;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Book&gt; <span class="title function_">getAll</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book getAll is running ...&quot;</span>);</span><br><span class="line">        List&lt;Book&gt; bookList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Book&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="type">Book</span> <span class="variable">book1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Book</span>();</span><br><span class="line">        book1.setType(<span class="string">&quot;计算机&quot;</span>);</span><br><span class="line">        book1.setName(<span class="string">&quot;SpringMVC入门教程&quot;</span>);</span><br><span class="line">        book1.setDescription(<span class="string">&quot;小试牛刀&quot;</span>);</span><br><span class="line">        bookList.add(book1);</span><br><span class="line"></span><br><span class="line">        <span class="type">Book</span> <span class="variable">book2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Book</span>();</span><br><span class="line">        book2.setType(<span class="string">&quot;计算机&quot;</span>);</span><br><span class="line">        book2.setName(<span class="string">&quot;SpringMVC实战教程&quot;</span>);</span><br><span class="line">        book2.setDescription(<span class="string">&quot;一代宗师&quot;</span>);</span><br><span class="line">        bookList.add(book2);</span><br><span class="line"></span><br><span class="line">        <span class="type">Book</span> <span class="variable">book3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Book</span>();</span><br><span class="line">        book3.setType(<span class="string">&quot;计算机丛书&quot;</span>);</span><br><span class="line">        book3.setName(<span class="string">&quot;SpringMVC实战教程进阶&quot;</span>);</span><br><span class="line">        book3.setDescription(<span class="string">&quot;一代宗师呕心创作&quot;</span>);</span><br><span class="line">        bookList.add(book3);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> bookList;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="页面访问处理"><a href="#页面访问处理" class="headerlink" title="页面访问处理"></a>页面访问处理</h5><p><strong>① 存在的问题</strong></p><p>打开浏览器输入<code>http://localhost/pages/books.html</code></p><img src="https://i-blog.csdnimg.cn/direct/5c66846e2a9c433ebe1a8007b6646306.png" alt="img" style="zoom:67%;" /><p><strong>② 解决方案</strong></p><p><strong>1). 出现错误的原因</strong></p><img src="https://i-blog.csdnimg.cn/direct/b3d5ce2f82984670a05db98e7da5d768.png" alt="img" style="zoom:67%;" /><p><strong>2). 代码实现</strong></p><ul><li>SpringMVC需要将静态资源进行放行。</li></ul><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="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringMvcSupport</span> <span class="keyword">extends</span> <span class="title class_">WebMvcConfigurationSupport</span> &#123;</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="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> &#123;</span><br><span class="line">        <span class="comment">//当访问/pages/????时候，从/pages目录下查找内容</span></span><br><span class="line">        registry.addResourceHandler(<span class="string">&quot;/pages/**&quot;</span>).addResourceLocations(<span class="string">&quot;/pages/&quot;</span>);</span><br><span class="line">        registry.addResourceHandler(<span class="string">&quot;/js/**&quot;</span>).addResourceLocations(<span class="string">&quot;/js/&quot;</span>);</span><br><span class="line">        registry.addResourceHandler(<span class="string">&quot;/css/**&quot;</span>).addResourceLocations(<span class="string">&quot;/css/&quot;</span>);</span><br><span class="line">        registry.addResourceHandler(<span class="string">&quot;/plugins/**&quot;</span>).addResourceLocations(<span class="string">&quot;/plugins/&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>该配置类是在config目录下，SpringMVC扫描的是controller包，所以该配置类还未生效，要想生效需要将SpringMvcConfig配置类进行修改</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&#123;&quot;com.itheima.controller&quot;,&quot;com.itheima.config&quot;&#125;)</span></span><br><span class="line"><span class="meta">@EnableWebMvc</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringMvcConfig</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">或者</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="meta">@EnableWebMvc</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringMvcConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>修改books.html页面</li></ul><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="comment">//添加</span></span><br><span class="line">        saveBook () &#123;</span><br><span class="line">            axios.post(<span class="string">&quot;/books&quot;</span>,<span class="built_in">this</span>.formData).then((res)=&gt;&#123;</span><br><span class="line"></span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;,</span><br><span class="line"></span><br><span class="line">        <span class="comment">//主页列表查询</span></span><br><span class="line">        getAll() &#123;</span><br><span class="line">            axios.get(<span class="string">&quot;/books&quot;</span>).then((res)=&gt;&#123;</span><br><span class="line">                <span class="built_in">this</span>.dataList = res.data;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;,</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><strong>核心逻辑：</strong></p><p><strong>①</strong> <strong><code>axios.post(&quot;/books&quot;, this.formData)</code></strong>：</p><ul><li>用 <code>POST</code> 方法向后端的 <code>/books</code> 接口发送请求。</li><li>把表单数据 <code>this.formData</code>（比如用户输入的书名、作者、价格等）作为请求体发送给后端。</li><li>对应后端的 <code>@PostMapping(&quot;/books&quot;)</code> 接口，用于 “创建新资源”。</li></ul><p><strong>②</strong> <strong><code>this.dataList = res.data</code></strong>：</p><ul><li>请求成功后，后端会返回所有书籍的 JSON 数据（<code>res.data</code>）。</li><li>把这些数据赋值给前端的 <code>dataList</code> 数组，页面就会自动渲染出书籍列表。</li></ul><h3 id="SSM整合"><a href="#SSM整合" class="headerlink" title="SSM整合"></a>SSM整合</h3><h4 id="整合配置"><a href="#整合配置" class="headerlink" title="整合配置"></a>整合配置</h4><h5 id="创建SpringConfig配置类"><a href="#创建SpringConfig配置类" class="headerlink" title="创建SpringConfig配置类"></a>创建SpringConfig配置类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&#123;&quot;com.itheima.service&quot;&#125;)</span></span><br><span class="line"><span class="meta">@PropertySource(&quot;classpath:jdbc.properties&quot;)</span></span><br><span class="line"><span class="meta">@Import(&#123;JdbcConfig.class,MyBatisConfig.class&#125;)</span></span><br><span class="line"><span class="meta">@EnableTransactionManagement</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="创建JdbcConfig配置类"><a href="#创建JdbcConfig配置类" class="headerlink" title="创建JdbcConfig配置类"></a>创建JdbcConfig配置类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.driver&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String driver;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.url&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String url;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.username&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.password&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">dataSource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        dataSource.setDriverClassName(driver);</span><br><span class="line">        dataSource.setUrl(url);</span><br><span class="line">        dataSource.setUsername(username);</span><br><span class="line">        dataSource.setPassword(password);</span><br><span class="line">        <span class="keyword">return</span> dataSource;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PlatformTransactionManager <span class="title function_">transactionManager</span><span class="params">(DataSource dataSource)</span>&#123;</span><br><span class="line">        <span class="type">DataSourceTransactionManager</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DataSourceTransactionManager</span>();</span><br><span class="line">        ds.setDataSource(dataSource);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="创建MybatisConfig配置类"><a href="#创建MybatisConfig配置类" class="headerlink" title="创建MybatisConfig配置类"></a>创建MybatisConfig配置类</h5><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBatisConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SqlSessionFactoryBean <span class="title function_">sqlSessionFactory</span><span class="params">(DataSource dataSource)</span>&#123;</span><br><span class="line">        <span class="type">SqlSessionFactoryBean</span> <span class="variable">factoryBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SqlSessionFactoryBean</span>();</span><br><span class="line">        factoryBean.setDataSource(dataSource);</span><br><span class="line">        factoryBean.setTypeAliasesPackage(<span class="string">&quot;com.itheima.domain&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> factoryBean;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MapperScannerConfigurer <span class="title function_">mapperScannerConfigurer</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">MapperScannerConfigurer</span> <span class="variable">msc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MapperScannerConfigurer</span>();</span><br><span class="line">        msc.setBasePackage(<span class="string">&quot;com.itheima.dao&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> msc;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="创建JdbcConfig配置类-1"><a href="#创建JdbcConfig配置类-1" class="headerlink" title="创建JdbcConfig配置类"></a>创建JdbcConfig配置类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.driver&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String driver;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.url&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String url;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.username&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.password&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">dataSource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        dataSource.setDriverClassName(driver);</span><br><span class="line">        dataSource.setUrl(url);</span><br><span class="line">        dataSource.setUsername(username);</span><br><span class="line">        dataSource.setPassword(password);</span><br><span class="line">        <span class="keyword">return</span> dataSource;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PlatformTransactionManager <span class="title function_">transactionManager</span><span class="params">(DataSource dataSource)</span>&#123;</span><br><span class="line">        <span class="type">DataSourceTransactionManager</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DataSourceTransactionManager</span>();</span><br><span class="line">        ds.setDataSource(dataSource);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="创建MybatisConfig配置类-1"><a href="#创建MybatisConfig配置类-1" class="headerlink" title="创建MybatisConfig配置类"></a>创建MybatisConfig配置类</h5><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBatisConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SqlSessionFactoryBean <span class="title function_">sqlSessionFactory</span><span class="params">(DataSource dataSource)</span>&#123;</span><br><span class="line">        <span class="type">SqlSessionFactoryBean</span> <span class="variable">factoryBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SqlSessionFactoryBean</span>();</span><br><span class="line">        factoryBean.setDataSource(dataSource);</span><br><span class="line">        factoryBean.setTypeAliasesPackage(<span class="string">&quot;com.itheima.domain&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> factoryBean;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MapperScannerConfigurer <span class="title function_">mapperScannerConfigurer</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">MapperScannerConfigurer</span> <span class="variable">msc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MapperScannerConfigurer</span>();</span><br><span class="line">        msc.setBasePackage(<span class="string">&quot;com.itheima.dao&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> msc;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>创建SpringMVC配置类</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima.controller&quot;)</span></span><br><span class="line"><span class="meta">@EnableWebMvc</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringMvcConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="创建Web项目入口配置类"><a href="#创建Web项目入口配置类" class="headerlink" title="创建Web项目入口配置类"></a>创建Web项目入口配置类</h4><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">class</span> <span class="title class_">ServletConfig</span> <span class="keyword">extends</span> <span class="title class_">AbstractAnnotationConfigDispatcherServletInitializer</span> &#123;</span><br><span class="line">    <span class="comment">//加载Spring配置类</span></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt;[] getRootConfigClasses() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Class</span>[]&#123;SpringConfig.class&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//加载SpringMVC配置类</span></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt;[] getServletConfigClasses() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Class</span>[]&#123;SpringMvcConfig.class&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//设置SpringMVC请求地址拦截规则</span></span><br><span class="line">    <span class="keyword">protected</span> String[] getServletMappings() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;/&quot;</span>&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//设置post请求中文乱码过滤器</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Filter[] getServletFilters() &#123;</span><br><span class="line">        <span class="type">CharacterEncodingFilter</span> <span class="variable">filter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CharacterEncodingFilter</span>();</span><br><span class="line">        filter.setEncoding(<span class="string">&quot;utf-8&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Filter</span>[]&#123;filter&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="功能模块开发"><a href="#功能模块开发" class="headerlink" title="功能模块开发"></a>功能模块开发</h4><h5 id="编写模型类"><a href="#编写模型类" class="headerlink" title="编写模型类"></a>编写模型类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Book</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer id;</span><br><span class="line">    <span class="keyword">private</span> String type;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String description;</span><br><span class="line">    <span class="comment">//getter...setter...toString省略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="编写Dao接口"><a href="#编写Dao接口" class="headerlink" title="编写Dao接口"></a>编写Dao接口</h5><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="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    @Insert(&quot;insert into tbl_book values(null,#&#123;type&#125;,#&#123;name&#125;,#&#123;description&#125;)&quot;)</span></span><br><span class="line">    <span class="meta">@Insert(&quot;insert into tbl_book (type,name,description) values(#&#123;type&#125;,#&#123;name&#125;,#&#123;description&#125;)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">(Book book)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Update(&quot;update tbl_book set type = #&#123;type&#125;, name = #&#123;name&#125;, description = #&#123;description&#125; where id = #&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">update</span><span class="params">(Book book)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Delete(&quot;delete from tbl_book where id = #&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">delete</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Select(&quot;select * from tbl_book where id = #&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Book <span class="title function_">getById</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Select(&quot;select * from tbl_book&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Book&gt; <span class="title function_">getAll</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="编写Service接口和实现类"><a href="#编写Service接口和实现类" class="headerlink" title="编写Service接口和实现类"></a>编写Service接口和实现类</h5><p><strong>Service接口</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BookService</span> &#123;</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 class="doctag">@param</span> book</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">save</span><span class="params">(Book book)</span>;</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 class="doctag">@param</span> book</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">update</span><span class="params">(Book book)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 按id删除</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">delete</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 按id查询</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Book <span class="title function_">getById</span><span class="params">(Integer id)</span>;</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 class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Book&gt; <span class="title function_">getAll</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Service实现类</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">save</span><span class="params">(Book book)</span> &#123;</span><br><span class="line">        bookDao.save(book);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">update</span><span class="params">(Book book)</span> &#123;</span><br><span class="line">        bookDao.update(book);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">delete</span><span class="params">(Integer id)</span> &#123;</span><br><span class="line">        bookDao.delete(id);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Book <span class="title function_">getById</span><span class="params">(Integer id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bookDao.getById(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> List&lt;Book&gt; <span class="title function_">getAll</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bookDao.getAll();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="编写Contorller类"><a href="#编写Contorller类" class="headerlink" title="编写Contorller类"></a>编写Contorller类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/books&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> BookService bookService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">save</span><span class="params">(<span class="meta">@RequestBody</span> Book book)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bookService.save(book);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PutMapping</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">update</span><span class="params">(<span class="meta">@RequestBody</span> Book book)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bookService.update(book);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@DeleteMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">delete</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bookService.delete(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Book <span class="title function_">getById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bookService.getById(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Book&gt; <span class="title function_">getAll</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bookService.getAll();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h4><h5 id="新建测试类"><a href="#新建测试类" class="headerlink" title="新建测试类"></a>新建测试类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = SpringConfig.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceTest</span> &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注入Service类、编写测试方法</strong></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="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = SpringConfig.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> BookService bookService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testGetById</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">Book</span> <span class="variable">book</span> <span class="operator">=</span> bookService.getById(<span class="number">1</span>);</span><br><span class="line">        System.out.println(book);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testGetAll</span><span class="params">()</span>&#123;</span><br><span class="line">        List&lt;Book&gt; all = bookService.getAll();</span><br><span class="line">        System.out.println(all);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="统一结果封装"><a href="#统一结果封装" class="headerlink" title="统一结果封装"></a>统一结果封装</h3><h4 id="设置统一数据返回结果类"><a href="#设置统一数据返回结果类" class="headerlink" title="设置统一数据返回结果类"></a>设置统一数据返回结果类</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Result</span>&#123;</span><br><span class="line"><span class="keyword">private</span> Object data;</span><br><span class="line"><span class="keyword">private</span> Integer code;</span><br><span class="line"><span class="keyword">private</span> String msg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>**注意：**Result类名及类中的字段并不是固定的，可以根据需要自行增减提供若干个构造方法，方便操作。</p><h4 id="表现层与前端数据传输协议实现"><a href="#表现层与前端数据传输协议实现" class="headerlink" title="表现层与前端数据传输协议实现"></a>表现层与前端数据传输协议实现</h4><h5 id="创建Result类"><a href="#创建Result类" class="headerlink" title="创建Result类"></a>创建Result类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Result</span> &#123;</span><br><span class="line">    <span class="comment">//描述统一格式中的数据</span></span><br><span class="line">    <span class="keyword">private</span> Object data;</span><br><span class="line">    <span class="comment">//描述统一格式中的编码，用于区分操作，可以简化配置0或1表示成功失败</span></span><br><span class="line">    <span class="keyword">private</span> Integer code;</span><br><span class="line">    <span class="comment">//描述统一格式中的消息，可选属性</span></span><br><span class="line">    <span class="keyword">private</span> String msg;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Result</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="comment">//构造方法是方便对象的创建</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Result</span><span class="params">(Integer code,Object data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="comment">//构造方法是方便对象的创建</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Result</span><span class="params">(Integer code, Object data, String msg)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.msg = msg;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="comment">//setter...getter...省略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="定义返回码Code类"><a href="#定义返回码Code类" class="headerlink" title="定义返回码Code类"></a>定义返回码Code类</h5><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="comment">//状态码</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Code</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">SAVE_OK</span> <span class="operator">=</span> <span class="number">20011</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">DELETE_OK</span> <span class="operator">=</span> <span class="number">20021</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">UPDATE_OK</span> <span class="operator">=</span> <span class="number">20031</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">GET_OK</span> <span class="operator">=</span> <span class="number">20041</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">SAVE_ERR</span> <span class="operator">=</span> <span class="number">20010</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">DELETE_ERR</span> <span class="operator">=</span> <span class="number">20020</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">UPDATE_ERR</span> <span class="operator">=</span> <span class="number">20030</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">GET_ERR</span> <span class="operator">=</span> <span class="number">20040</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="修改Controller类的返回值"><a href="#修改Controller类的返回值" class="headerlink" title="修改Controller类的返回值"></a>修改Controller类的返回值</h5><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//统一每一个控制器方法返回值</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/books&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> BookService bookService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">save</span><span class="params">(<span class="meta">@RequestBody</span> Book book)</span> &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> bookService.save(book);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(flag ? Code.SAVE_OK:Code.SAVE_ERR,flag);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PutMapping</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">update</span><span class="params">(<span class="meta">@RequestBody</span> Book book)</span> &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> bookService.update(book);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(flag ? Code.UPDATE_OK:Code.UPDATE_ERR,flag);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@DeleteMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">delete</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> bookService.delete(id);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(flag ? Code.DELETE_OK:Code.DELETE_ERR,flag);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">getById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> &#123;</span><br><span class="line">        <span class="type">Book</span> <span class="variable">book</span> <span class="operator">=</span> bookService.getById(id);</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">code</span> <span class="operator">=</span> book != <span class="literal">null</span> ? Code.GET_OK : Code.GET_ERR;</span><br><span class="line">        <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> book != <span class="literal">null</span> ? <span class="string">&quot;&quot;</span> : <span class="string">&quot;数据查询失败，请重试！&quot;</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(code,book,msg);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">getAll</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;Book&gt; bookList = bookService.getAll();</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">code</span> <span class="operator">=</span> bookList != <span class="literal">null</span> ? Code.GET_OK : Code.GET_ERR;</span><br><span class="line">        <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> bookList != <span class="literal">null</span> ? <span class="string">&quot;&quot;</span> : <span class="string">&quot;数据查询失败，请重试！&quot;</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(code,bookList,msg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>核心逻辑：</strong></p><p>① 如果 <code>book</code> 不为 <code>null</code>（查询成功）：<code>code = Code.GET_OK</code>，<code>msg = &quot;&quot;</code></p><p>② 如果 <code>book</code> 为 <code>null</code>（查询失败）：<code>code = Code.GET_ERR</code>，<code>msg = &quot;数据查询失败，请重试！&quot;</code></p><h3 id="统一异常处理"><a href="#统一异常处理" class="headerlink" title="统一异常处理"></a>统一异常处理</h3><h4 id="异常处理器的使用"><a href="#异常处理器的使用" class="headerlink" title="异常处理器的使用"></a>异常处理器的使用</h4><h5 id="创建异常处理器类"><a href="#创建异常处理器类" class="headerlink" title="创建异常处理器类"></a>创建异常处理器类</h5><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="comment">//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器</span></span><br><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProjectExceptionAdvice</span> &#123;</span><br><span class="line">    <span class="comment">//除了自定义的异常处理器，保留对Exception类型的异常处理，用于处理非预期的异常</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">doException</span><span class="params">(Exception ex)</span>&#123;</span><br><span class="line">      System.out.println(<span class="string">&quot;嘿嘿,异常你哪里跑！&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(<span class="number">666</span>,<span class="literal">null</span>,<span class="string">&quot;嘿嘿,异常你哪里跑！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="知识点-4"><a href="#知识点-4" class="headerlink" title="知识点"></a>知识点</h5><p>① @RestControllerAdvice</p><table><thead><tr><th>名称</th><th>@RestControllerAdvice</th></tr></thead><tbody><tr><td>类型</td><td>类注解</td></tr><tr><td>位置</td><td>Rest风格开发的控制器增强类定义上方</td></tr><tr><td>作用</td><td>为Rest风格开发的控制器类做增强</td></tr></tbody></table><p>② @ExceptionHandler</p><table><thead><tr><th>名称</th><th>@ExceptionHandler</th></tr></thead><tbody><tr><td>类型</td><td>方法注解</td></tr><tr><td>位置</td><td>专用于异常处理的控制器方法上方</td></tr><tr><td>作用</td><td>设置指定异常的处理方案，功能等同于控制器方法，出现异常后终止原始控制器执行，并转入当前方法执行</td></tr></tbody></table><h4 id="项目异常处理方案"><a href="#项目异常处理方案" class="headerlink" title="项目异常处理方案"></a>项目异常处理方案</h4><h5 id="自定义异常类"><a href="#自定义异常类" class="headerlink" title="自定义异常类"></a>自定义异常类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//自定义异常处理器，用于封装异常信息，对异常进行分类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SystemException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Integer code;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">getCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setCode</span><span class="params">(Integer code)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SystemException</span><span class="params">(Integer code, String message)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message);</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SystemException</span><span class="params">(Integer code, String message, Throwable cause)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message, cause);</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//自定义异常处理器，用于封装异常信息，对异常进行分类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BusinessException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Integer code;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">getCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setCode</span><span class="params">(Integer code)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BusinessException</span><span class="params">(Integer code, String message)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message);</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BusinessException</span><span class="params">(Integer code, String message, Throwable cause)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message, cause);</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li>让自定义异常类继承<code>RuntimeException</code>的好处是，后期在抛出这两个异常的时候，就不用在try…catch…或throws了</li><li>自定义异常类中添加<code>code</code>属性的原因是为了更好的区分异常是来自哪个业务的</li></ul><h5 id="将其他异常包定义成自定义异常"><a href="#将其他异常包定义成自定义异常" class="headerlink" title="将其他异常包定义成自定义异常"></a>将其他异常包定义成自定义异常</h5><p>假如在BookServiceImpl的getById方法抛异常了，该如何来包装呢?</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Book <span class="title function_">getById</span><span class="params">(Integer id)</span> &#123;</span><br><span class="line">    <span class="comment">//模拟业务异常，包装成自定义异常</span></span><br><span class="line">    <span class="keyword">if</span>(id == <span class="number">1</span>)&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(Code.BUSINESS_ERR,<span class="string">&quot;请不要使用你的技术挑战我的耐性!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//模拟系统异常，将可能出现的异常进行包装，转换成自定义异常</span></span><br><span class="line">    <span class="keyword">try</span>&#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>/<span class="number">0</span>;</span><br><span class="line">    &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">SystemException</span>(Code.SYSTEM_TIMEOUT_ERR,<span class="string">&quot;服务器访问超时，请重试!&quot;</span>,e);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> bookDao.getById(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="处理器类中处理自定义异常"><a href="#处理器类中处理自定义异常" class="headerlink" title="处理器类中处理自定义异常"></a><strong>处理器类中处理自定义异常</strong></h5><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器</span></span><br><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProjectExceptionAdvice</span> &#123;</span><br><span class="line">    <span class="comment">//@ExceptionHandler用于设置当前处理器类对应的异常类型</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(SystemException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">doSystemException</span><span class="params">(SystemException ex)</span>&#123;</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">//发送邮件给开发人员,ex对象发送给开发人员</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(ex.getCode(),<span class="literal">null</span>,ex.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExceptionHandler(BusinessException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">doBusinessException</span><span class="params">(BusinessException ex)</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(ex.getCode(),<span class="literal">null</span>,ex.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//除了自定义的异常处理器，保留对Exception类型的异常处理，用于处理非预期的异常</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">doOtherException</span><span class="params">(Exception ex)</span>&#123;</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">//发送邮件给开发人员,ex对象发送给开发人员</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(Code.SYSTEM_UNKNOW_ERR,<span class="literal">null</span>,<span class="string">&quot;系统繁忙，请稍后再试！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="前后台协议联调"><a href="#前后台协议联调" class="headerlink" title="前后台协议联调"></a>前后台协议联调</h3><h4 id="列表功能"><a href="#列表功能" class="headerlink" title="列表功能"></a>列表功能</h4><figure class="highlight javascript"><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"><span class="title function_">getAll</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">//发送ajax请求</span></span><br><span class="line">    axios.<span class="title function_">get</span>(<span class="string">&quot;/books&quot;</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>)=&gt;</span>&#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">dataList</span> = res.<span class="property">data</span>.<span class="property">data</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="添加功能"><a href="#添加功能" class="headerlink" title="添加功能"></a>添加功能</h4><h5 id="handleCreate打开新增面板"><a href="#handleCreate打开新增面板" class="headerlink" title="handleCreate打开新增面板"></a><code>handleCreate</code>打开新增面板</h5><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></pre></td><td class="code"><pre><span class="line">handleCreate() &#123;</span><br><span class="line">    <span class="built_in">this</span>.dialogFormVisible = <span class="literal">true</span>;</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h5 id="handleAdd方法发送异步请求并携带数据"><a href="#handleAdd方法发送异步请求并携带数据" class="headerlink" title="handleAdd方法发送异步请求并携带数据"></a><code>handleAdd</code>方法发送异步请求并携带数据</h5><figure class="highlight javascript"><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="title function_">handleAdd</span> () &#123;</span><br><span class="line">    <span class="comment">//发送ajax请求</span></span><br><span class="line">    <span class="comment">//this.formData是表单中的数据，最后是一个json数据</span></span><br><span class="line">    axios.<span class="title function_">post</span>(<span class="string">&quot;/books&quot;</span>,<span class="variable language_">this</span>.<span class="property">formData</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>)=&gt;</span>&#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">dialogFormVisible</span> = <span class="literal">false</span>;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">getAll</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="添加功能状态处理"><a href="#添加功能状态处理" class="headerlink" title="添加功能状态处理"></a>添加功能状态处理</h4><p><strong>① 思路分析</strong></p><p>新增成功是关闭面板，重新查询数据，那么新增失败以后该如何处理?</p><ol><li>在handlerAdd方法中根据后台返回的数据来进行不同的处理</li><li>如果后台返回的是成功，则提示成功信息，并关闭面板</li><li>如果后台返回的是失败，则提示错误信息</li></ol><p><strong>② 代码实现</strong></p><p><strong>1). 修改前端页面</strong></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">handleAdd () &#123;</span><br><span class="line">    <span class="comment">//发送ajax请求</span></span><br><span class="line">    axios.post(<span class="string">&quot;/books&quot;</span>,<span class="built_in">this</span>.formData).then((res)=&gt;&#123;</span><br><span class="line">        <span class="comment">//如果操作成功，关闭弹层，显示数据</span></span><br><span class="line">        <span class="keyword">if</span>(res.data.code == <span class="number">20011</span>)&#123;</span><br><span class="line">            <span class="built_in">this</span>.dialogFormVisible = <span class="literal">false</span>;</span><br><span class="line">            <span class="built_in">this</span>.$message.success(<span class="string">&quot;添加成功&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span>(res.data.code == <span class="number">20010</span>)&#123;</span><br><span class="line">            <span class="built_in">this</span>.$message.error(<span class="string">&quot;添加失败&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="built_in">this</span>.$message.error(res.data.msg);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;).<span class="keyword">finally</span>(()=&gt;&#123;</span><br><span class="line">        <span class="built_in">this</span>.getAll();</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2). 后台返回操作结果，将Dao层的增删改方法返回值从<code>void</code>改成<code>int</code></strong></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="keyword">interface</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Insert(&quot;insert into tbl_book (type,name,description) values(#&#123;type&#125;,#&#123;name&#125;,#&#123;description&#125;)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">save</span><span class="params">(Book book)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Update(&quot;update tbl_book set type = #&#123;type&#125;, name = #&#123;name&#125;, description = #&#123;description&#125; where id = #&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">update</span><span class="params">(Book book)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Delete(&quot;delete from tbl_book where id = #&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">delete</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Select(&quot;select * from tbl_book where id = #&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Book <span class="title function_">getById</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Select(&quot;select * from tbl_book&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Book&gt; <span class="title function_">getAll</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>在使用 MyBatis 这类框架时，<code>insert</code>、<code>update</code>、<code>delete</code> 这类<strong>增删改操作</strong>，本质上是在修改数据库的数据。框架本身会自动统计这些操作<strong>影响了多少行数据</strong>。</p><ul><li>如果返回值是 <code>void</code>，上层代码（Service&#x2F;Controller）就完全不知道这次数据库操作到底有没有真正执行成功，只能 “盲目相信” 它成功了。</li><li>如果返回值是 <code>int</code>，这个 <code>int</code> 就代表<strong>受影响的行数</strong>。比如返回 <code>1</code> 表示成功新增 &#x2F; 修改 &#x2F; 删除了 1 条记录，返回 <code>0</code> 就表示没有任何数据被操作（比如要删除的 ID 不存在）。</li></ul><p>3). 处理完新增后，会发现新增还存在一个问题，新增成功后，再次点击<code>新增</code>按钮会发现之前的数据还存在，这个时候就需要在新增的时候将表单内容清空。</p><figure class="highlight javascript"><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="title function_">resetForm</span>(<span class="params"></span>)&#123;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">formData</span> = &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">handleCreate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">dialogFormVisible</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">resetForm</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="修改功能"><a href="#修改功能" class="headerlink" title="修改功能"></a>修改功能</h4><h5 id="修改handleUpdate方法"><a href="#修改handleUpdate方法" class="headerlink" title="修改handleUpdate方法"></a>修改<code>handleUpdate</code>方法</h5><figure class="highlight javascript"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//弹出编辑窗口</span></span><br><span class="line"><span class="title function_">handleUpdate</span>(<span class="params">row</span>) &#123;</span><br><span class="line">    <span class="comment">// console.log(row);   //row.id 查询条件</span></span><br><span class="line">    <span class="comment">//查询数据，根据id查询</span></span><br><span class="line">    axios.<span class="title function_">get</span>(<span class="string">&quot;/books/&quot;</span>+row.<span class="property">id</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>)=&gt;</span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(res.<span class="property">data</span>.<span class="property">code</span> == <span class="number">20041</span>)&#123;</span><br><span class="line">            <span class="comment">//展示弹层，加载数据</span></span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">formData</span> = res.<span class="property">data</span>.<span class="property">data</span>;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">dialogFormVisible4Edit</span> = <span class="literal">true</span>;</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">error</span>(res.<span class="property">data</span>.<span class="property">msg</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="修改handleEdit方法"><a href="#修改handleEdit方法" class="headerlink" title="修改handleEdit方法"></a>修改<code>handleEdit</code>方法</h5><figure class="highlight javascript"><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="title function_">handleEdit</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">//发送ajax请求</span></span><br><span class="line">    axios.<span class="title function_">put</span>(<span class="string">&quot;/books&quot;</span>,<span class="variable language_">this</span>.<span class="property">formData</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>)=&gt;</span>&#123;</span><br><span class="line">        <span class="comment">//如果操作成功，关闭弹层，显示数据</span></span><br><span class="line">        <span class="keyword">if</span>(res.<span class="property">data</span>.<span class="property">code</span> == <span class="number">20031</span>)&#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">dialogFormVisible4Edit</span> = <span class="literal">false</span>;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">success</span>(<span class="string">&quot;修改成功&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span>(res.<span class="property">data</span>.<span class="property">code</span> == <span class="number">20030</span>)&#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">error</span>(<span class="string">&quot;修改失败&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">error</span>(res.<span class="property">data</span>.<span class="property">msg</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;).<span class="title function_">finally</span>(<span class="function">()=&gt;</span>&#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">getAll</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="删除功能"><a href="#删除功能" class="headerlink" title="删除功能"></a>删除功能</h4><p>修改<code>handleDelete</code>方法</p><figure class="highlight javascript"><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="title function_">handleDelete</span>(<span class="params">row</span>) &#123;</span><br><span class="line">    <span class="comment">//1.弹出提示框</span></span><br><span class="line">    <span class="variable language_">this</span>.$confirm(<span class="string">&quot;此操作永久删除当前数据，是否继续？&quot;</span>,<span class="string">&quot;提示&quot;</span>,&#123;</span><br><span class="line">        <span class="attr">type</span>:<span class="string">&#x27;info&#x27;</span></span><br><span class="line">    &#125;).<span class="title function_">then</span>(<span class="function">()=&gt;</span>&#123;</span><br><span class="line">        <span class="comment">//2.做删除业务</span></span><br><span class="line">        axios.<span class="title function_">delete</span>(<span class="string">&quot;/books/&quot;</span>+row.<span class="property">id</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>)=&gt;</span>&#123;</span><br><span class="line">            <span class="keyword">if</span>(res.<span class="property">data</span>.<span class="property">code</span>  <span class="number">20021</span>)&#123;</span><br><span class="line">                <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">success</span>(<span class="string">&quot;删除成功&quot;</span>);</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">error</span>(<span class="string">&quot;删除失败&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).<span class="title function_">finally</span>(<span class="function">()=&gt;</span>&#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="title function_">getAll</span>();</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;).<span class="title function_">catch</span>(<span class="function">()=&gt;</span>&#123;</span><br><span class="line">        <span class="comment">//3.取消删除</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">info</span>(<span class="string">&quot;取消删除操作&quot;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><strong>① $confirm 的作用：</strong></p><ul><li>第 1 个参数：对话框的<strong>提示内容</strong>，这里是 “此操作将永久删除当前数据，是否继续？”</li><li>第 2 个参数：对话框的<strong>标题</strong>，这里是 “提示”</li><li>第 3 个参数：配置对象，<code>type: &#39;info&#39;</code> 表示对话框的类型为 “信息提示”</li></ul><p><strong>② <code>$message</code> 的作用</strong></p><p>它是前端页面的 “反馈工具”，当用户完成某个操作（比如删除、修改、新增）后，用简洁的弹窗告诉用户 “操作成功 &#x2F; 失败 &#x2F; 已取消”，提升交互体验，让用户明确知道自己的操作有没有生效。</p><figure class="highlight javascript"><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="variable language_">this</span>.$confirm(<span class="string">&quot;此操作将永久删除当前数据，是否继续？&quot;</span>, <span class="string">&quot;提示&quot;</span>, &#123;</span><br><span class="line">  <span class="attr">type</span>: <span class="string">&#x27;info&#x27;</span></span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  axios.<span class="title function_">delete</span>(<span class="string">&quot;/books/&quot;</span>+row.<span class="property">id</span>).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span>(res.<span class="property">data</span>.<span class="property">code</span> === <span class="number">20021</span>) &#123;</span><br><span class="line">      <span class="comment">// 场景1：删除成功 → 绿色的成功提示</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">success</span>(<span class="string">&quot;删除成功&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 场景2：删除失败 → 红色的错误提示</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">error</span>(<span class="string">&quot;删除失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;).<span class="title function_">finally</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">getAll</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 场景3：用户取消删除 → 蓝色/灰色的信息提示</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">info</span>(<span class="string">&quot;取消删除操作&quot;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="拦截器"><a href="#拦截器" class="headerlink" title="拦截器"></a>拦截器</h3><h4 id="拦截器入门案例"><a href="#拦截器入门案例" class="headerlink" title="拦截器入门案例"></a>拦截器入门案例</h4><h5 id="创建拦截器类"><a href="#创建拦截器类" class="headerlink" title="创建拦截器类"></a>创建拦截器类</h5><p>让类实现HandlerInterceptor接口，重写接口中的三个方法。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="comment">//定义拦截器类，实现HandlerInterceptor接口</span></span><br><span class="line"><span class="comment">//注意当前类必须受Spring容器控制</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProjectInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="comment">//原始方法调用前执行的内容</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;preHandle...&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="comment">//原始方法调用后执行的内容</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;postHandle...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="comment">//原始方法调用完成后执行的内容</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;afterCompletion...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>**注意：**拦截器类要被SpringMVC容器扫描到。</p><h5 id="配置拦截器类"><a href="#配置拦截器类" class="headerlink" title="配置拦截器类"></a>配置拦截器类</h5><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="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringMvcSupport</span> <span class="keyword">extends</span> <span class="title class_">WebMvcConfigurationSupport</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProjectInterceptor projectInterceptor;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> &#123;</span><br><span class="line">        registry.addResourceHandler(<span class="string">&quot;/pages/**&quot;</span>).addResourceLocations(<span class="string">&quot;/pages/&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">        <span class="comment">//配置拦截器</span></span><br><span class="line">        registry.addInterceptor(projectInterceptor).addPathPatterns(<span class="string">&quot;/books&quot;</span> );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p><strong>重写方法：配置静态资源映射</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> &#123;</span><br><span class="line">    registry.addResourceHandler(<span class="string">&quot;/pages/**&quot;</span>).addResourceLocations(<span class="string">&quot;/pages/&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个方法的作用是：<strong>让 Spring MVC 能正确处理静态资源（HTML&#x2F;CSS&#x2F;JS&#x2F; 图片）</strong>。</p><ul><li><code>addResourceHandler(&quot;/pages/**&quot;)</code>：定义 URL 规则，所有以 <code>/pages/</code> 开头的请求，都当作静态资源请求处理。</li><li><code>addResourceLocations(&quot;/pages/&quot;)</code>：告诉 Spring，这些静态资源存放在服务器的 <code>/pages/</code> 目录下</li></ul><h4 id="拦截器参数"><a href="#拦截器参数" class="headerlink" title="拦截器参数"></a>拦截器参数</h4><h5 id="前置处理方法"><a href="#前置处理方法" class="headerlink" title="前置处理方法"></a>前置处理方法</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request,</span></span><br><span class="line"><span class="params">                         HttpServletResponse response,</span></span><br><span class="line"><span class="params">                         Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;preHandle&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>request：请求对象</li><li>response：响应对象</li><li>handler：被调用的处理器对象，本质上是一个方法对象，对反射中的Method对象进行了再包装</li></ul><p>使用request对象可以获取请求数据中的内容，如获取请求头的<code>Content-Type</code></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">contentType</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Content-Type&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;preHandle...&quot;</span>+contentType);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">SpringMVC 入门、Tomcat、控制器与请求映射</summary>
    
    
    
    <category term="SpringMVC" scheme="https://kon-forever.cloud/categories/SpringMVC/"/>
    
    
    <category term="Spring" scheme="https://kon-forever.cloud/tags/Spring/"/>
    
    <category term="SpringMVC" scheme="https://kon-forever.cloud/tags/SpringMVC/"/>
    
  </entry>
  
  <entry>
    <title>SSM学习笔记之 Spring</title>
    <link href="https://kon-forever.cloud/2026/03/20/SSM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BSpring/"/>
    <id>https://kon-forever.cloud/2026/03/20/SSM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BSpring/</id>
    <published>2026-03-20T01:00:00.000Z</published>
    <updated>2026-03-20T01:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="SSM学习笔记"><a href="#SSM学习笔记" class="headerlink" title="SSM学习笔记"></a>SSM学习笔记</h2><h3 id="Spring相关概念"><a href="#Spring相关概念" class="headerlink" title="Spring相关概念"></a>Spring相关概念</h3><h4 id="Spring4-架构图"><a href="#Spring4-架构图" class="headerlink" title="Spring4 架构图"></a>Spring4 架构图</h4><img src="https://i-blog.csdnimg.cn/direct/803a448a3d69410781f30866d69af89b.png" alt="img" style="zoom:50%;" /><h5 id="核心层"><a href="#核心层" class="headerlink" title="核心层"></a>核心层</h5><ul><li>Core Container:核心容器，这个模块是Spring最核心的模块，其他的都需要依赖该模块</li></ul><h5 id="AOP层"><a href="#AOP层" class="headerlink" title="AOP层"></a>AOP层</h5><ul><li>AOP:面向切面编程，它依赖核心层容器，目的是在不改变原有代码的前提下对其进行功能增强</li><li>Aspects:AOP是思想,Aspects是对AOP思想的具体实现</li></ul><h5 id="数据层"><a href="#数据层" class="headerlink" title="数据层"></a>数据层</h5><ul><li>Data Access:数据访问，Spring全家桶中有对数据访问的具体实现技术</li><li>Data Integration:数据集成，Spring支持整合其他的数据层解决方案，比如Mybatis</li><li>Transactions:事务，Spring中事务管理是Spring AOP的一个具体实现，也是后期学习的重点内容</li></ul><h5 id="Web层"><a href="#Web层" class="headerlink" title="Web层"></a>Web层</h5><ul><li>这一层的内容将在SpringMVC框架具体学习</li></ul><h5 id="Test层"><a href="#Test层" class="headerlink" title="Test层"></a>Test层</h5><ul><li>Spring主要整合了Junit来完成单元测试和集成测试</li></ul><h4 id="IoC、IoC容器、Bean、DI"><a href="#IoC、IoC容器、Bean、DI" class="headerlink" title="IoC、IoC容器、Bean、DI"></a>IoC、IoC容器、Bean、DI</h4><h5 id="什么是控制反转？"><a href="#什么是控制反转？" class="headerlink" title="什么是控制反转？"></a>什么是控制反转？</h5><p>使用对象时，由主动new产生对象转换为由<strong>外部</strong>提供对象，此过程中对象创建控制权由程序转移到外部，此思想称为控制反转。</p><h5 id="Spring和IoC之间的关系是什么呢"><a href="#Spring和IoC之间的关系是什么呢" class="headerlink" title="Spring和IoC之间的关系是什么呢?"></a>Spring和IoC之间的关系是什么呢?</h5><ul><li>Spring技术对IoC思想进行了实现</li><li>Spring提供了一个容器，称为IoC容器，用来充当IoC思想中的”外部”</li></ul><h5 id="IoC容器的作用以及内部存放的是什么"><a href="#IoC容器的作用以及内部存放的是什么" class="headerlink" title="IoC容器的作用以及内部存放的是什么?"></a>IoC容器的作用以及内部存放的是什么?</h5><ul><li>IoC容器负责对象的创建、初始化等一系列工作，其中包含了数据层和业务层的类对象</li><li>被创建或被管理的对象在IoC容器中统称为Bean</li><li>IoC容器中放的就是一个个的Bean对象</li></ul><h5 id="DI（Dependency-Injection）依赖注入"><a href="#DI（Dependency-Injection）依赖注入" class="headerlink" title="DI（Dependency Injection）依赖注入"></a>DI（Dependency Injection）依赖注入</h5><p>在容器中建立bean与bean之间的依赖关系的整个过程，称为依赖注入</p><img src="https://i-blog.csdnimg.cn/direct/4a71c286ea724e1e96d6be11d07665e2.png" alt="img" style="zoom:50%;" /><h3 id="入门案例"><a href="#入门案例" class="headerlink" title="入门案例"></a>入门案例</h3><h4 id="IoC入门案例"><a href="#IoC入门案例" class="headerlink" title="IoC入门案例"></a>IoC入门案例</h4><h5 id="关键步骤"><a href="#关键步骤" class="headerlink" title="关键步骤"></a>关键步骤</h5><p>① 在配置文件中完成bean的配置</p><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></pre></td><td class="code"><pre><span class="line">    <span class="comment">&lt;!--bean标签标示配置bean</span></span><br><span class="line"><span class="comment">    id属性标示给bean起名字</span></span><br><span class="line"><span class="comment">    class属性表示给bean定义类型</span></span><br><span class="line"><span class="comment">--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookService&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.service.impl.BookServiceImpl&quot;</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>核心逻辑：</strong></p><p>① 为什么 class 类型指定的是实现类，不是接口？</p><p>直接原因：Spring IOC 容器的核心是<strong>创建对象实例</strong>，如果你把 class 指定为接口（比如<code>com.itheima.dao.BookDao</code>），Spring 容器在尝试创建对象时，会和上面的 Java 代码一样失败 —— 因为它无法实例化一个接口，最终会抛出类似<code>Cannot instantiate interface com.itheima.dao.BookDao</code>的异常。</p><p>从容器中获取对象进行方法调用</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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">App</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">//获取IoC容器</span></span><br><span class="line"><span class="type">ApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;applicationContext.xml&quot;</span>); </span><br><span class="line"><span class="comment">//        BookDao bookDao = (BookDao) ctx.getBean(&quot;bookDao&quot;);</span></span><br><span class="line"><span class="comment">//        bookDao.save();</span></span><br><span class="line">        <span class="type">BookService</span> <span class="variable">bookService</span> <span class="operator">=</span> (BookService) ctx.getBean(<span class="string">&quot;bookService&quot;</span>);</span><br><span class="line">        bookService.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="DI入门实例"><a href="#DI入门实例" class="headerlink" title="DI入门实例"></a>DI入门实例</h4><h5 id="关键步骤-1"><a href="#关键步骤-1" class="headerlink" title="关键步骤"></a>关键步骤</h5><p>修改配置完成注入</p><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><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">&lt;!--bean标签标示配置bean</span></span><br><span class="line"><span class="comment">    id属性标示给bean起名字</span></span><br><span class="line"><span class="comment">    class属性表示给bean定义类型</span></span><br><span class="line"><span class="comment">--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookService&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.service.impl.BookServiceImpl&quot;</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!--配置server与dao的关系--&gt;</span></span><br><span class="line">        <span class="comment">&lt;!--property标签表示配置当前bean的属性</span></span><br><span class="line"><span class="comment">        name属性表示配置哪一个具体的属性</span></span><br><span class="line"><span class="comment">        ref属性表示参照哪一个bean</span></span><br><span class="line"><span class="comment">--&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;bookDao&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>注意：配置中的两个bookDao的含义是不一样的</strong></p><p>1). name&#x3D;”bookDao”中<code>bookDao</code>的作用是让Spring的IoC容器在获取到名称后，将首字母小写，前面加set找对应的<code>setBookDao()</code>方法进行对象注入</p><p>对应 <code>BookServiceImpl</code> 的代码：</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="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="comment">// 👇 这里的变量名是 bookDao，所以 name 必须写 &quot;bookDao&quot;</span></span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 👇 setter方法名是 setBookDao，Spring 会自动匹配 &quot;bookDao&quot;（去掉set，首字母小写）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBookDao</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>如果把类里的变量名改成 <code>dao</code>，setter 改成 <code>setDao</code>，那配置里的 <code>name</code> 必须改成 <code>&quot;dao&quot;</code>，否则 Spring 找不到要赋值的属性，会报错；</li><li>比如：<code>private BookDao dao; + public void setDao(BookDao dao)</code> → 配置要写成 <code>&lt;property name=&quot;dao&quot; ref=&quot;bookDao&quot;/&gt;</code>。</li></ul><p>2). ref&#x3D;”bookDao”中<code>bookDao</code>的作用是让Spring能在IoC容器中找到id为<code>bookDao</code>的Bean对象给<code>bookService</code>进行注入</p><img src="https://i-blog.csdnimg.cn/direct/89890451bdef4d0ea08e4ff4055f8b64.png" alt="img" style="zoom: 50%;" /><h3 id="IoC相关内容"><a href="#IoC相关内容" class="headerlink" title="IoC相关内容"></a>IoC相关内容</h3><h4 id="bean基础配置"><a href="#bean基础配置" class="headerlink" title="bean基础配置"></a>bean基础配置</h4><h5 id="bean基础配置-id与class"><a href="#bean基础配置-id与class" class="headerlink" title="bean基础配置(id与class)"></a>bean基础配置(id与class)</h5><img src="https://i-blog.csdnimg.cn/direct/4316474407014198bc563aab8a0bc9e9.png" alt="img" style="zoom:50%;" /><h5 id="bean的name属性"><a href="#bean的name属性" class="headerlink" title="bean的name属性"></a>bean的name属性</h5><p><img src="https://i-blog.csdnimg.cn/direct/eacef96074d84fd69454416ea59fe1de.png" alt="img"></p><p>① 代码实现</p><p><strong>步骤1：配置别名</strong></p><p>打开spring的配置文件applicationContext.xml</p><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></pre></td><td class="code"><pre><span class="line">    <span class="comment">&lt;!--name:为bean指定别名，别名可以有多个，使用逗号，分号，空格进行分隔--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookService&quot;</span> <span class="attr">name</span>=<span class="string">&quot;service service4 bookEbi&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.service.impl.BookServiceImpl&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;bookDao&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!--scope：为bean设置作用范围，可选值为单例singloton，非单例prototype--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">name</span>=<span class="string">&quot;dao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>步骤2：根据名称容器中获取bean对象</strong></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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppForName</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">ApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;applicationContext.xml&quot;</span>);</span><br><span class="line">        <span class="comment">//此处根据bean标签的id属性和name属性的任意一个值来获取bean对象</span></span><br><span class="line">        <span class="type">BookService</span> <span class="variable">bookService</span> <span class="operator">=</span> (BookService) ctx.getBean(<span class="string">&quot;service4&quot;</span>);</span><br><span class="line">        bookService.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="bean作用范围scope配置"><a href="#bean作用范围scope配置" class="headerlink" title="bean作用范围scope配置"></a>bean作用范围scope配置</h5><img src="https://i-blog.csdnimg.cn/direct/4043b5769f864477975537637a6b10f5.png" alt="img" style="zoom: 50%;" /><p><strong>代码实现</strong></p><p><strong>1). 单例</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">name</span>=<span class="string">&quot;dao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span> <span class="attr">scope</span>=<span class="string">&quot;singleton&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p><strong>2). 非单例</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">name</span>=<span class="string">&quot;dao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span> <span class="attr">scope</span>=<span class="string">&quot;prototype&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><h4 id="bean实例化"><a href="#bean实例化" class="headerlink" title="bean实例化"></a>bean实例化</h4><h5 id="构造方法实例化"><a href="#构造方法实例化" class="headerlink" title="构造方法实例化"></a>构造方法实例化</h5><p>① 编写运行程序</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="keyword">class</span> <span class="title class_">AppForInstanceBook</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">ApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> </span><br><span class="line">            <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;applicationContext.xml&quot;</span>);</span><br><span class="line">        <span class="type">BookDao</span> <span class="variable">bookDao</span> <span class="operator">=</span> (BookDao) ctx.getBean(<span class="string">&quot;bookDao&quot;</span>);</span><br><span class="line">        bookDao.save();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② BookDaoImpl</p><p>在BookDaoImpl类中添加一个无参构造函数，并打印一句话，方便观察结果。</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="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BookDaoImpl</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao constructor is running ....&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>无参构造方法是在执行 <code>new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;)</code> 这行代码时被 Spring 调用的</strong></p><p><strong>第一步：执行<code>new ClassPathXmlApplicationContext(...)</code>时，Spring 会做这些事：</strong></p><ul><li>解析<code>applicationContext.xml</code>，找到<code>&lt;bean id=&quot;bookDao&quot; class=&quot;com.itheima.dao.impl.BookDaoImpl&quot;/&gt;</code>这个配置；</li><li>通过反射获取<code>BookDaoImpl</code>的 Class 对象；</li><li>调用<code>BookDaoImpl</code>的<strong>无参构造方法</strong>创建实例（这就是 “构造方法实例化” 的核心步骤）；</li><li>把创建好的实例存入容器，等待后续获取。✅ 这一步，你写在<code>BookDaoImpl</code>无参构造里的<code>System.out.println(&quot;book dao constructor is running ....&quot;)</code>就会执行 —— 控制台打印这句话，就是无参构造被调用的铁证。</li></ul><p><strong>第二步：执行<code>ctx.getBean(&quot;bookDao&quot;)</code></strong></p><p>Spring 从自己的 “对象仓库” 里，找到<code>id=bookDao</code>的对象（就是刚才创建好的<code>BookDaoImpl</code>实例），把它返回给你。</p><p><strong>第三步：执行<code>bookDao.save()</code></strong></p><p>调用这个对象的<code>save()</code>方法，控制台打印：<code>book dao save ...</code>。</p><h5 id="静态工厂实例化（了解即可）"><a href="#静态工厂实例化（了解即可）" class="headerlink" title="静态工厂实例化（了解即可）"></a>静态工厂实例化（了解即可）</h5><p>① 准备一个OrderDao和OrderDaoImpl类</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="keyword">interface</span> <span class="title class_">OrderDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">OrderDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;order dao save ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② 创建一个工厂类OrderDaoFactory并提供一个静态方法</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//静态工厂创建对象</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderDaoFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> OrderDao <span class="title function_">getOrderDao</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">OrderDaoImpl</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>③ 编写AppForInstanceOrder运行类，在类中通过工厂获取对象</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppForInstanceOrder</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">//通过静态工厂创建对象</span></span><br><span class="line">        <span class="type">OrderDao</span> <span class="variable">orderDao</span> <span class="operator">=</span> OrderDaoFactory.getOrderDao();</span><br><span class="line">        orderDao.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>④ 在spring的配置文件中添加以下内容</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;orderDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.factory.OrderDaoFactory&quot;</span> <span class="attr">factory-method</span>=<span class="string">&quot;getOrderDao&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p><strong>1). 关键部分解析：</strong></p><p>**class：**工厂类的类全名</p><p>**factory-mehod：**具体工厂类中创建对象的方法名</p><p><strong>2). 对应关系如下图：</strong></p><img src="https://i-blog.csdnimg.cn/direct/a3c64df4cc034c68aed8a14cf42b9755.png" alt="img" style="zoom:50%;" /><p>⑤ 在AppForInstanceOrder运行类，使用从IoC容器中获取bean的方法进行运行测试</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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppForInstanceOrder</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">ApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;applicationContext.xml&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">OrderDao</span> <span class="variable">orderDao</span> <span class="operator">=</span> (OrderDao) ctx.getBean(<span class="string">&quot;orderDao&quot;</span>);</span><br><span class="line"></span><br><span class="line">        orderDao.save();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>Spring 加载<code>OrderDaoFactory</code>类（无需创建工厂类实例，因为方法是静态的）；</li><li>调用<code>OrderDaoFactory.getOrderDao()</code>静态方法；</li><li>将方法返回的<code>OrderDaoImpl</code>实例存入容器，标识为<code>orderDao</code>；</li><li>后续<code>ctx.getBean(&quot;orderDao&quot;)</code>获取的就是这个实例。</li></ul><h5 id="实例工厂与FactoryBean"><a href="#实例工厂与FactoryBean" class="headerlink" title="实例工厂与FactoryBean"></a>实例工厂与FactoryBean</h5><p>① 准备一个UserDao和UserDaoImpl类</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="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">UserDao</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;user dao save ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② 创建一个工厂类OrderDaoFactory并提供一个普通方法，注意此处和静态工厂的工厂类不一样的地方是方法，关键在于此处<strong>不是静态方法</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserDaoFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> UserDao <span class="title function_">getUserDao</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">UserDaoImpl</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>③ 编写AppForInstanceUser运行类，在类中通过工厂获取对象</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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppForInstanceUser</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">//创建实例工厂对象</span></span><br><span class="line">        <span class="type">UserDaoFactory</span> <span class="variable">userDaoFactory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserDaoFactory</span>();</span><br><span class="line">        <span class="comment">//通过实例工厂对象创建对象</span></span><br><span class="line">        <span class="type">UserDao</span> <span class="variable">userDao</span> <span class="operator">=</span> userDaoFactory.getUserDao();</span><br><span class="line">        userDao.save();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>④ 在spring的配置文件中添加以下内容</p><figure class="highlight xml"><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="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userFactory&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.factory.UserDaoFactory&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">factory-method</span>=<span class="string">&quot;getUserDao&quot;</span> <span class="attr">factory-bean</span>=<span class="string">&quot;userFactory&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p>实例化工厂运行的顺序是:</p><ul><li>创建实例化工厂对象,对应的是第一行配置</li><li>调用对象中的方法来创建bean，对应的是第二行配置<ul><li>factory-bean：工厂的实例对象</li><li>factory-method：工厂对象中的具体创建对象的方法名,对应关系如下:</li></ul></li></ul><img src="https://i-blog.csdnimg.cn/direct/5d12b3cada214273a0d3ea38ef51f0f6.png" alt="img" style="zoom: 50%;" /><h5 id="FactoryBean的使用（重要）"><a href="#FactoryBean的使用（重要）" class="headerlink" title="FactoryBean的使用（重要）"></a>FactoryBean的使用（重要）</h5><p>① 创建一个UserDaoFactoryBean的类，实现FactoryBean接口，重写接口的方法</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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserDaoFactoryBean</span> <span class="keyword">implements</span> <span class="title class_">FactoryBean</span>&lt;UserDao&gt; &#123;</span><br><span class="line">    <span class="comment">//代替原始实例工厂中创建对象的方法</span></span><br><span class="line">    <span class="keyword">public</span> UserDao <span class="title function_">getObject</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">UserDaoImpl</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//返回所创建类的Class对象</span></span><br><span class="line">    <span class="keyword">public</span> Class&lt;?&gt; getObjectType() &#123;</span><br><span class="line">        <span class="keyword">return</span> UserDao.class;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② 在Spring的配置文件中进行配置</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.factory.UserDaoFactoryBean&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><h4 id="bean的生命周期"><a href="#bean的生命周期" class="headerlink" title="bean的生命周期"></a>bean的生命周期</h4><h5 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h5><ul><li>bean创建之后，想要添加内容，比如用来初始化需要用到资源</li><li>bean销毁之前，想要添加内容，比如用来释放用到的资源</li></ul><h5 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 添加初始化和销毁方法</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//表示bean初始化对应的操作</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;init...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//表示bean销毁前对应的操作</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">destory</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;destory...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 配置生命周期</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span> <span class="attr">init-method</span>=<span class="string">&quot;init&quot;</span> <span class="attr">destory-method</span>=<span class="string">&quot;destory&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p>Spring 创建<code>bookDao</code>这个 Bean 实例后，会<strong>自动调用</strong><code>BookDaoImpl</code>类中名为<code>init</code>的方法（初始化逻辑）；当 Spring 容器关闭、销毁<code>bookDao</code>实例前，会<strong>自动调用</strong>类中名为<code>destroy</code>的方法（销毁 &#x2F; 清理逻辑）。</p><p><strong>③ close关闭容器</strong></p><figure class="highlight java"><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="type">ClassPathXmlApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> </span><br><span class="line">    <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;applicationContext.xml&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="DI相关内容"><a href="#DI相关内容" class="headerlink" title="DI相关内容"></a>DI相关内容</h3><h4 id="setter注入"><a href="#setter注入" class="headerlink" title="setter注入"></a>setter注入</h4><h5 id="注入简单数据类型"><a href="#注入简单数据类型" class="headerlink" title="注入简单数据类型"></a>注入简单数据类型</h5><p>需求：给BookDaoImpl注入一些简单数据类型的数据</p><ol><li>在BookDaoImpl类中声明对应的简单数据类型的属性</li><li>为这些属性提供对应的setter方法</li><li>在applicationContext.xml中配置</li></ol><p><strong>① 声明属性并提供setter方法</strong></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="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String databaseName;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> connectionNum;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setConnectionNum</span><span class="params">(<span class="type">int</span> connectionNum)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.connectionNum = connectionNum;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDatabaseName</span><span class="params">(String databaseName)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.databaseName = databaseName;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span>+databaseName+<span class="string">&quot;,&quot;</span>+connectionNum);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 配置文件中进行注入配置</strong></p><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></pre></td><td class="code"><pre><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;databaseName&quot;</span> <span class="attr">value</span>=<span class="string">&quot;mysql&quot;</span>/&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;connectionNum&quot;</span> <span class="attr">value</span>=<span class="string">&quot;10&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.UserDaoImpl&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookService&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.service.impl.BookServiceImpl&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;bookDao&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;userDao&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>对于<strong>引用数据类型</strong>使用的是<code>&lt;property name=&quot;&quot; ref=&quot;&quot;/&gt;</code></li><li>对于<strong>简单数据类型</strong>使用的是<code>&lt;property name=&quot;&quot; value=&quot;&quot;/&gt;</code></li></ul><h5 id="构造器注入引用数据类型"><a href="#构造器注入引用数据类型" class="headerlink" title="构造器注入引用数据类型"></a>构造器注入引用数据类型</h5><p>(1) 思路分析</p><ol><li>将bookDao的setter方法删除掉</li><li>添加带有bookDao参数的构造方法</li><li>在applicationContext.xml中配置</li></ol><p>(2) 代码实现</p><p><strong>① 删除setter方法并提供构造方法</strong></p><p>原来的 <code>BookServiceImpl</code></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 原来的 setter 方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBookDao</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book service save ...&quot;</span>);</span><br><span class="line">        bookDao.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在改成构造方法注入后：</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">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BookServiceImpl</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book service save ...&quot;</span>);</span><br><span class="line">        bookDao.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>**核心逻辑：**这个构造方法怪怪的，返回值是什么，方法名又是什么？</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">BookServiceImpl</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>构造方法和普通方法的核心区别</strong></p><table><thead><tr><th>特征</th><th>普通方法（比如 setter 方法）</th><th>构造方法（你问的这段代码）</th></tr></thead><tbody><tr><td>方法名</td><td>任意（通常 set&#x2F;get 开头）</td><td><strong>必须和类名完全一致</strong></td></tr><tr><td>返回值类型</td><td>必须声明（void&#x2F;int&#x2F;String 等）</td><td><strong>不能声明任何返回值</strong>（连 void 都不能写）</td></tr><tr><td>作用</td><td>执行特定功能（比如赋值）</td><td>创建对象时<strong>初始化对象</strong></td></tr><tr><td>调用方式</td><td>对象创建后手动调用（obj.setXxx ()）</td><td>由 JVM 自动调用（new）</td></tr></tbody></table><p>逐行解释：</p><ul><li><strong>方法名 <code>BookServiceImpl</code></strong>：<strong>必须和类名完全一致</strong>（你的类名就是 <code>BookServiceImpl</code>），这是构造方法的 “身份标识”，少一个字母、大小写不一样都不行。</li><li><strong>没有返回值类型</strong>：你看不到 <code>void</code>、<code>int</code> 等返回值声明，这是构造方法的强制规则 —— 因为构造方法的 “返回值” 是<strong>创建好的当前类对象</strong>，这个返回值由 JVM 自动处理，不需要你手动写。比如你写 <code>new BookServiceImpl(bookDao)</code> 时，JVM 会执行这个构造方法，然后自动返回一个 <code>BookServiceImpl</code> 类型的对象，你不用管返回值的声明。</li></ul><p><strong>② 配置文件中进行配置构造方式注入</strong></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></pre></td><td class="code"><pre><span class="line">    &lt;bean id=<span class="string">&quot;bookDao&quot;</span> class=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>/&gt;</span><br><span class="line">    &lt;bean id=<span class="string">&quot;bookService&quot;</span> class=<span class="string">&quot;com.itheima.service.impl.BookServiceImpl&quot;</span>&gt;</span><br><span class="line">        &lt;constructor-arg name=<span class="string">&quot;bookDao&quot;</span> ref=<span class="string">&quot;bookDao&quot;</span>/&gt;</span><br><span class="line">    &lt;/bean&gt;</span><br><span class="line">&lt;/beans&gt;</span><br></pre></td></tr></table></figure><p>**核心逻辑：**内层 <code>&lt;constructor-arg&gt;</code> 标签</p><p>这是构造方法注入的关键，专门用来 “给构造方法传参数”：</p><ul><li><code>name=&quot;bookDao&quot;</code>：指定要传给构造方法的<strong>参数名</strong>（对应你之前写的 <code>public BookServiceImpl(BookDao bookDao)</code> 里的 <code>bookDao</code> 参数）；</li><li><code>ref=&quot;bookDao&quot;</code>：指定参数的<strong>值</strong>—— 这里的 <code>ref</code> 是 “引用” 的意思，告诉 Spring：“这个参数的值，不是新造的，而是去我的对象仓库里找那个名字叫 <code>bookDao</code> 的对象”。</li></ul><p>Spring 启动后，会按顺序执行配置里的指令：</p><p><strong>1. 执行 <code>&lt;bean id=&quot;bookDao&quot; .../&gt;</code>：</strong></p><ul><li>调用 <code>BookDaoImpl</code> 的无参构造（如果没写，编译器会默认生成），创建 <code>BookDaoImpl</code> 对象；</li><li>把这个对象存到容器里，标记为 “id&#x3D;bookDao”。</li></ul><p><strong>2. 执行 <code>&lt;bean id=&quot;bookService&quot; .../&gt;</code>：</strong></p><ul><li>先看里面有 <code>&lt;constructor-arg&gt;</code>，知道要调用 <code>BookServiceImpl</code> 的<strong>带参构造方法</strong>；</li><li>去容器里找到 “id&#x3D;bookDao” 的那个对象（就是第一步造的）；</li><li>调用 <code>new BookServiceImpl(bookDao)</code> 创建 <code>BookServiceImpl</code> 对象；</li><li>把这个对象存到容器里，标记为 “id&#x3D;bookService”。</li></ul><h4 id="构造器注入多个简单数据类型"><a href="#构造器注入多个简单数据类型" class="headerlink" title="构造器注入多个简单数据类型"></a>构造器注入多个简单数据类型</h4><h5 id="思路分析-1"><a href="#思路分析-1" class="headerlink" title="思路分析"></a>思路分析</h5><ol><li>提供一个包含这两个参数的构造方法</li><li>在applicationContext.xml中进行注入配置</li></ol><h5 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 提供多个属性的构造函数</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String databaseName;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> connectionNum;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BookDaoImpl</span><span class="params">(String databaseName, <span class="type">int</span> connectionNum)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.databaseName = databaseName;</span><br><span class="line">        <span class="built_in">this</span>.connectionNum = connectionNum;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span>+databaseName+<span class="string">&quot;,&quot;</span>+connectionNum);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>② 在applicationContext.xml中配置注入</p><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></pre></td><td class="code"><pre><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">&quot;databaseName&quot;</span> <span class="attr">value</span>=<span class="string">&quot;mysql&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">&quot;connectionNum&quot;</span> <span class="attr">value</span>=<span class="string">&quot;666&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.UserDaoImpl&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookService&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.service.impl.BookServiceImpl&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;bookDao&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;userDao&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="为什么构造器一定是强制依赖的，setter-不是（重点）"><a href="#为什么构造器一定是强制依赖的，setter-不是（重点）" class="headerlink" title="为什么构造器一定是强制依赖的，setter 不是（重点）"></a>为什么构造器一定是强制依赖的，setter 不是（重点）</h4><h5 id="setter-有参但不用传参：本质是-“我压根不调用这个方法”"><a href="#setter-有参但不用传参：本质是-“我压根不调用这个方法”" class="headerlink" title="setter 有参但不用传参：本质是 “我压根不调用这个方法”"></a>setter 有参但不用传参：本质是 “我压根不调用这个方法”</h5><p>setter 方法（比如 <code>setBookDao(BookDao bookDao)</code>）的参数规则是：<strong>如果调用这个方法，就必须传 BookDao 参数；但我可以选择不调用这个方法</strong>。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao; <span class="comment">// 初始值为 null</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 有参的 setter 方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBookDao</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        bookDao.save(); <span class="comment">// 没调用 setter 的话，这里会空指针</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 测试代码</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 创建对象：调用无参构造，完全不用管 setter</span></span><br><span class="line">        <span class="type">BookServiceImpl</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BookServiceImpl</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 可选操作：我可以调用 setter（传参），也可以不调用（不传参）</span></span><br><span class="line">        <span class="comment">// service.setBookDao(new BookDaoImpl()); // 注释掉=不传参、不调用</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 运行方法：没调 setter 的话，bookDao 是 null，运行时报错</span></span><br><span class="line">        service.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>区分：</strong></p><ul><li><strong>setter 方法和构造方法是两码事</strong>：setter 是用来给成员变量赋值的普通方法（要声明返回值），构造方法是用来创建对象的特殊方法（不用声明返回值）</li></ul><h5 id="构造器有参就必须传参-——-因为-“创建对象必须调用构造器”"><a href="#构造器有参就必须传参-——-因为-“创建对象必须调用构造器”" class="headerlink" title="构造器有参就必须传参 —— 因为 “创建对象必须调用构造器”"></a>构造器有参就必须传参 —— 因为 “创建对象必须调用构造器”</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 有参的构造器</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BookServiceImpl</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 测试代码</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 编译报错！因为创建对象必须调用构造器，而构造器要求传 BookDao 参数</span></span><br><span class="line">        <span class="type">BookServiceImpl</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BookServiceImpl</span>(); </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的关键是：<strong>创建对象的动作（<code>new</code>）和构造器的调用是绑定的 —— 只要 new 对象，就必须调构造器，构造器要参数就必须传，躲不开</strong>；而 setter 是 new 完对象之后的 “额外动作”，调不调全看你。</p><h3 id="自动装配（了解即可）"><a href="#自动装配（了解即可）" class="headerlink" title="自动装配（了解即可）"></a>自动装配（了解即可）</h3><h5 id="代码实现-2"><a href="#代码实现-2" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>项目中添加BookDao、BookDaoImpl、BookService和BookServiceImpl类</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String databaseName;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> connectionNum;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBookDao</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book service save ...&quot;</span>);</span><br><span class="line">        bookDao.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>resources下提供spring的配置文件</strong></p><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></pre></td><td class="code"><pre><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>/&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--autowire属性：开启自动装配，通常使用按类型装配--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookService&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.service.impl.BookServiceImpl&quot;</span> <span class="attr">autowire</span>=<span class="string">&quot;byType&quot;</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p>**核心逻辑：**按类型（<code>byType</code>）自动装配时，Spring 匹配的 “类型” 是什么</p><p>① 先找到 “要注入的成员变量”，在 <code>BookServiceImpl</code> 类里，有一个需要注入的成员变量：</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="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="comment">// 这个成员变量的类型是 BookDao</span></span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao; </span><br><span class="line"></span><br><span class="line">    <span class="comment">// 必须有 setter 方法（自动装配 byType/byName 都依赖 setter）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBookDao</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的关键是：<strong>成员变量 <code>bookDao</code> 声明的类型是 <code>BookDao</code>（接口）</strong>，这就是 Spring 要去匹配的 “目标类型”。</p><p>② Spring 会做这几件事：</p><ul><li>检查 <code>BookServiceImpl</code> 里所有需要注入的成员变量（这里就是 <code>bookDao</code>）。</li><li>拿到它的类型：<code>BookDao</code>。</li><li>去整个 IoC 容器里，找<strong>所有类型是 <code>BookDao</code> 或其子类 &#x2F; 实现类</strong>的 bean。</li><li>如果找到<strong>恰好 1 个</strong>，就自动调用 <code>setBookDao()</code> 把它注入进去；</li><li>如果找到 0 个或多个，就报错（0 个注入失败，多个报 <code>NoUniqueBeanDefinitionException</code>）。</li></ul><h3 id="集合注入（了解即可）"><a href="#集合注入（了解即可）" class="headerlink" title="集合注入（了解即可）"></a>集合注入（了解即可）</h3><h4 id="代码实现-3"><a href="#代码实现-3" class="headerlink" title="代码实现"></a>代码实现</h4><h5 id="注入数组类型数据"><a href="#注入数组类型数据" class="headerlink" title="注入数组类型数据"></a>注入数组类型数据</h5><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;array&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>100<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>200<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>300<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br></pre></td></tr></table></figure><h5 id="注入List类型数据"><a href="#注入List类型数据" class="headerlink" title="注入List类型数据"></a>注入List类型数据</h5><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;list&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">list</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>itcast<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>itheima<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>boxuegu<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>chuanzhihui<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">list</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br></pre></td></tr></table></figure><h5 id="注入Map类型数据"><a href="#注入Map类型数据" class="headerlink" title="注入Map类型数据"></a>注入Map类型数据</h5><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;map&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">map</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">entry</span> <span class="attr">key</span>=<span class="string">&quot;country&quot;</span> <span class="attr">value</span>=<span class="string">&quot;china&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">entry</span> <span class="attr">key</span>=<span class="string">&quot;province&quot;</span> <span class="attr">value</span>=<span class="string">&quot;henan&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">entry</span> <span class="attr">key</span>=<span class="string">&quot;city&quot;</span> <span class="attr">value</span>=<span class="string">&quot;kaifeng&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">map</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br></pre></td></tr></table></figure><h5 id="注入Properties类型数据"><a href="#注入Properties类型数据" class="headerlink" title="注入Properties类型数据"></a><strong>注入Properties类型数据</strong></h5><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;properties&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">props</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">prop</span> <span class="attr">key</span>=<span class="string">&quot;country&quot;</span>&gt;</span>china<span class="tag">&lt;/<span class="name">prop</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">prop</span> <span class="attr">key</span>=<span class="string">&quot;province&quot;</span>&gt;</span>henan<span class="tag">&lt;/<span class="name">prop</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">prop</span> <span class="attr">key</span>=<span class="string">&quot;city&quot;</span>&gt;</span>kaifeng<span class="tag">&lt;/<span class="name">prop</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">props</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="IoC-DI-配置管理第三方bean"><a href="#IoC-DI-配置管理第三方bean" class="headerlink" title="IoC &#x2F; DI 配置管理第三方bean"></a>IoC &#x2F; DI 配置管理第三方bean</h3><h4 id="思路分析-2"><a href="#思路分析-2" class="headerlink" title="思路分析"></a>思路分析</h4><p>(1) 使用第三方的技术，需要在pom.xml添加依赖</p><p>(2) 在配置文件中将【第三方的类】制作成一个bean，让IoC容器进行管理</p><p>(3) 数据库连接需要基础的四要素<code>驱动</code>、<code>连接</code>、<code>用户名</code>和<code>密码</code>，【如何注入】到对应的bean中</p><p>(4) 从IoC容器中获取对应的bean对象，将其打印到控制台查看结果</p><h4 id="代码实现-4"><a href="#代码实现-4" class="headerlink" title="代码实现"></a>代码实现</h4><h5 id="导入druid的依赖"><a href="#导入druid的依赖" class="headerlink" title="导入druid的依赖"></a>导入<code>druid</code>的依赖</h5><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>druid<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.1.16<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h5 id="配置第三方bean"><a href="#配置第三方bean" class="headerlink" title="配置第三方bean"></a>配置第三方bean</h5><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></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--管理DruidDataSource对象--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">class</span>=<span class="string">&quot;com.alibaba.druid.pool.DruidDataSource&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;driverClassName&quot;</span> <span class="attr">value</span>=<span class="string">&quot;com.mysql.jdbc.Driver&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;url&quot;</span> <span class="attr">value</span>=<span class="string">&quot;jdbc:mysql://localhost:3306/spring_db&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">value</span>=<span class="string">&quot;root&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;password&quot;</span> <span class="attr">value</span>=<span class="string">&quot;root&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li>driverClassName:数据库驱动</li><li>url:数据库连接地址</li><li>username:数据库连接用户名</li><li>password:数据库连接密码</li><li>数据库连接的四要素要和自己使用的数据库信息一致。</li></ul><h4 id="加载properties文件"><a href="#加载properties文件" class="headerlink" title="加载properties文件"></a>加载properties文件</h4><h5 id="思路分析-3"><a href="#思路分析-3" class="headerlink" title="思路分析"></a>思路分析</h5><p>① 在resources下创建一个jdbc.properties(文件的名称可以任意)</p><p>② 将数据库连接四要素配置到配置文件中</p><p>③ 在Spring的配置文件中加载properties文件</p><p>④ 使用加载到的值实现属性注入</p><p>其中第3，4步骤是需要大家重点关注，具体是如何实现。</p><h5 id="代码实现-5"><a href="#代码实现-5" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 准备properties配置文件</strong></p><p>resources下创建一个jdbc.properties文件,并添加对应的属性键值对</p><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></pre></td><td class="code"><pre><span class="line">jdbc.driver=com.mysql.jdbc.Driver</span><br><span class="line">jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db</span><br><span class="line">jdbc.username=root</span><br><span class="line">jdbc.password=root</span><br></pre></td></tr></table></figure><p><strong>② 开启<code>context</code>命名空间</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://www.springframework.org/schema/beans&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:context</span>=<span class="string">&quot;http://www.springframework.org/schema/context&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;</span></span></span><br><span class="line"><span class="string"><span class="tag">            http://www.springframework.org/schema/beans</span></span></span><br><span class="line"><span class="string"><span class="tag">            http://www.springframework.org/schema/beans/spring-beans.xsd</span></span></span><br><span class="line"><span class="string"><span class="tag">            http://www.springframework.org/schema/context</span></span></span><br><span class="line"><span class="string"><span class="tag">            http://www.springframework.org/schema/context/spring-context.xsd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>③ 加载properties配置文件</strong></p><p>在配置文件中使用<code>context</code>命名空间下的标签来加载properties配置文件</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">&quot;jdbc.properties&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p><strong>④ 完成属性注入</strong></p><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></pre></td><td class="code"><pre><span class="line">    <span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">&quot;jdbc.properties&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;dataSource&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.alibaba.druid.pool.DruidDataSource&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;driverClassName&quot;</span> <span class="attr">value</span>=<span class="string">&quot;$&#123;jdbc.driver&#125;&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;url&quot;</span> <span class="attr">value</span>=<span class="string">&quot;$&#123;jdbc.url&#125;&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">value</span>=<span class="string">&quot;$&#123;jdbc.username&#125;&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;password&quot;</span> <span class="attr">value</span>=<span class="string">&quot;$&#123;jdbc.password&#125;&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="读取单个属性"><a href="#读取单个属性" class="headerlink" title="读取单个属性"></a>读取单个属性</h4><h5 id="思路分析-4"><a href="#思路分析-4" class="headerlink" title="思路分析"></a>思路分析</h5><p>① 在项目中添加BookDao和BookDaoImpl类</p><p>② 为BookDaoImpl添加一个name属性并提供setter方法</p><p>③ 在jdbc.properties中添加数据注入到bookDao中打印方便查询结果</p><p>④ 在applicationContext.xml添加配置完成配置文件加载、属性注入(${key})</p><h5 id="代码实现-6"><a href="#代码实现-6" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 在项目中添对应的类</strong></p><p>BookDao和BookDaoImpl类，并在BookDaoImpl类中添加<code>name</code>属性与setter方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span> + name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 完成配置文件的读取与注入</strong></p><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></pre></td><td class="code"><pre><span class="line">    <span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">&quot;jdbc.properties&quot;</span>/&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;name&quot;</span> <span class="attr">value</span>=<span class="string">&quot;$&#123;jdbc.driver&#125;&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><h5 id="当有多个properties配置文件需要被加载，该如何配置"><a href="#当有多个properties配置文件需要被加载，该如何配置" class="headerlink" title="当有多个properties配置文件需要被加载，该如何配置?"></a>当有多个properties配置文件需要被加载，该如何配置?</h5><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></pre></td><td class="code"><pre><span class="line">    <span class="comment">&lt;!--方式一 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">&quot;jdbc.properties,jdbc2.properties&quot;</span> <span class="attr">system-properties-mode</span>=<span class="string">&quot;NEVER&quot;</span>/&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--方式二--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">&quot;*.properties&quot;</span> <span class="attr">system-properties-mode</span>=<span class="string">&quot;NEVER&quot;</span>/&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--方式三 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">&quot;classpath:*.properties&quot;</span> <span class="attr">system-properties-mode</span>=<span class="string">&quot;NEVER&quot;</span>/&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--方式四--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">&quot;classpath*:*.properties&quot;</span> <span class="attr">system-properties-mode</span>=<span class="string">&quot;NEVER&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li>方式一：可以实现，如果配置文件多的话，每个都需要配置</li><li>方式二：<code>*.properties</code>代表所有以properties结尾的文件都会被加载，可以解决方式一的问题，但是不标准</li><li>方式三：标准的写法，<code>classpath:</code>代表的是从根路径下开始查找，但是只能查询当前项目的根路径</li><li>方式四：不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的properties配置文件</li></ul><h3 id="核心容器"><a href="#核心容器" class="headerlink" title="核心容器"></a>核心容器</h3><h4 id="Bean的三种获取方式"><a href="#Bean的三种获取方式" class="headerlink" title="Bean的三种获取方式"></a>Bean的三种获取方式</h4><h5 id="方式一："><a href="#方式一：" class="headerlink" title="方式一："></a>方式一：</h5><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="type">BookDao</span> <span class="variable">bookDao</span> <span class="operator">=</span> (BookDao) ctx.getBean(<span class="string">&quot;bookDao&quot;</span>);</span><br></pre></td></tr></table></figure><p>这种方式存在的问题是每次获取的时候都需要进行类型转换</p><h5 id="方式二："><a href="#方式二：" class="headerlink" title="方式二："></a>方式二：</h5><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="type">BookDao</span> <span class="variable">bookDao</span> <span class="operator">=</span> ctx.getBean(<span class="string">&quot;bookDao&quot;</span>，BookDao.class);</span><br></pre></td></tr></table></figure><p>这种方式可以解决类型强转问题，但是参数又多加了一个，相对来说没有简化多少。</p><h5 id="方式三："><a href="#方式三：" class="headerlink" title="方式三："></a>方式三：</h5><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="type">BookDao</span> <span class="variable">bookDao</span> <span class="operator">=</span> ctx.getBean(BookDao.class);</span><br></pre></td></tr></table></figure><p>这种方式就类似我们之前所学习依赖注入中的按类型注入。必须要确保IoC容器中该类型对应的bean对象只能有一个。</p><h4 id="BeanFactory的使用（了解即可）"><a href="#BeanFactory的使用（了解即可）" class="headerlink" title="BeanFactory的使用（了解即可）"></a>BeanFactory的使用（了解即可）</h4><h5 id="使用BeanFactory来创建IoC容器的具体实现方式为"><a href="#使用BeanFactory来创建IoC容器的具体实现方式为" class="headerlink" title="使用BeanFactory来创建IoC容器的具体实现方式为"></a><strong>使用BeanFactory来创建IoC容器的具体实现方式为</strong></h5><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppForBeanFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Resource</span> <span class="variable">resources</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;applicationContext.xml&quot;</span>);</span><br><span class="line">        <span class="type">BeanFactory</span> <span class="variable">bf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">XmlBeanFactory</span>(resources);</span><br><span class="line">        <span class="type">BookDao</span> <span class="variable">bookDao</span> <span class="operator">=</span> bf.getBean(BookDao.class);</span><br><span class="line">        bookDao.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="BeanFactory-和-ApplicationContext-之间的区别"><a href="#BeanFactory-和-ApplicationContext-之间的区别" class="headerlink" title="BeanFactory 和 ApplicationContext 之间的区别"></a>BeanFactory 和 ApplicationContext 之间的区别</h5><ul><li>BeanFactory是延迟加载，只有在获取bean对象的时候才会去创建</li><li>ApplicationContext是立即加载，容器加载的时候就会创建bean对象</li><li>ApplicationContext要想成为延迟加载，只需要按照如下方式进行配置</li></ul><figure class="highlight xml"><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="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>  <span class="attr">lazy-init</span>=<span class="string">&quot;true&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="IOC-DI-注解开发"><a href="#IOC-DI-注解开发" class="headerlink" title="IOC &#x2F; DI 注解开发"></a>IOC &#x2F; DI 注解开发</h3><h4 id="注解开发定义bean"><a href="#注解开发定义bean" class="headerlink" title="注解开发定义bean"></a>注解开发定义bean</h4><h5 id="代码实现-7"><a href="#代码实现-7" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 删除原XML配置</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;bookDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.itheima.dao.impl.BookDaoImpl&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p><strong>② Dao上添加注解</strong></p><p>在BookDaoImpl类上添加<code>@Component</code>注解</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component(&quot;bookDao&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span> );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意：</strong>@Component注解不可以添加在接口上，因为接口是无法创建对象的。</p><p>XML与注解配置的对应关系：</p><img src="https://i-blog.csdnimg.cn/direct/de48b07919294c2c9265aefc6f926892.png" alt="img" style="zoom:67%;" /><p><strong>③ 配置Spring的注解包扫描</strong></p><p>为了让Spring框架能够扫描到写在类上的注解，需要在配置文件上进行包扫描</p><figure class="highlight xml"><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="tag">&lt;<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">&quot;com.itheima&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>component-scan</p><ul><li>component：组件，Spring将管理的bean视作自己的一个组件</li><li>scan：扫描</li></ul><p>base-package指定Spring框架扫描的包路径，它会扫描指定包及其子包中的所有类上的注解。</p><ul><li>包路径越多[如:com.itheima.dao.impl]，扫描的范围越小速度越快</li><li>包路径越少[如:com.itheima]，扫描的范围越大速度越慢</li><li>一般扫描到项目的组织名称即Maven的groupId下[如:com.itheima]即可。</li></ul><p><strong>④ *<em>Service上添加注解*</em></strong></p><p>在BookServiceImpl类上也添加<code>@Component</code>交给Spring框架管理</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBookDao</span><span class="params">(BookDao bookDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookDao = bookDao;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book service save ...&quot;</span>);</span><br><span class="line">        bookDao.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="Component-及其衍生注解"><a href="#Component-及其衍生注解" class="headerlink" title="@Component 及其衍生注解"></a>@Component 及其衍生注解</h5><table><thead><tr><th>名称</th><th>@Component&#x2F;@Controller&#x2F;@Service&#x2F;@Repository</th></tr></thead><tbody><tr><td>类型</td><td>类注解</td></tr><tr><td>位置</td><td>类定义上方</td></tr><tr><td>作用</td><td>设置该类为spring管理的bean</td></tr><tr><td>属性</td><td>value（默认）：定义bean的id</td></tr></tbody></table><h4 id="纯注解开发模式"><a href="#纯注解开发模式" class="headerlink" title="纯注解开发模式"></a>纯注解开发模式</h4><h5 id="思路分析-5"><a href="#思路分析-5" class="headerlink" title="思路分析"></a>思路分析</h5><ul><li>将配置文件applicationContext.xml删除掉，使用类来替换。</li></ul><h5 id="代码实现-8"><a href="#代码实现-8" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 创建配置类</strong></p><p>创建一个配置类<code>SpringConfig</code></p><figure class="highlight java"><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">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② *<em>标识该类为配置类*</em></strong></p><p>在配置类上添加<code>@Configuration</code>注解，将其标识为一个配置类,替换<code>applicationContext.xml</code></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>③ 用注解替换包扫描配置</strong></p><p>在配置类上添加包扫描注解<code>@ComponentScan</code>替换<code>&lt;context:component-scan base-package=&quot;&quot;/&gt;</code></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>④ 创建运行类并执行</strong></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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppForAnnotation</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">ApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(SpringConfig.class);</span><br><span class="line">        <span class="type">BookDao</span> <span class="variable">bookDao</span> <span class="operator">=</span> (BookDao) ctx.getBean(<span class="string">&quot;bookDao&quot;</span>);</span><br><span class="line">        System.out.println(bookDao);</span><br><span class="line">        <span class="type">BookService</span> <span class="variable">bookService</span> <span class="operator">=</span> ctx.getBean(BookService.class);</span><br><span class="line">        System.out.println(bookService);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="执行流程"><a href="#执行流程" class="headerlink" title="执行流程"></a>执行流程</h5><p><strong>① 配置类的作用（替代 xml 文件）</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>@Configuration</code>注解：相当于给这个类贴上 <strong>“Spring 核心配置文件” 的标签，告诉 Spring 框架：“这个类不是普通的 Java 类，它是用来替代原来的 applicationContext.xml 的配置类”</strong></li><li><code>@ComponentScan(&quot;com.itheima&quot;)</code>注解：<strong>告诉 Spring“去<code>com.itheima</code>这个包（包括子包）里找所有带<code>@Component</code>&#x2F;<code>@Service</code>&#x2F;<code>@Repository</code>的类”</strong></li></ul><p><strong>② 运行类的执行流程（核心）</strong></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">class</span> <span class="title class_">AppForAnnotation</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 初始化Spring容器，加载配置类</span></span><br><span class="line">        <span class="type">ApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(SpringConfig.class);</span><br><span class="line">        <span class="comment">// 2. 从容器中获取BookDao对象</span></span><br><span class="line">        <span class="type">BookDao</span> <span class="variable">bookDao</span> <span class="operator">=</span> (BookDao) ctx.getBean(<span class="string">&quot;bookDao&quot;</span>);</span><br><span class="line">        System.out.println(bookDao);</span><br><span class="line">        <span class="comment">// 3. 从容器中获取BookService对象</span></span><br><span class="line">        <span class="type">BookService</span> <span class="variable">bookService</span> <span class="operator">=</span> ctx.getBean(BookService.class);</span><br><span class="line">        System.out.println(bookService);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>逐行拆解执行逻辑：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);</span><br></pre></td></tr></table></figure><ul><li>第一步：识别<code>SpringConfig</code>类上的<code>@Configuration</code>注解，确认这是核心配置类；</li><li>第二步：读取<code>@ComponentScan(&quot;com.itheima&quot;)</code>，去指定包下扫描所有带注解的类（比如<code>BookDao</code>带<code>@Repository</code>、<code>BookService</code>带<code>@Service</code>）；</li><li>第三步：自动创建这些类的实例（对象），并把对象存入 Spring 的 “容器”（ApplicationContext）中管理</li></ul><h5 id="Configuration、-ComponentScan"><a href="#Configuration、-ComponentScan" class="headerlink" title="@Configuration、@ComponentScan"></a>@Configuration、@ComponentScan</h5><p><strong>知识点1：@Configuration</strong></p><table><thead><tr><th>名称</th><th>@Configuration</th></tr></thead><tbody><tr><td>类型</td><td>类注解</td></tr><tr><td>位置</td><td>类定义上方</td></tr><tr><td>作用</td><td>设置该类为spring配置类</td></tr><tr><td>属性</td><td>value（默认）：定义bean的id</td></tr></tbody></table><p><strong>知识点2：@ComponentScan</strong></p><table><thead><tr><th>名称</th><th>@ComponentScan</th></tr></thead><tbody><tr><td>类型</td><td>类注解</td></tr><tr><td>位置</td><td>类定义上方</td></tr><tr><td>作用</td><td>设置spring配置类扫描路径，用于加载使用注解格式定义的bean</td></tr><tr><td>属性</td><td>value（默认）：扫描路径，此路径可以逐层向下扫描</td></tr></tbody></table><h3 id="注解开发依赖注入"><a href="#注解开发依赖注入" class="headerlink" title="注解开发依赖注入"></a><strong>注解开发依赖注入</strong></h3><h4 id="注解实现按照名称注入"><a href="#注解实现按照名称注入" class="headerlink" title="注解实现按照名称注入"></a>注解实现按照名称注入</h4><p>(1) 当根据类型在容器中找到多个bean,注入参数的属性名又和容器中bean的名称不一致，这个时候该如何解决，就需要使用到<code>@Qualifier</code>来指定注入哪个名称的bean对象。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BookService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier(&quot;bookDao1&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> BookDao bookDao;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book service save ...&quot;</span>);</span><br><span class="line">        bookDao.save();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>@Qualifier注解后的值就是需要注入的bean的名称。</p><p><strong>注意：</strong>@Qualifier不能独立使用，必须和@Autowired一起使用</p><h4 id="简单数据类型注入"><a href="#简单数据类型注入" class="headerlink" title="简单数据类型注入"></a>简单数据类型注入</h4><h5 id="使用-Value注解，将值写入注解的参数中"><a href="#使用-Value注解，将值写入注解的参数中" class="headerlink" title="使用@Value注解，将值写入注解的参数中"></a>使用<code>@Value</code>注解，将值写入注解的参数中</h5><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">@Repository(&quot;bookDao&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="meta">@Value(&quot;itheima&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span> + name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="注解读取properties配置文件"><a href="#注解读取properties配置文件" class="headerlink" title="注解读取properties配置文件"></a>注解读取properties配置文件</h5><p><code>@Value</code>一般会被用在从properties配置文件中读取内容进行使用</p><p><strong>① resource下准备properties文件</strong></p><p>jdbc.properties</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">name=itheima888</span><br></pre></td></tr></table></figure><p><strong>② 使用注解加载properties配置文件</strong></p><p>在配置类上添加<code>@PropertySource</code>注解</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="meta">@PropertySource(&quot;jdbc.properties&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>③ 使用@Value读取配置文件中的内容</strong></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">@Repository(&quot;bookDao&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BookDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">BookDao</span> &#123;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;name&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">save</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;book dao save ...&quot;</span> + name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="注册开发管理第三方bean"><a href="#注册开发管理第三方bean" class="headerlink" title="注册开发管理第三方bean"></a>注册开发管理第三方bean</h3><h4 id="注解开发管理第三方bean"><a href="#注解开发管理第三方bean" class="headerlink" title="注解开发管理第三方bean"></a>注解开发管理第三方bean</h4><h5 id="代码实现-9"><a href="#代码实现-9" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 在配置类中添加一个方法（后续里面的数据库信息会用另外一个类承接）</strong></p><p>注意该方法的返回值就是要创建的Bean对象类型</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        ds.setDriverClassName(<span class="string">&quot;com.mysql.jdbc.Driver&quot;</span>);</span><br><span class="line">        ds.setUrl(<span class="string">&quot;jdbc:mysql://localhost:3306/spring_db&quot;</span>);</span><br><span class="line">        ds.setUsername(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        ds.setPassword(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>DataSource</code> 是 <code>javax.sql</code> 包下的一个<strong>接口</strong>，它是 Java 官方定义的 “数据源” 标准规范，核心作用是：</p><ul><li>统一管理数据库连接（替代传统的 <code>DriverManager</code>）</li><li>支持连接池技术，复用数据库连接，提升性能</li></ul><p><strong>② 在方法上添加<code>@Bean</code>注解</strong></p><p>@Bean注解的作用是将方法的返回值制作为Spring管理的一个bean对象</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="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        ds.setDriverClassName(<span class="string">&quot;com.mysql.jdbc.Driver&quot;</span>);</span><br><span class="line">        ds.setUrl(<span class="string">&quot;jdbc:mysql://localhost:3306/spring_db&quot;</span>);</span><br><span class="line">        ds.setUsername(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        ds.setPassword(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="引入外部配置类"><a href="#引入外部配置类" class="headerlink" title="引入外部配置类"></a>引入外部配置类</h4><h5 id="使用-Import引入"><a href="#使用-Import引入" class="headerlink" title="使用@Import引入"></a>使用<code>@Import</code>引入</h5><p>这种方案可以不用加<code>@Configuration</code>注解，但是必须在Spring配置类上使用<code>@Import</code>注解手动引入需要</p><p>加载的配置类</p><p><strong>① 去除JdbcConfig类上的注解</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcConfig</span> &#123;</span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        ds.setDriverClassName(<span class="string">&quot;com.mysql.jdbc.Driver&quot;</span>);</span><br><span class="line">        ds.setUrl(<span class="string">&quot;jdbc:mysql://localhost:3306/spring_db&quot;</span>);</span><br><span class="line">        ds.setUsername(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        ds.setPassword(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 在Spring配置类中引入</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="comment">//@ComponentScan(&quot;com.itheima.config&quot;)</span></span><br><span class="line"><span class="meta">@Import(&#123;JdbcConfig.class&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="注解开发实现为第三方bean注入资源"><a href="#注解开发实现为第三方bean注入资源" class="headerlink" title="注解开发实现为第三方bean注入资源"></a>注解开发实现为第三方bean注入资源</h3><h4 id="简单数据类型"><a href="#简单数据类型" class="headerlink" title="简单数据类型"></a>简单数据类型</h4><h5 id="代码实现-10"><a href="#代码实现-10" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 类中提供四个属性</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcConfig</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String driver;</span><br><span class="line">    <span class="keyword">private</span> String url;</span><br><span class="line">    <span class="keyword">private</span> String userName;</span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        ds.setDriverClassName(<span class="string">&quot;com.mysql.jdbc.Driver&quot;</span>);</span><br><span class="line">        ds.setUrl(<span class="string">&quot;jdbc:mysql://localhost:3306/spring_db&quot;</span>);</span><br><span class="line">        ds.setUsername(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        ds.setPassword(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 使用<code>@Value</code>注解引入值</strong></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="keyword">class</span> <span class="title class_">JdbcConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Value(&quot;com.mysql.jdbc.Driver&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String driver;</span><br><span class="line">    <span class="meta">@Value(&quot;jdbc:mysql://localhost:3306/spring_db&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String url;</span><br><span class="line">    <span class="meta">@Value(&quot;root&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String userName;</span><br><span class="line">    <span class="meta">@Value(&quot;password&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        ds.setDriverClassName(driver);</span><br><span class="line">        ds.setUrl(url);</span><br><span class="line">        ds.setUsername(userName);</span><br><span class="line">        ds.setPassword(password);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="引用数据类型"><a href="#引用数据类型" class="headerlink" title="引用数据类型"></a>引用数据类型</h4><h5 id="需求分析"><a href="#需求分析" class="headerlink" title="需求分析"></a>需求分析</h5><p>假设在构建DataSource对象的时候，需要用到BookDao对象，该如何把BookDao对象注入进方法内让其使用呢?</p><h5 id="代码实现-11"><a href="#代码实现-11" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 在SpringConfig中扫描BookDao</strong></p><p>扫描的目的是让Spring能管理到BookDao,也就是说要让IoC容器中有一个bookDao对象</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima.dao&quot;)</span></span><br><span class="line"><span class="meta">@Import(&#123;JdbcConfig.class&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 在JdbcConfig类的方法上添加参数</strong></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">@Bean</span></span><br><span class="line"><span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">(BookDao bookDao)</span>&#123;</span><br><span class="line">    System.out.println(bookDao);</span><br><span class="line">    <span class="type">DruidDataSource</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">    ds.setDriverClassName(driver);</span><br><span class="line">    ds.setUrl(url);</span><br><span class="line">    ds.setUsername(userName);</span><br><span class="line">    ds.setPassword(password);</span><br><span class="line">    <span class="keyword">return</span> ds;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="执行流程-1"><a href="#执行流程-1" class="headerlink" title="执行流程"></a>执行流程</h5><p>① 识别 <code>SpringConfig</code> 上的 <code>@Configuration</code>，确认这是主配置。</p><p>② 执行 <code>@ComponentScan(&quot;com.itheima.dao&quot;)</code>：</p><ul><li>去 <code>com.itheima.dao</code> 包下扫描，找到 <code>BookDaoImpl</code>（它上面有 <code>@Repository</code> 或 <code>@Component</code> 注解）。</li><li>自动创建 <code>BookDaoImpl</code> 对象，作为 <code>bookDao</code> Bean 存入 IoC 容器。</li></ul><p>③ 执行 <code>@Import(JdbcConfig.class)</code>：</p><ul><li>主动加载 <code>JdbcConfig</code> 类，准备解析它里面的 <code>@Bean</code> 方法。</li></ul><p>④ Spring 发现这个方法需要一个 <code>BookDao</code> 类型的参数。</p><p>⑤ 它会去 IoC 容器中查找：“有没有类型为 <code>BookDao</code> 的 Bean？”找到之前扫描创建的 <code>bookDao</code> 对象，确认可以作为参数传入。</p><h3 id="Spring-整合-MyBatis"><a href="#Spring-整合-MyBatis" class="headerlink" title="Spring 整合 MyBatis"></a>Spring 整合 MyBatis</h3><h4 id="代码实现-12"><a href="#代码实现-12" class="headerlink" title="代码实现"></a>代码实现</h4><h5 id="创建Spring的主配置类"><a href="#创建Spring的主配置类" class="headerlink" title="创建Spring的主配置类"></a>创建Spring的主配置类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//配置类注解</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="comment">//包扫描，主要扫描的是项目中的AccountServiceImpl类</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="创建数据源的配置类"><a href="#创建数据源的配置类" class="headerlink" title="创建数据源的配置类"></a>创建数据源的配置类</h5><p>在<strong>配置类</strong>中完成数据源的创建</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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.driver&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String driver;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.url&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String url;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.username&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String userName;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.password&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        ds.setDriverClassName(driver);</span><br><span class="line">        ds.setUrl(url);</span><br><span class="line">        ds.setUsername(userName);</span><br><span class="line">        ds.setPassword(password);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="主配置类中读properties并引入数据源配置类"><a href="#主配置类中读properties并引入数据源配置类" class="headerlink" title="主配置类中读properties并引入数据源配置类"></a>主配置类中读properties并引入数据源配置类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="meta">@PropertySource(&quot;classpath:jdbc.properties&quot;)</span></span><br><span class="line"><span class="meta">@Import(JdbcConfig.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="创建Mybatis配置类并配置SqlSessionFactory"><a href="#创建Mybatis配置类并配置SqlSessionFactory" class="headerlink" title="创建Mybatis配置类并配置SqlSessionFactory"></a>创建Mybatis配置类并配置SqlSessionFactory</h5><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="keyword">class</span> <span class="title class_">MybatisConfig</span> &#123;</span><br><span class="line">    <span class="comment">//定义bean，SqlSessionFactoryBean，用于产生SqlSessionFactory对象</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SqlSessionFactoryBean <span class="title function_">sqlSessionFactory</span><span class="params">(DataSource dataSource)</span>&#123;</span><br><span class="line">        <span class="type">SqlSessionFactoryBean</span> <span class="variable">ssfb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SqlSessionFactoryBean</span>();</span><br><span class="line">        <span class="comment">//设置模型类的别名扫描</span></span><br><span class="line">        ssfb.setTypeAliasesPackage(<span class="string">&quot;com.itheima.domain&quot;</span>);</span><br><span class="line">        <span class="comment">//设置数据源</span></span><br><span class="line">        ssfb.setDataSource(dataSource);</span><br><span class="line">        <span class="keyword">return</span> ssfb;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//定义bean，返回MapperScannerConfigurer对象</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MapperScannerConfigurer <span class="title function_">mapperScannerConfigurer</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">MapperScannerConfigurer</span> <span class="variable">msc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MapperScannerConfigurer</span>();</span><br><span class="line">        msc.setBasePackage(<span class="string">&quot;com.itheima.dao&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> msc;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><p>① 使用SqlSessionFactoryBean封装SqlSessionFactory需要的环境信息</p><img src="https://i-blog.csdnimg.cn/direct/57fa35723ade4af98020dc7cec388443.png" alt="img" style="zoom:50%;" /><p>图里的代码，其实就是把 XML 配置里的关键部分 “翻译” 成了 Java 方法调用：</p><table><thead><tr><th>原生 MyBatis XML 配置</th><th>Spring Java 配置 (SqlSessionFactoryBean)</th><th>作用</th></tr></thead><tbody><tr><td><code>&lt;typeAliases&gt;</code></td><td><code>setTypeAliasesPackage(&quot;com.itheima.domain&quot;)</code></td><td>为实体类设置别名，简化 SQL 映射文件中的类名书写</td></tr><tr><td><code>&lt;dataSource&gt;</code></td><td><code>setDataSource(dataSource)</code></td><td>注入数据源，MyBatis 用它来建立数据库连接</td></tr></tbody></table><p>② 为什么 dataSource 参数不用自己 new？</p><p>这是 Spring 的依赖注入（DI）特性在起作用：</p><ul><li>你的项目里肯定已经配置了一个 Druid 数据源（<code>DruidDataSource</code>），并且它已经被注册成了一个 Spring Bean。</li><li>当 Spring 初始化 <code>sqlSessionFactory()</code> 这个方法时，发现它需要一个 <code>DataSource</code> 类型的参数，就会自动在容器里寻找一个符合类型的 Bean（也就是你的 Druid 数据源），并自动注入进来。</li></ul><p>③ 使用MapperScannerConfigurer加载Dao接口，创建代理对象保存到IoC容器中</p><img src="https://i-blog.csdnimg.cn/direct/1a1579d6bd1e4700b04ddb3a6af70a8d.png" alt="img" style="zoom:50%;" /><h5 id="主配置类中引入Mybatis配置类"><a href="#主配置类中引入Mybatis配置类" class="headerlink" title="主配置类中引入Mybatis配置类"></a><strong>主配置类中引入Mybatis配置类</strong></h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="meta">@PropertySource(&quot;classpath:jdbc.properties&quot;)</span></span><br><span class="line"><span class="meta">@Import(&#123;JdbcConfig.class,MybatisConfig.class&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="执行流程-2"><a href="#执行流程-2" class="headerlink" title="执行流程"></a>执行流程</h4><h5 id="执行new-AnnotationConfigApplicationContext-SpringConfig-class-时，Spring-开始启动："><a href="#执行new-AnnotationConfigApplicationContext-SpringConfig-class-时，Spring-开始启动：" class="headerlink" title="执行new AnnotationConfigApplicationContext(SpringConfig.class)时，Spring 开始启动："></a>执行<code>new AnnotationConfigApplicationContext(SpringConfig.class)</code>时，Spring 开始启动：</h5><ul><li><code>@ComponentScan(&quot;com.itheima&quot;)</code>：扫描指定包下所有带<code>@Service</code>&#x2F;<code>@Repository</code>等注解的类，准备创建 Bean；</li><li><code>@PropertySource(&quot;classpath:jdbc.properties&quot;)</code>：加载数据库配置文件，读取<code>jdbc.driver</code>&#x2F;<code>url</code>等配置项；</li><li><code>@Import(...)</code>：导入数据源配置类（<code>JdbcConfig</code>）和 MyBatis 配置类（<code>MybatisConfig</code>），触发这两个类的解析。</li></ul><h3 id="Spring-整合-Junit"><a href="#Spring-整合-Junit" class="headerlink" title="Spring 整合 Junit"></a>Spring 整合 Junit</h3><h4 id="代码实现-13"><a href="#代码实现-13" class="headerlink" title="代码实现"></a>代码实现</h4><h5 id="编写测试类"><a href="#编写测试类" class="headerlink" title="编写测试类"></a>编写测试类</h5><p>在test \ java下创建一个AccountServiceTest</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="comment">//设置类运行器</span></span><br><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="comment">//设置Spring环境对应的配置类</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = &#123;SpringConfiguration.class&#125;)</span> <span class="comment">//加载配置类</span></span><br><span class="line"><span class="comment">//@ContextConfiguration(locations=&#123;&quot;classpath:applicationContext.xml&quot;&#125;)//加载配置文件</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AccountServiceTest</span> &#123;</span><br><span class="line">    <span class="comment">//支持自动装配注入bean</span></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AccountService accountService;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testFindById</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(accountService.findById(<span class="number">1</span>));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testFindAll</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(accountService.findAll());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>核心逻辑：</strong></p><p><strong>①</strong> <strong>@RunWith(SpringJUnit4ClassRunner.class)</strong></p><ul><li><strong>作用</strong>：指定用 Spring 提供的专用类运行器来执行测试。</li><li>为什么需要它：<ul><li>普通的 JUnit 运行器只会执行你的测试方法，不会启动 Spring 容器。</li><li>这个特殊的运行器会先<strong>启动 Spring 容器</strong>，然后再运行你的测试方法，这样 Spring 的所有功能（如<code>@Autowired</code>）才能生效。</li></ul></li></ul><p><strong>②</strong> <strong>@ContextConfiguration(classes &#x3D; {SpringConfiguration.class})</strong></p><ul><li><strong>作用</strong>：告诉 Spring 容器，应该加载哪些配置来创建 Bean。</li><li><code>classes</code>：如果你的配置是用 Java 类写的（比如<code>@Configuration</code>注解的类），就用这个属性，指定配置类。</li><li><code>locations</code>：如果你的配置是用 XML 文件写的（比如<code>applicationContext.xml</code>），就用这个属性，指定 XML 文件路径。</li></ul><h4 id="知识点"><a href="#知识点" class="headerlink" title="知识点"></a>知识点</h4><h5 id="RunWith"><a href="#RunWith" class="headerlink" title="@RunWith"></a>@RunWith</h5><table><thead><tr><th>名称</th><th>@RunWith</th></tr></thead><tbody><tr><td>类型</td><td>测试类注解</td></tr><tr><td>位置</td><td>测试类定义上方</td></tr><tr><td>作用</td><td>设置JUnit运行器</td></tr><tr><td>属性</td><td>value（默认）：运行所使用的运行期</td></tr></tbody></table><h5 id="ContextConfiguration"><a href="#ContextConfiguration" class="headerlink" title="@ContextConfiguration"></a>@ContextConfiguration</h5><table><thead><tr><th>名称</th><th>@ContextConfiguration</th></tr></thead><tbody><tr><td>类型</td><td>测试类注解</td></tr><tr><td>位置</td><td>测试类定义上方</td></tr><tr><td>作用</td><td>设置JUnit加载的Spring核心配置</td></tr><tr><td>属性</td><td>classes：核心配置类，可以使用数组的格式设定加载多个配置类 locations:配置文件，可以使用数组的格式设定加载多个配置文件名称</td></tr></tbody></table><h3 id="AOP-核心概念"><a href="#AOP-核心概念" class="headerlink" title="AOP 核心概念"></a>AOP 核心概念</h3><h4 id="连接点"><a href="#连接点" class="headerlink" title="连接点"></a>连接点</h4><p>程序执行过程中的任意位置，粒度为执行方法、抛出异常、设置变量等。在SpringAOP中，理解为方法的执行，可以直接理解成所有的方法</p><h4 id="切入点"><a href="#切入点" class="headerlink" title="切入点"></a>切入点</h4><p>匹配连接点的式子，在SpringAOP中，一个切入点可以描述一个具体方法，也可也匹配多个方法。</p><p>连接点范围要比切入点范围大，是切入点的方法也一定是连接点，但是是连接点的方法就不一定要被增强，所以可能不是切入点。</p><h4 id="通知"><a href="#通知" class="headerlink" title="通知"></a>通知</h4><p>在切入点处执行的操作，也就是共性功能。在SpringAOP中，功能最终以方法的形式呈现</p><h4 id="通知类"><a href="#通知类" class="headerlink" title="通知类"></a>通知类</h4><p>定义通知的类</p><h4 id="切面"><a href="#切面" class="headerlink" title="切面"></a>切面</h4><p>描述通知与切入点的对应关系。</p><img src="https://i-blog.csdnimg.cn/direct/b8b1872b6deb46238376be8710807c36.png" alt="img" style="zoom:50%;" /><h3 id="AOP-入门案例"><a href="#AOP-入门案例" class="headerlink" title="AOP 入门案例"></a>AOP 入门案例</h3><h4 id="代码实现-14"><a href="#代码实现-14" class="headerlink" title="代码实现"></a>代码实现</h4><h5 id="定义通知类和通知"><a href="#定义通知类和通知" class="headerlink" title="定义通知类和通知"></a>定义通知类和通知</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAdvice</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(System.currentTimeMillis());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="定义切入点"><a href="#定义切入点" class="headerlink" title="定义切入点"></a>定义切入点</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(void com.itheima.dao.BookDao.update())&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(System.currentTimeMillis());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>切入点定义依托一个**不具有实际意义的方法进行，**即无参数、无返回值、方法体无实际逻辑。</li></ul><h5 id="制作切面"><a href="#制作切面" class="headerlink" title="制作切面"></a>制作切面</h5><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="keyword">class</span> <span class="title class_">MyAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(void com.itheima.dao.BookDao.update())&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Before(&quot;pt()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(System.currentTimeMillis());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>绑定切入点与通知关系，并指定通知添加到原始连接点的具体执行位置</p><img src="https://i-blog.csdnimg.cn/direct/3d57a2960d194c68a4238210e4b372d0.png" alt="img" style="zoom:50%;" /><p><strong>说明：</strong>@Before翻译过来是之前，也就是说通知会在切入点方法执行之前执行</p><h5 id="将通知类配给容器并标识其为切面类"><a href="#将通知类配给容器并标识其为切面类" class="headerlink" title="将通知类配给容器并标识其为切面类"></a>将通知类配给容器并标识其为切面类</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(void com.itheima.dao.BookDao.update())&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Before(&quot;pt()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(System.currentTimeMillis());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="开启注解格式AOP功能"><a href="#开启注解格式AOP功能" class="headerlink" title="开启注解格式AOP功能"></a>开启注解格式AOP功能</h5><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="meta">@EnableAspectJAutoProxy</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="知识点-1"><a href="#知识点-1" class="headerlink" title="知识点"></a>知识点</h4><h5 id="EnableAspectJAutoProxy"><a href="#EnableAspectJAutoProxy" class="headerlink" title="@EnableAspectJAutoProxy"></a>@EnableAspectJAutoProxy</h5><table><thead><tr><th>名称</th><th>@EnableAspectJAutoProxy</th></tr></thead><tbody><tr><td>类型</td><td>配置类注解</td></tr><tr><td>位置</td><td>配置类定义上方</td></tr><tr><td>作用</td><td>开启注解格式AOP功能</td></tr></tbody></table><h5 id="Aspect"><a href="#Aspect" class="headerlink" title="@Aspect"></a>@Aspect</h5><table><thead><tr><th>名称</th><th>@Aspect</th></tr></thead><tbody><tr><td>类型</td><td>类注解</td></tr><tr><td>位置</td><td>切面类定义上方</td></tr><tr><td>作用</td><td>设置当前类为AOP切面类</td></tr></tbody></table><h5 id="Pointcut"><a href="#Pointcut" class="headerlink" title="@Pointcut"></a>@Pointcut</h5><table><thead><tr><th>名称</th><th>@Pointcut</th></tr></thead><tbody><tr><td>类型</td><td>方法注解</td></tr><tr><td>位置</td><td>切入点方法定义上方</td></tr><tr><td>作用</td><td>设置切入点方法</td></tr><tr><td>属性</td><td>value（默认）：切入点表达式</td></tr></tbody></table><h3 id="AOP-配置管理"><a href="#AOP-配置管理" class="headerlink" title="AOP 配置管理"></a>AOP 配置管理</h3><h4 id="切入点表达式"><a href="#切入点表达式" class="headerlink" title="切入点表达式"></a>切入点表达式</h4><h5 id="语法格式"><a href="#语法格式" class="headerlink" title="语法格式"></a>语法格式</h5><blockquote><p>execution(访问修饰符 返回值 包名.类&#x2F;接口名.方法名(参数) 异常名）</p></blockquote><h5 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h5><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">execution(<span class="keyword">public</span> User com.itheima.service.UserService.findById(<span class="type">int</span>))</span><br></pre></td></tr></table></figure><ul><li>execution：动作关键字，描述切入点的行为动作，例如execution表示执行到指定切入点</li><li>public：访问修饰符,还可以是public，private等，可以省略</li><li>User：返回值，写返回值类型</li><li>com.itheima.service：包名，多级包使用点连接</li><li>UserService：类&#x2F;接口名称</li><li>findById：方法名</li><li>int：参数，直接写参数的类型，多个类型用逗号隔开</li><li>异常名：方法定义中抛出指定异常，可以省略</li></ul><h5 id="通配符"><a href="#通配符" class="headerlink" title="通配符"></a>通配符</h5><p>① <code>*：</code>单个独立的任意符号，可以独立出现，也可以作为前缀或者后缀的匹配符出现</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">execution（<span class="keyword">public</span> * com.itheima.*.UserService.find*(*))</span><br></pre></td></tr></table></figure><p>② <code>..</code>：多个连续的任意符号，可以独立出现，常用于简化包名与参数的书写</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">execution（<span class="keyword">public</span> User com..UserService.findById(..))</span><br></pre></td></tr></table></figure><p>匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法</p><h5 id="书写技巧"><a href="#书写技巧" class="headerlink" title="书写技巧"></a>书写技巧</h5><p>① 描述切入点通<strong>常描述接口</strong>，而不描述实现类，如果描述到实现类，就出现紧耦合了</p><p>② 访问控制修饰符针对接口开发均采用public描述（<strong>可省略访问控制修饰符描述</strong>）</p><p>③ 通常<strong>不使用异常</strong>作为<strong>匹配</strong>规则</p><p>④ <strong>接口名&#x2F;类名</strong>书写名称与模块相关的<strong>采用*匹配</strong>，例如UserService书写成*Service，绑定业务层接口名</p><h4 id="AOP-通知类型"><a href="#AOP-通知类型" class="headerlink" title="AOP 通知类型"></a>AOP 通知类型</h4><h5 id="AOP通知共分为5种类型"><a href="#AOP通知共分为5种类型" class="headerlink" title="AOP通知共分为5种类型"></a>AOP通知共分为5种类型</h5><ul><li>前置通知</li><li>后置通知</li><li><strong>环绕通知(重点)</strong></li><li>返回后通知(了解)</li><li>抛出异常后通知(了解)</li></ul><h5 id="通知类型的使用"><a href="#通知类型的使用" class="headerlink" title="通知类型的使用"></a>通知类型的使用</h5><p><strong>① 前置通知</strong></p><p>修改MyAdvice，在before方法上添加<code>@Before注解</code></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="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(void com.itheima.dao.BookDao.update())&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Before(&quot;pt()&quot;)</span></span><br><span class="line">    <span class="comment">//此处也可以写成 @Before(&quot;MyAdvice.pt()&quot;),不建议</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">before</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;before advice ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>② 后置通知</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(void com.itheima.dao.BookDao.update())&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Before(&quot;pt()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">before</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;before advice ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@After(&quot;pt()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">after</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;after advice ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>③ 环绕通知</strong></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="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(void com.itheima.dao.BookDao.update())&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(int com.itheima.dao.BookDao.select())&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pt2</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;pt2()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">aroundSelect</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;around before advice ...&quot;</span>);</span><br><span class="line">        <span class="comment">//表示对原始操作的调用</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">ret</span> <span class="operator">=</span> pjp.proceed();</span><br><span class="line">        System.out.println(<span class="string">&quot;around after advice ...&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> ret;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li>为什么返回的是Object而不是int的<strong>主要原因是Object类型更通用。</strong></li><li>在环绕通知中是可以对原始方法返回值就行修改的。</li></ul><h5 id="环绕通知注意事项"><a href="#环绕通知注意事项" class="headerlink" title="环绕通知注意事项"></a>环绕通知注意事项</h5><p>① 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用，进而实现原始方法调用前后同时添加通知</p><p>② 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行</p><p>③ 对原始方法的调用可以不接收返回值，通知方法设置成void即可，如果接收返回值，最好设定为Object类型</p><h4 id="业务层接口执行效率"><a href="#业务层接口执行效率" class="headerlink" title="业务层接口执行效率"></a>业务层接口执行效率</h4><h5 id="需求分析-1"><a href="#需求分析-1" class="headerlink" title="需求分析"></a>需求分析</h5><p>任意业务层接口执行均可显示其执行效率（执行时长）</p><h5 id="代码实现-15"><a href="#代码实现-15" class="headerlink" title="代码实现"></a>代码实现</h5><p><strong>① 开启SpringAOP的注解功能</strong></p><p>在Spring的主配置文件SpringConfig类中添加注解</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="meta">@EnableAspectJAutoProxy</span></span><br></pre></td></tr></table></figure><p><strong>② 创建AOP的通知类</strong></p><ul><li>该类要被Spring管理，需要添加@Component</li><li>要标识该类是一个AOP的切面类，需要添加@Aspect</li><li>配置切入点表达式，需要添加一个方法，并添加@Pointcut</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProjectAdvice</span> &#123;</span><br><span class="line">    <span class="comment">//配置业务层的所有方法</span></span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(* com.itheima.service.*Service.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">servicePt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">runSpeed</span><span class="params">()</span>&#123;</span><br><span class="line">        </span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>③ 添加环绕通知</strong></p><p>在runSpeed()方法上添加@Around</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProjectAdvice</span> &#123;</span><br><span class="line">    <span class="comment">//配置业务层的所有方法</span></span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(* com.itheima.service.*Service.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">servicePt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="comment">//@Around(&quot;ProjectAdvice.servicePt()&quot;) 可以简写为下面的方式</span></span><br><span class="line">    <span class="meta">@Around(&quot;servicePt()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">runSpeed</span><span class="params">(ProceedingJoinPoint pjp)</span>&#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">ret</span> <span class="operator">=</span> pjp.proceed();</span><br><span class="line">        <span class="keyword">return</span> ret;</span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>④ 完成核心业务，记录万次执行的时间</strong></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="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProjectAdvice</span> &#123;</span><br><span class="line">    <span class="comment">//配置业务层的所有方法</span></span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(* com.itheima.service.*Service.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">servicePt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="comment">//@Around(&quot;ProjectAdvice.servicePt()&quot;) 可以简写为下面的方式</span></span><br><span class="line">    <span class="meta">@Around(&quot;servicePt()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">runSpeed</span><span class="params">(ProceedingJoinPoint pjp)</span>&#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++) &#123;</span><br><span class="line">           pjp.proceed();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">&quot;业务层接口万次执行时间: &quot;</span>+(end-start)+<span class="string">&quot;ms&quot;</span>);</span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>⑤ 程序优化</strong></p><p>目前程序所面临的问题是，多个方法一起执行测试的时候，控制台都打印的是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">业务层接口万次执行时间:xxxms</span><br></pre></td></tr></table></figure><p>我们没有办法区分到底是哪个接口的哪个方法执行的具体时间，具体如何优化?</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProjectAdvice</span> &#123;</span><br><span class="line">    <span class="comment">//配置业务层的所有方法</span></span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(* com.itheima.service.*Service.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">servicePt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="comment">//@Around(&quot;ProjectAdvice.servicePt()&quot;) 可以简写为下面的方式</span></span><br><span class="line">    <span class="meta">@Around(&quot;servicePt()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">runSpeed</span><span class="params">(ProceedingJoinPoint pjp)</span>&#123;</span><br><span class="line">        <span class="comment">//获取执行签名信息</span></span><br><span class="line">        <span class="type">Signature</span> <span class="variable">signature</span> <span class="operator">=</span> pjp.getSignature();</span><br><span class="line">        <span class="comment">//通过签名获取执行操作名称(接口名)</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">className</span> <span class="operator">=</span> signature.getDeclaringTypeName();</span><br><span class="line">        <span class="comment">//通过签名获取执行操作名称(方法名)</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">methodName</span> <span class="operator">=</span> signature.getName();</span><br><span class="line">        </span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++) &#123;</span><br><span class="line">           pjp.proceed();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">&quot;万次执行：&quot;</span>+ className+<span class="string">&quot;.&quot;</span>+methodName+<span class="string">&quot;----&gt;&quot;</span> +(end-start) + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="AOP通知获取数据"><a href="#AOP通知获取数据" class="headerlink" title="AOP通知获取数据"></a>AOP通知获取数据</h3><h4 id="返回后通知获取返回值"><a href="#返回后通知获取返回值" class="headerlink" title="返回后通知获取返回值"></a>返回后通知获取返回值</h4><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="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(* com.itheima.dao.BookDao.findName(..))&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AfterReturning(value = &quot;pt()&quot;,returning = &quot;ret&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterReturning</span><span class="params">(Object ret)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;afterReturning advice ...&quot;</span>+ret);</span><br><span class="line">    &#125;</span><br><span class="line"><span class="comment">//其他的略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><h5 id="参数名的问题"><a href="#参数名的问题" class="headerlink" title="参数名的问题"></a><strong>参数名的问题</strong></h5><p><img src="https://i-blog.csdnimg.cn/direct/b8cdaab9556a4f58a57e26d48fc793c5.png" alt="img"></p><h4 id="afterReturning方法参数类型的问题"><a href="#afterReturning方法参数类型的问题" class="headerlink" title="afterReturning方法参数类型的问题"></a><strong>afterReturning方法参数类型的问题</strong></h4><p>参数类型可以写成String，但是为了能匹配更多的参数类型，建议写成Object类型</p><h5 id="afterReturning方法参数的顺序问题"><a href="#afterReturning方法参数的顺序问题" class="headerlink" title="afterReturning方法参数的顺序问题"></a><strong>afterReturning方法参数的顺序问题</strong></h5><p><img src="https://i-blog.csdnimg.cn/direct/da50c67c8e0f41618098e690e18c8a6a.png" alt="img"></p><h5 id="为什么这里必须显示写上-value-“pt-”"><a href="#为什么这里必须显示写上-value-“pt-”" class="headerlink" title="为什么这里必须显示写上 value &#x3D; “pt( )”"></a>为什么这里必须显示写上 value &#x3D; “pt( )”</h5><ul><li>所有注解的核心属性都会命名为<code>value</code>；</li><li>如果给注解赋值时，<strong>只给 value 属性赋值</strong>，没有其他属性需要设置，就可以省略<code>value=</code>，直接写值；</li><li>如果需要给多个属性赋值（比如同时给<code>value</code>和<code>returning</code>赋值），就必须显式写出所有属性名，不能省略。</li></ul><h3 id="百度网盘密码数据兼容处理"><a href="#百度网盘密码数据兼容处理" class="headerlink" title="百度网盘密码数据兼容处理"></a>百度网盘密码数据兼容处理</h3><h4 id="开启SpringAOP的注解功能"><a href="#开启SpringAOP的注解功能" class="headerlink" title="开启SpringAOP的注解功能"></a>开启SpringAOP的注解功能</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="meta">@EnableAspectJAutoProxy</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="编写通知类"><a href="#编写通知类" class="headerlink" title="编写通知类"></a>编写通知类</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(boolean com.itheima.service.*Service.*(*,*))&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">servicePt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="添加环绕通知"><a href="#添加环绕通知" class="headerlink" title="添加环绕通知"></a>添加环绕通知</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(boolean com.itheima.service.*Service.*(*,*))&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">servicePt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;DataAdvice.servicePt()&quot;)</span></span><br><span class="line">    <span class="comment">// @Around(&quot;servicePt()&quot;)这两种写法都对</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">trimStr</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">ret</span> <span class="operator">=</span> pjp.proceed();</span><br><span class="line">        <span class="keyword">return</span> ret;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="完成核心业务，处理参数中的空格"><a href="#完成核心业务，处理参数中的空格" class="headerlink" title="完成核心业务，处理参数中的空格"></a>完成核心业务，处理参数中的空格</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(boolean com.itheima.service.*Service.*(*,*))&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">servicePt</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;DataAdvice.servicePt()&quot;)</span></span><br><span class="line">    <span class="comment">// @Around(&quot;servicePt()&quot;)这两种写法都对</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">trimStr</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="comment">//获取原始方法的参数</span></span><br><span class="line">        Object[] args = pjp.getArgs();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; args.length; i++) &#123;</span><br><span class="line">            <span class="comment">//判断参数是不是字符串</span></span><br><span class="line">            <span class="keyword">if</span>(args[i].getClass().equals(String.class))&#123;</span><br><span class="line">                args[i] = args[i].toString().trim();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//将修改后的参数传入到原始方法的执行中</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">ret</span> <span class="operator">=</span> pjp.proceed(args);</span><br><span class="line">        <span class="keyword">return</span> ret;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="AOP事务管理"><a href="#AOP事务管理" class="headerlink" title="AOP事务管理"></a>AOP事务管理</h3><h4 id="需求分析-2"><a href="#需求分析-2" class="headerlink" title="需求分析"></a>需求分析</h4><p>(1) 需求：实现任意两个账户间转账操作</p><p>(2) 需求微缩：A账户减钱，B账户加钱</p><h4 id="代码实现-16"><a href="#代码实现-16" class="headerlink" title="代码实现"></a>代码实现</h4><h5 id="在需要被事务管理的方法上添加注解"><a href="#在需要被事务管理的方法上添加注解" class="headerlink" title="在需要被事务管理的方法上添加注解"></a>在需要被事务管理的方法上添加注解</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">AccountService</span> &#123;</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 class="doctag">@param</span> out 传出方</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> in 转入方</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> money 金额</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(String out,String in ,Double money)</span> ;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AccountServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">AccountService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AccountDao accountDao;</span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(String out,String in ,Double money)</span> &#123;</span><br><span class="line">        accountDao.outMoney(out,money);</span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>/<span class="number">0</span>;</span><br><span class="line">        accountDao.inMoney(in,money);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="在JdbcConfig类中配置事务管理器"><a href="#在JdbcConfig类中配置事务管理器" class="headerlink" title="在JdbcConfig类中配置事务管理器"></a>在JdbcConfig类中配置事务管理器</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.driver&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String driver;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.url&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String url;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.username&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String userName;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jdbc.password&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">DruidDataSource</span> <span class="variable">ds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DruidDataSource</span>();</span><br><span class="line">        ds.setDriverClassName(driver);</span><br><span class="line">        ds.setUrl(url);</span><br><span class="line">        ds.setUsername(userName);</span><br><span class="line">        ds.setPassword(password);</span><br><span class="line">        <span class="keyword">return</span> ds;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//配置事务管理器，mybatis使用的是jdbc事务</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PlatformTransactionManager <span class="title function_">transactionManager</span><span class="params">(DataSource dataSource)</span>&#123;</span><br><span class="line">        <span class="type">DataSourceTransactionManager</span> <span class="variable">transactionManager</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DataSourceTransactionManager</span>();</span><br><span class="line">        transactionManager.setDataSource(dataSource);</span><br><span class="line">        <span class="keyword">return</span> transactionManager;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>**注意：**事务管理器要根据使用技术进行选择，Mybatis框架使用的是JDBC事务，可以直接使用<code>DataSourceTransactionManager</code></p><h4 id=""><a href="#" class="headerlink" title=""></a></h4><h5 id="开启事务注解"><a href="#开启事务注解" class="headerlink" title="开启事务注解"></a>开启事务注解</h5><p>在SpringConfig的配置类中开启</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">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(&quot;com.itheima&quot;)</span></span><br><span class="line"><span class="meta">@PropertySource(&quot;classpath:jdbc.properties&quot;)</span></span><br><span class="line"><span class="meta">@Import(&#123;JdbcConfig.class,MybatisConfig.class</span></span><br><span class="line"><span class="meta">//开启注解式事务驱动</span></span><br><span class="line"><span class="meta">@EnableTransactionManagement</span></span><br><span class="line"><span class="meta">public class SpringConfig &#123;</span></span><br><span class="line"><span class="meta">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="Spring事务属性"><a href="#Spring事务属性" class="headerlink" title="Spring事务属性"></a>Spring事务属性</h4><h5 id="事务配置"><a href="#事务配置" class="headerlink" title="事务配置"></a>事务配置</h5><img src="https://i-blog.csdnimg.cn/direct/319c60b9456b4a35b95f902a6b40f55b.png" alt="img" style="zoom:50%;" /><p>上面这些属性都可以在<code>@Transactional</code>注解的参数上进行设置。</p><p>① readOnly：true只读事务，false读写事务，增删改要设为false，查询设为true。</p><p>② timeout：设置超时时间单位秒，在多长时间之内事务没有提交成功就自动回滚，-1表示不设置超时时间。</p><p>③ rollbackFor：当出现指定异常进行事务回滚</p><h5 id="转账业务追加日志案例"><a href="#转账业务追加日志案例" class="headerlink" title="转账业务追加日志案例"></a><strong>转账业务追加日志案例</strong></h5><p><strong>① 需求分析</strong></p><ul><li>需求：实现任意两个账户间转账操作，并对每次转账操作在数据库进行留痕</li><li>需求微缩：A账户减钱，B账户加钱，数据库记录日志</li></ul><p><strong>② 代码实现</strong></p><p><strong>1). 添加LogDao接口</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">LogDao</span> &#123;</span><br><span class="line">    <span class="meta">@Insert(&quot;insert into tbl_log (info,createDate) values(#&#123;info&#125;,now())&quot;)</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">log</span><span class="params">(String info)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2). 添加LogService接口与实现类</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">LogService</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">log</span><span class="params">(String out, String in, Double money)</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">LogService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> LogDao logDao;</span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">log</span><span class="params">(String out,String in,Double money )</span> &#123;</span><br><span class="line">        logDao.log(<span class="string">&quot;转账操作由&quot;</span>+out+<span class="string">&quot;到&quot;</span>+in+<span class="string">&quot;,金额：&quot;</span>+money);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3). 在转账的业务中添加记录日志</strong></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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">LogService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> LogDao logDao;</span><br><span class="line"><span class="comment">//propagation设置事务属性：传播行为设置为当前操作需要新事务</span></span><br><span class="line">    <span class="meta">@Transactional(propagation = Propagation.REQUIRES_NEW)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">log</span><span class="params">(String out,String in,Double money )</span> &#123;</span><br><span class="line">        logDao.log(<span class="string">&quot;转账操作由&quot;</span>+out+<span class="string">&quot;到&quot;</span>+in+<span class="string">&quot;,金额：&quot;</span>+money);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="关于-Transactional"><a href="#关于-Transactional" class="headerlink" title="关于@Transactional"></a>关于@Transactional</h5><p><code>@Transactional</code>是<strong>事务管理注解</strong>，核心作用是控制业务层的数据库操作原子性，因此<strong>只应该写在业务层（Service 层）</strong>，<strong>且优先写在「实现类 &#x2F; 实现类方法」上，绝对不要写在 Controller 层或 DAO 层。</strong></p><h5 id="事务的传播行为"><a href="#事务的传播行为" class="headerlink" title="事务的传播行为"></a>事务的传播行为</h5><img src="https://i-blog.csdnimg.cn/direct/c3b1527555ef4eeb9158278c0acd4cdd.png" alt="img" style="zoom: 50%;" /><p>看前两个就行</p>]]></content>
    
    
    <summary type="html">Spring 核心概念、IoC/DI、Bean 与入门案例</summary>
    
    
    
    <category term="Spring" scheme="https://kon-forever.cloud/categories/Spring/"/>
    
    
    <category term="Spring" scheme="https://kon-forever.cloud/tags/Spring/"/>
    
  </entry>
  
</feed>
