<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title></title>
    <description></description>
    <link>https://www.someget.cn/</link>
    <atom:link href="https://www.someget.cn/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 04 Jan 2026 17:32:36 +0000</pubDate>
    <lastBuildDate>Sun, 04 Jan 2026 17:32:36 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>A New Stickerbox Experience — The Best AI-Integrated Hardware Product I’ve Seen Recently</title>
        <description>&lt;h2 id=&quot;preface&quot;&gt;Preface&lt;/h2&gt;

&lt;p&gt;This is the official website: &lt;a href=&quot;https://stickerbox.com&quot;&gt;https://stickerbox.com&lt;/a&gt;. It’s a kids product that combines AI image generation and printing, mainly targeting the children’s market. The logic is pretty simple: you can input a text prompt via voice or the app, generate an image, and then print it directly as a sticker.&lt;/p&gt;

&lt;p&gt;AI is on fire right now—both software and hardware products are popping up nonstop. I recently saw this super interesting AI hardware product on Instagram, and I honestly think it’s one of the best AIGC + product combinations I’ve seen lately. I’m genuinely surprised the founder came up with this angle and nailed the kids segment so precisely—kids naturally love drawing and stickers.&lt;/p&gt;

&lt;p&gt;When I checked the official site, it said new orders wouldn’t ship until February. But I got lucky: I saw it early, and the site shipped on 12/23 (even though I ordered in November). Really looking forward to it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260102104727995.png?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;image-20260101222510113&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Official promo image — looks pretty fun&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101222348628.png?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;image-20260101222348551&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Order details&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;unboxing&quot;&gt;Unboxing&lt;/h2&gt;

&lt;p&gt;When the package arrived, I noticed the box was quite small—not bulky at all.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101223629740.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8345&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Outer packaging&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;After opening it, the overall build quality felt pretty nice. A pleasant surprise: there was a signed card inside. I even touched it on purpose—you can tell it’s actually handwritten, not something mass-printed. Little details like this feel genuinely sincere.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260102093101277.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Everything in the box&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225621677.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8347&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Hand-signed card&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;The device itself comes with three rolls of printing paper, plus a small box of crayons so kids can color the black-and-white stickers after printing. The device is super light in hand—kids shouldn’t have any trouble holding or carrying it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101230123547.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8351&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Three included paper rolls&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225651560.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8350&quot; /&gt;&lt;/p&gt;
&lt;center&gt;The device itself — compact and lightweight&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225748199.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8352&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Included mini crayons&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;connection--setup&quot;&gt;Connection &amp;amp; Setup&lt;/h2&gt;

&lt;p&gt;Before using it, you need to download a dedicated app for connection and setup.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101230355887.PNG?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8348&quot; /&gt;&lt;/p&gt;
&lt;center&gt;App download guide&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;After powering on, you need to connect to Wi‑Fi and bind the device. The screen brightness is pretty good and the display looks clear.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101230804547.png?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Wi‑Fi connection screen&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101230222692.PNG?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8355&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Device binding flow&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225807586.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8356&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Binding successful — screen quality is decent&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;user-experience&quot;&gt;User Experience&lt;/h2&gt;

&lt;p&gt;When installing the paper, I did something a bit dumb. After opening the compartment, I saw a black roller bar in the back. Out of habit, I assumed it was the kind of printer that needs a ribbon or carbon film, so I fed the paper in facing that black strip… and it just wouldn’t print no matter what.&lt;/p&gt;

&lt;p&gt;Then it hit me: &lt;strong&gt;this is a thermal printer!&lt;/strong&gt; That strip is simply for gripping and feeding the paper. Thermal paper changes color when heated. Once I corrected the direction, everything worked fine. Paper loading is actually pretty convenient.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225920913.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8353&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Internal structure — classic thermal printer design&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;In real use, &lt;strong&gt;the responsiveness is way faster than I expected&lt;/strong&gt;. Both AI image generation and printing feel very smooth.&lt;/p&gt;

&lt;p&gt;For voice input, it &lt;strong&gt;supports Chinese&lt;/strong&gt;, which is a big plus. The downside is that the device’s built-in small screen doesn’t handle Chinese well—it doesn’t support UTF-8, so Chinese prompts show up as garbled “tofu blocks.” But the mobile app displays Chinese prompts normally. Also, since the servers are overseas, I’m not sure how the direct connection speed is from within mainland China, or whether network latency becomes an issue.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225936281.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8360&quot; /&gt;&lt;/p&gt;
&lt;center&gt;Printed result — the detail is pretty solid&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing Thoughts&lt;/h2&gt;

&lt;p&gt;This is honestly a naturally great application.&lt;/p&gt;

&lt;p&gt;For AIGC-generated images, there’s still a gap before they can truly match professional Figma design drafts or commercial creative posters. Most of the time, they’re more of an assistive tool, and you still need professional designers involved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But for kids, AI-generated images are already more than fun enough!&lt;/strong&gt; Kids aren’t chasing pixel-perfect design. As long as it’s interesting and can turn what’s in their head into a real, visible sticker, that’s enough. Stickerbox nails this point perfectly.&lt;/p&gt;

&lt;p&gt;I think the software-hardware integration is done really well, and the price isn’t bad either: &lt;strong&gt;$99&lt;/strong&gt;, including the printer and three boxes of paper. Refills cost $9.99 per box (50 stickers). The idea is genuinely great.&lt;/p&gt;

&lt;p&gt;But then again, &lt;strong&gt;the real masters of thermal printers are still China&lt;/strong&gt;. Thermal printing has already penetrated every corner in China: shipping labels, supermarket receipts, restaurant receipts, delivery receipts, and so on. The cost of thermal printers has been pushed extremely low—you can buy a decent one for just a few dozen RMB. And China has tons of thermal paper manufacturers that can customize paper in all kinds of sizes.&lt;/p&gt;

&lt;p&gt;If you built this domestically, the hardware cost might be covered with just &lt;strong&gt;50 RMB&lt;/strong&gt;. China really doesn’t lack technology, and the supply chain is insanely mature. What’s truly missing is this kind of &lt;strong&gt;creativity&lt;/strong&gt;—perfectly matching technology with a real-world scenario.&lt;/p&gt;
</description>
        <pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/en/other/2026/01/01/stickerbox.html</link>
        <guid isPermaLink="true">https://www.someget.cn/en/other/2026/01/01/stickerbox.html</guid>
        
        
        <category>en</category>
        
        <category>other</category>
        
      </item>
    
      <item>
        <title>Stickerbox新体验-近期觉得AI结合最好的硬件产品</title>
        <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;

&lt;p&gt;这是官网 &lt;a href=&quot;https://stickerbox.com&quot;&gt;https://stickerbox.com&lt;/a&gt;，这是一款结合了 AI 绘图和打印的儿童产品，主要面向儿童市场。它的逻辑很简单：可以通过语音或者 APP 输入文字描述，然后生成图片，最后直接打印出来成为贴纸。&lt;/p&gt;

&lt;p&gt;AI 现在很火，不管是软件产品还是硬件产品层出不穷。最近在 Ins 上看到这个非常有意思的 AI 硬件产品，我觉得这是近期与 AIGC 结合最好的产品了。我很惊讶于为什么 Founder 可以想到这个结合点，精准地抓住了儿童领域——孩子们天生喜欢画画和贴纸。&lt;/p&gt;

&lt;p&gt;我在官网看的时候发现现在下单要等到 2 月份才能发货，不过我运气不错，看到的时候很早，官网 12. 23 号就发货了(虽然我也是 11 月下单的)，非常期待。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260102104727995.png?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;image-20260101222510113&quot; /&gt;&lt;/p&gt;

&lt;center&gt;官网的宣传图，看起来很有趣&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101222348628.png?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;image-20260101222348551&quot; /&gt;&lt;/p&gt;
&lt;center&gt;订单详情&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;开箱&quot;&gt;开箱&lt;/h2&gt;

&lt;p&gt;收到快递的时候，发现盒子还挺小的，并不笨重。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101223629740.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8345&quot; /&gt;&lt;/p&gt;
&lt;center&gt;外包装盒子&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;打开包装，整体质感还是挺不错的。比较惊喜的是里面还有一张卡片带有签名，我特意摸了一下，确实是手写签名的痕迹，而不是批量印上去的，这种小细节让人感觉很有诚意。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260102093101277.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;center&gt;开箱全家福&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225621677.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8347&quot; /&gt;&lt;/p&gt;
&lt;center&gt;手写签名卡片&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;机器本身自带了三卷打印纸，还送了一盒小蜡笔，方便小朋友在打印出来的黑白贴纸上涂色。机器拿在手里非常轻，对于小朋友来说拿取应该没有任何负担。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101230123547.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8351&quot; /&gt;&lt;/p&gt;
&lt;center&gt;自带的三卷纸&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225651560.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8350&quot; /&gt;&lt;/p&gt;
&lt;center&gt;机器本体，很轻巧&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225748199.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8352&quot; /&gt;&lt;/p&gt;
&lt;center&gt;附赠的小蜡笔&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;连接与设置&quot;&gt;连接与设置&lt;/h2&gt;

&lt;p&gt;使用前需要下载一个专门的 App 进行连接和设置。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101230355887.PNG?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8348&quot; /&gt;&lt;/p&gt;
&lt;center&gt;App下载引导&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;开机后需要链接 WiFi 并绑定设备。屏幕亮度还挺不错的，显示很清晰。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101230804547.png?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;center&gt;连接WiFi界面&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101230222692.PNG?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8355&quot; /&gt;&lt;/p&gt;
&lt;center&gt;设备绑定流程&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225807586.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8356&quot; /&gt;&lt;/p&gt;
&lt;center&gt;绑定成功，屏幕素质还可以&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;使用体验&quot;&gt;使用体验&lt;/h2&gt;

&lt;p&gt;在安装打印纸的时候我稍微犯了个蠢。打开仓盖看到后面有一个黑色的滚条，我之前惯性思维以为是那种需要色带或者碳板的打印原理，把纸面对着这个黑色小条卡进去，结果死活打不出来。&lt;/p&gt;

&lt;p&gt;后来才反应过来，&lt;strong&gt;这是热敏打印机啊！&lt;/strong&gt; 那个小条单纯就是用来卡纸和传输纸张的，热敏纸本身受热就能变色。调整好方向后就一切正常了，卡纸过程其实挺方便的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225920913.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8353&quot; /&gt;&lt;/p&gt;
&lt;center&gt;内部结构，经典的热敏打印机构造&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;实际体验下来，&lt;strong&gt;响应速度比我想象中的快很多&lt;/strong&gt;。不管是 AI 出图的速度还是打印的速度，都非常流畅。&lt;/p&gt;

&lt;p&gt;在语音输入上，它是&lt;strong&gt;支持中文&lt;/strong&gt;的，这点好评。但是美中不足的是，机器自带的小屏幕对中文显示支持不好，不支持 UTF-8 字符集，中文描述内容在屏幕上会乱码变成“豆腐块”。不过手机 App 端是可以正常显示中文描述内容的。另外因为服务器在海外，不知道在中国国内直连的速度怎么样，是否有网络延迟的问题。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20260101225936281.jpg?x-oss-process=image/auto-orient,1/resize,w_1200,limit_0/format,webp/quality,Q_80&quot; alt=&quot;IMG_8360&quot; /&gt;&lt;/p&gt;
&lt;center&gt;打印出来的效果，精度还不错&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;后言&quot;&gt;后言&lt;/h2&gt;

&lt;p&gt;这其实是一个天然的好应用产品。&lt;/p&gt;

&lt;p&gt;对于 AIGC 生成的图片来说，目前离真正专业的 Figma 设计稿或者商业创意海报还有距离，更多时候只能作为辅助工具，还是需要专业设计师介入。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;但是对于儿童来说，AI 生成的图片已经足够好玩了！&lt;/strong&gt; 儿童本身并不追求 pixel-perfect 的设计感，只要有趣、能把脑子里的想法变成看得见的贴纸就行了。Stickerbox 这个产品就是精准地抓住了这个点。&lt;/p&gt;

&lt;p&gt;我觉得软硬件结合得很好，而且价格也不算贵，&lt;strong&gt;99 美金&lt;/strong&gt;，包含打印机和三盒纸。后续的耗材纸张是 9.99 美金一盒（50 张贴纸）。这个创意非常好。&lt;/p&gt;

&lt;p&gt;不过转念一想，&lt;strong&gt;玩转热敏打印机最厉害的还得是中国&lt;/strong&gt;。热敏技术在中国已经渗透到各个领域了：快递单、超市小票、餐厅小票、外卖小票等等。热敏打印机的成本也被压得非常低廉，几十块钱就能买到一个不错的热敏打印机了。而且中国有非常多的热敏打印纸厂家，可以提供各种规格的纸张定制。&lt;/p&gt;

&lt;p&gt;如果在国内做这个，可能硬件成本 &lt;strong&gt;50 元人民币&lt;/strong&gt; 就能 cover 下来。中国真的不缺技术，供应链极其成熟，缺的真的是这种把技术和场景完美结合的&lt;strong&gt;创意&lt;/strong&gt;。&lt;/p&gt;
</description>
        <pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/other/2026/01/01/stickerbox.html</link>
        <guid isPermaLink="true">https://www.someget.cn/other/2026/01/01/stickerbox.html</guid>
        
        
        <category>other</category>
        
      </item>
    
      <item>
        <title>Data automatic caching annotations</title>
        <description>&lt;h2 id=&quot;preface&quot;&gt;Preface&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20220515183156.png&quot; alt=&quot;image-20220515183156811&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;background&quot;&gt;Background&lt;/h3&gt;

&lt;p&gt;This post will introduce a library I wrote myself, library address &lt;a href=&quot;https://github.com/oreoft/cache-anno&quot;&gt;here&lt;/a&gt;, the main role is to provide an annotation, use this annotation in your method, the library provides functionality will help you automatically cache the data, the next time you call this method as long as the input parameter is the same as the direct will be from the cache! The next time you call the method, as long as the input parameter is the same, you will get the data directly from the cache, and you will not execute the method again (the content of the method may be to go to the DB or PRC).&lt;/p&gt;

&lt;p&gt;The reason for writing this library is that there is a similar tool within the company , at first did not Get to it has much use , as in the company to contact more business needs , found that this is really too usefull . And I have some of my own more ideas about it , so I intend to write their own open source out on the one hand can be used for their other open source projects , on the other hand, I think there are the same needs of the people are quite a lot , and Github does not have a perfect similar libraries.&lt;/p&gt;

&lt;p&gt;Different levels of volume of users of the code is really different, I also do in the last company is also the C end of the business, for the use of cache, although very much, but mainly the use of Redis data structure features to do some business implementation will be more, for example, using string to do mutual exclusion lock/configuration switches/status logging, using zset to do the list/realization of the needs of the dmq, list/set/hash to do high-performance short-term database. set/hash to do high performance short duration database. &lt;strong&gt;For a lot of DB data are directly check&lt;/strong&gt;, the main product at that time the user volume is very smooth, took the financing of the time the company has money, the database with a very good, from the library is also a lot of online do not need to pressure test, No requirement for RTT of the interface.&lt;/p&gt;

&lt;p&gt;But now the company, the C-terminal interface are done to limit the flow, in addition to paging data almost all the data to add a layer of cache, and even the first page of the paging data will be done. This is very troublesome, every time you have to write a lot of repetitive code, take the value from Redis, empty away on the DB/RPC, and then write back to Redis. the most horrible is the batch fetch value, from Redis to get after the hit after the batch out of the no hit and then go to DB/RPC fetch, and then write back to the value of this part of the Redis. because of the user volume is very large, and there is a wool party will brush the interface, the value of the no hit, may also need to do empty cache to prevent penetration to the DB. may also need to do empty cache to prevent penetration into the DB.&lt;/p&gt;

&lt;h3 id=&quot;contains&quot;&gt;Contains&lt;/h3&gt;

&lt;p&gt;This repository contains the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;@Cache annotation&lt;/li&gt;
  &lt;li&gt;Automatically cache the specified method&lt;/li&gt;
  &lt;li&gt;It can automatically cache non-existent data to prevent cache penetration under concurrency&lt;/li&gt;
  &lt;li&gt;Automatic mutex lock can be enabled when acquiring cache to prevent cache breakdown and protect DB&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Functions similar to Spring-cache, I think this library is much better than Spring-cache, first of all Spring-cache does not support the key for each write to set a separate expiration time, which Gives me good reason not to use it; and then EL expression on the cost of getting started is also very high, but it implements the function of the function is very simple and senseless function, I think that this is the same as Spring’s own convention over configuration contrary to this library provides four types (described below) allows you to follow the convention to reduce the configuration, the simplest only need to provide prefix can be used. I think this and Spring’s own convention over configuration contrary to what this library provides four types (described below) allows you to follow the convention to reduce the configuration , the simplest only need to provide prefix can be used . Spring-cache also does not support the bulk query process to query only the incremental data , also does not support the empty cache , also does not support the anti-penetration …..&lt;/p&gt;

&lt;h2 id=&quot;how-to-import&quot;&gt;how-to-import&lt;/h2&gt;

&lt;p&gt;This library has been put on the maven central warehouse, only need to import the pom file of the project. Please note &lt;strong&gt;that version 1.x.x are testing version and cannot work properly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For all version queries click &lt;a href=&quot;https://mvnrepository.com/artifact/cn.someget/cache-anno&quot;&gt;HERE&lt;/a&gt;&lt;/strong&gt; &lt;a href=&quot;https://mvnrepository.com/artifact/cn.someget/cache-anno&quot;&gt;&lt;strong&gt;HERE&lt;/strong&gt;&lt;/a&gt; &lt;a href=&quot;https://mvnrepository.com/artifact/cn.someget/cache-anno&quot;&gt;&lt;strong&gt;HERE&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;maven&quot;&gt;Maven&lt;/h3&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- https://mvnrepository.com/artifact/cn.someget/cache-anno --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;cn.someget&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;cache-anno&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.0.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;gradle&quot;&gt;Gradle&lt;/h3&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// https://mvnrepository.com/artifact/cn.someget/cache-anno&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;implementation&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;group:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;cn.someget&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;cache-anno&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;version:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;2.0.0&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There is no configure need to for this library, all beans are exposed through spring.factories and can be directly scanned by the startup class.&lt;/p&gt;

&lt;h2 id=&quot;how-to-use&quot;&gt;How-to-use&lt;/h2&gt;

&lt;h3 id=&quot;notes&quot;&gt;Notes&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;This library relies on spring’s auto-assembled redis or manually assembled redisTemplate, and the configuration supports jedis and lettuce.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The data stored using this annotation is serialized as String, and of course, the automatic read data deserialization of annotations is also String. If you use annotations to store data, but do not use annotations to read data, please use String deserialization to read.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;All redis io exceptions have been captured, and the exceptions will be printed to the log, which will not pollute the business code, will not affect your data reading, and will eventually read data from the db&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;

&lt;h4 id=&quot;1-getting-started-and-principles&quot;&gt;1. Getting Started and Principles&lt;/h4&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	&lt;span class=&quot;c1&quot;&gt;// It is recommended to define the prefix as a constant for easy reuse. One to one does not need to pass the clazz parameter&lt;/span&gt;
	&lt;span class=&quot;nd&quot;&gt;@Cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user:info:%d&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserInfoBO&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getIpUserInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;UserInfo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userInfoMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;selectByUid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;UserInfoBO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserInfoBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;BeanUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the above method is not annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Cache&lt;/code&gt;, it is a simple method to query user information from the user table via uid, but it is still such a simple method after adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Cache&lt;/code&gt;.&lt;br /&gt;
Before the query, the parameter uid will be spliced according to the prefix in the annotation, and then try to get data from Redis. &lt;br /&gt;
&lt;strong&gt;If the execution function does not hit the cache, it will be automatically written to the cache after the execution is completed.&lt;/strong&gt;&lt;br /&gt;
The next time this function is executed, the prefix splicing parameter will be executed and the data will be tried to obtain from Redis. Because it was written automatically last time, the data will be returned directly, and the function will not be executed again.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20220514204948.png&quot; alt=&quot;image-20220514204948154&quot; style=&quot;zoom:67%;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;2-advanced-comprehension&quot;&gt;2. advanced comprehension&lt;/h4&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	&lt;span class=&quot;c1&quot;&gt;// The return value is a collection type, clazz must be passed&lt;/span&gt;
	&lt;span class=&quot;nd&quot;&gt;@Cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mall:item:%d&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;listItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemsIds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;BaseResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MallItemsDTO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemsRemoteClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;listItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ItemsReqDTO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CollectionUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;emptyMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mallItemsDTO&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;MallItemsBO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mallItemsBO&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;BeanUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mallItemsDTO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;MallItemsBO:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getItemId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;identity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The method is not annotated with @Cache. It is a function to remotely obtain item details from other services in batches through itemId.&lt;br /&gt;
When @Cache is added, it is still the original method. &lt;strong&gt;Before the query, all itemIds in the parameters and the prefix in the annotation will be spliced together, and then the results will be obtained from redis at one time. If all itemIds are obtained, they will be returned directly.
If there is an itemId that is not hit, the missed itemId will be unified and used for remote fetching. Finally, the summary in Redis (the remote fetching will be automatically written to the cache)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20220514204802.png&quot; alt=&quot;image-20220514204802863&quot; style=&quot;zoom:67%;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;3-supported-method-types&quot;&gt;3. Supported method types&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;type&lt;/th&gt;
      &lt;th&gt;prefix&lt;/th&gt;
      &lt;th&gt;input&lt;/th&gt;
      &lt;th&gt;output&lt;/th&gt;
      &lt;th&gt;remark&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;one to one&lt;/td&gt;
      &lt;td&gt;custom: placeholder&lt;/td&gt;
      &lt;td&gt;wrapper type or String&lt;/td&gt;
      &lt;td&gt;? extends Object&lt;/td&gt;
      &lt;td&gt;The number of input parameters is equal to the number of placeholders&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ont to list&lt;/td&gt;
      &lt;td&gt;custom: placeholder&lt;/td&gt;
      &lt;td&gt;wrapper type or String&lt;/td&gt;
      &lt;td&gt;List&amp;lt;? extends Object&amp;gt;&lt;/td&gt;
      &lt;td&gt;The same as above, in theory, List and object are same thing for this library, because I use String serialization&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;list to map_one&lt;/td&gt;
      &lt;td&gt;custom: placeholder&lt;/td&gt;
      &lt;td&gt;List&lt;wrapper type=&quot;&quot; or=&quot;&quot; String=&quot;&quot;&gt;&lt;/wrapper&gt;&lt;/td&gt;
      &lt;td&gt;Map&amp;lt;Input wrapper type or String,  ? extends Object&amp;gt;&lt;/td&gt;
      &lt;td&gt;If it is a batch query, the first input parameter must be the corresponding query List. Each element in the list will be spliced with the prefix, so the placeholder of the prefix is the placeholder corresponding to the element in the list.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;list to map_map&lt;/td&gt;
      &lt;td&gt;custom: placeholder&lt;/td&gt;
      &lt;td&gt;List&lt;wrapper type=&quot;&quot; or=&quot;&quot; String=&quot;&quot;&gt;&lt;/wrapper&gt;&lt;/td&gt;
      &lt;td&gt;Map&amp;lt;Input wrapper type or String,  List&amp;lt;? extends Object»&lt;/td&gt;
      &lt;td&gt;This type is actually the same as above. Each element in the type List corresponds to an object. Each element of this type List corresponds to a list. I deserialize the same.&lt;br /&gt;&lt;strong&gt;Due to the limitation of java’s generic erasure, it is impossible to determine what the value generic of Map is. Please set the parameter hasMoreValue in @Cache to true&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Placeholders should be noted that the string type requires the placeholder is %s, the integer placeholder is %d, and the floating-point placeholder is %f &lt;a href=&quot;https://www.cnblogs.com/happyday56/p/3996498.html&quot;&gt;Please refer to here for details&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The summary is divided into two categories. The input parameter is an object or a List, that is, a single acquisition and a batch acquisition. If it is a batch acquisition, remember that the List must be No. 1, and the method input parameters cannot exceed two, otherwise the unsupported method will be thrown. abnormal.&lt;/p&gt;

&lt;h4 id=&quot;4-the-meaning-of-the-parameters-in-the-annotation&quot;&gt;4. The meaning of the parameters in the annotation&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;name&lt;/th&gt;
      &lt;th&gt;meaning&lt;/th&gt;
      &lt;th&gt;remarks&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;prefix&lt;/td&gt;
      &lt;td&gt;The prefix of the key in Redis&lt;/td&gt;
      &lt;td&gt;To use a placeholder, if the input parameter is long, the placeholder is prefixKey:%d&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;expire&lt;/td&gt;
      &lt;td&gt;Expiration time (in seconds)&lt;/td&gt;
      &lt;td&gt;If you do not set the expiration time when using annotations, the default is 10 minutes. Note that the expiration time is enabled by default in the write cache of this library.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;missExpire&lt;/td&gt;
      &lt;td&gt;Empty cache expiration time (in seconds)&lt;/td&gt;
      &lt;td&gt;If it is 0, it means that the empty cache is not enabled (the default is 0). The expiration time of the empty cache means that if the result is not found from the db, an empty cache will be generated to Redis. The expiration time of this empty cache (the normal cache must be short, recommended 3- 10 seconds)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;hasMoreValue&lt;/td&gt;
      &lt;td&gt;Whether list to map_map type&lt;/td&gt;
      &lt;td&gt;Due to the limitation of generic erasure in java, it is impossible to determine what the value generic of Map is. Please set the parameter hasMoreValue in @Cache to true&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;clazz&lt;/td&gt;
      &lt;td&gt;Collection class return value corresponding type&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;If the return value is List or Map&lt;/strong&gt;, &lt;strong&gt;this must be passed&lt;/strong&gt;, because java generic erasure leads to inability to perceive the generic type of the collection, and deserialization needs to be used. If it is a one to one type, this can be omitted.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;usingLocalCache&lt;/td&gt;
      &lt;td&gt;Whether to use local cache&lt;/td&gt;
      &lt;td&gt;After setting true, the local cache (using caffeine) will be queried before reading from Redis. Similarly, the data will be written back to caffeine after taking it.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;5-detailed-description-of-other-functions&quot;&gt;5. Detailed description of other functions&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;Enable empty cache writes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;@Cache contains the attribute missExpire. The meaning of the attribute is the expiration time of the value that does not exist in the DB (in seconds).
The default value is 0. If it is 0, it means that if the value of the query does not exist in the DB, no empty cache processing will be performed.
If it is not 0, then the query value from Redis will not be hit, and the method query will be performed. If the method query returns no result,
a null value will be stored (if it is an object, it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;{\&quot;id\&quot;:-1}&quot;&lt;/code&gt; , if it is a collection, it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt;), the expiration time is the value of missExpire (recommended 3-10 seconds),
and all types in the table are supported.
&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20220514213607.png&quot; alt=&quot;image-20220514213607659&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;After the empty cache is enabled, the cache processing needs to be deleted after inserting records, because the corresponding value may already exist in the DB, but there is still an empty value in Redis that is in the TTL.&lt;/li&gt;
  &lt;li&gt;&lt;del&gt;If the empty cache is an object, it will cache an object with an id of -1. If it is a collection, an empty collection will be cached. An object with an id of -1 will not be returned to the method caller and will be filtered out directly, which is in line with your coding habits.
&lt;strong&gt;It should be noted that the cache object must have an id field (both Integer and Long), otherwise it cannot be filtered, which will return an empty object with all properties null.&lt;/strong&gt;&lt;/del&gt;
Version 2.0.1 or later supports setting the object empty cache to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;{}&quot;&lt;/code&gt;, so it does not have to contain an id. The caller of the method that hits the empty cache will get null, which is in line with everyone’s coding habits.
All empty cache objects must be constructed without parameters, otherwise deserialization cannot generate empty objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;enable local cache&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Cache&lt;/code&gt; contains the attribute usingLocalCache, which means enable local cache or not,
In e-commerce marketing, commodity data is frequently obtained through RPC, because the QPS of the marketing scenario is very high. Even if there is Redis before RPC, the frequent acquisition of commodities leads to a very high QPS of Redis.
The product data does not change very much, so it is necessary to add a local cache before redis, which will reduce the qps of redis&lt;/p&gt;

&lt;p&gt;There are many scenarios where using local cache will reduce qps.
This library supports local caching. Just use the annotation to set the attribute of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usingLocalCache&lt;/code&gt; to true (default is false).
The local cache used by this library is caffeine, which has recently overwhelmed Guava. , so that the local cache is queried before getting the data, and if the local cache does not hit, then Redis is queried.&lt;/p&gt;

&lt;p&gt;Note: Multi-layer caching will increase the possibility of Cache-DB inconsistency. Here, the default TTL of the local cache is 3 seconds, and modification is not supported for the time being.&lt;/p&gt;

&lt;h2 id=&quot;post-word&quot;&gt;Post-word&lt;/h2&gt;

&lt;p&gt;next-steps&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Improve unit testing, welcome everyone to pr&lt;/li&gt;
  &lt;li&gt;There are already good solutions to prevent cache breakdown, and the next version will merge&lt;/li&gt;
  &lt;li&gt;Evicting the cache using annotations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your joining is very welcome! &lt;a href=&quot;https://github.com/oreoft/cache-anno/issues/new&quot;&gt;New Issue&lt;/a&gt; or Pull Request.&lt;/p&gt;
</description>
        <pubDate>Sun, 15 May 2022 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/en/java/2022/05/15/cache-anno01.html</link>
        <guid isPermaLink="true">https://www.someget.cn/en/java/2022/05/15/cache-anno01.html</guid>
        
        
        <category>en</category>
        
        <category>java</category>
        
      </item>
    
      <item>
        <title>用无感知的方式为你的数据加上一层缓存</title>
        <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20220515183156.png&quot; alt=&quot;image-20220515183156811&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;背景&quot;&gt;背景&lt;/h3&gt;

&lt;p&gt;本篇文章会介绍一个我自己写的库，库地址在&lt;a href=&quot;https://github.com/oreoft/cache-anno&quot;&gt;这里&lt;/a&gt;，主要作用是提供一个注解，在你方法上使用这个注解，库提供的功能会帮你把数据自动缓存起来，下次再调用这个方法只要入参是一致的则直接会从缓存里面拿数据，不会再执行方法了(方法里面的内容可能是走DB或者PRC)。&lt;/p&gt;

&lt;p&gt;写这个库的原因是公司内部有一个类似工具，刚开始并没有Get到它有多大的用处，随着在公司接触更多的业务需求，发现这真的是太香了。并且我对它有一些自己的更多想法，所以打算自己写一个开源出来一方面是可以给自己其他开源项目使用，另外一方面我觉得有相同需求的人还蛮多的，而Github上也没有一个尽善尽美的类似库。&lt;/p&gt;

&lt;p&gt;不同量级用户的代码真的是不一样，我在原来公司也做的也是C端的业务，对于缓存运用虽然非常多，但是主要是利用Redis数据结构特点来做一些业务上的实现会更多一些，例如用string来做互斥锁/配置开关/状态记录，用zset来做榜单/实现dmq需求，用list/set/hash来做高性能短时数据库。&lt;strong&gt;对于很多DB的数据都是直接查&lt;/strong&gt;，主要当时的产品用户量非常平稳，拿了融资的那段时间公司有钱，数据库用的非常好，从库也多， 上线不需要压测，随便点点RTT自己觉得过得去就行，所以我觉得偶尔写一下读写redis的代码也没啥用工作量。&lt;/p&gt;

&lt;p&gt;但是现在的公司，C端接口都做限流，除了分页数据几乎所有数据都加一层缓存，甚至分页数据的第一页都会做。这就很麻烦，每次都要写一大堆重复代码，从Redis取值，空走就DB/RPC，然后回写Redis。最可怕的是批量取值，从Redis去拿完以后还要把没有命中的筛选出来然后去DB/RPC取，然后回写这部分值到Redis。因为用户量很大，还有羊毛党会刷接口，未命中的值可能还需要做空缓存防止穿透到DB。&lt;/p&gt;

&lt;h3 id=&quot;内容&quot;&gt;内容&lt;/h3&gt;

&lt;p&gt;本仓库包含以下内容：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;@Cache注解一个&lt;/li&gt;
  &lt;li&gt;对指定方法进行自动缓存(Redis或者caffeine本地缓存)&lt;/li&gt;
  &lt;li&gt;可对不存在的数据进行自动空缓存，并发下防止缓存穿透&lt;/li&gt;
  &lt;li&gt;可开启获取缓存时自动进行互斥锁，防止缓存击穿保护DB(下个版本更新)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;功能类似Spring-cache，但是我普信男一次，我觉得本库比Springcache的好用很多，首先Spring-cache的不支持给每次写入的key单独设置过期时间，这个已经劝退我了；然后EL表达式上手成本也很高，但是它实现的功能是很简单的无感功能，我觉得这和Spring本身的约定大于配置相违背，相反呢本库提供四种类型(下面介绍)让你遵循约定减少配置，最简单只需要提供prefix就可以使用。Spring-cache也不支持批量查询过程中只查询增量数据，也不支持空缓存，也不支持防击穿…..&lt;/p&gt;

&lt;h2 id=&quot;安装导入&quot;&gt;安装导入&lt;/h2&gt;

&lt;p&gt;本库已经上架maven中央仓库，已经引入到自己项目pom文件中就行，&lt;strong&gt;请注意直接在mvnrepository会出现很多2.0.0以下的版本，请不要使用&lt;/strong&gt;，那…那…是我上架的是做测试不小心发到release上的debug版本。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;所有版本查询请点击&lt;a href=&quot;https://mvnrepository.com/artifact/cn.someget/cache-anno&quot;&gt;这里&lt;/a&gt;&lt;/strong&gt; &lt;a href=&quot;https://mvnrepository.com/artifact/cn.someget/cache-anno&quot;&gt;&lt;strong&gt;这里&lt;/strong&gt;&lt;/a&gt; &lt;a href=&quot;https://mvnrepository.com/artifact/cn.someget/cache-anno&quot;&gt;&lt;strong&gt;这里&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maven&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- https://mvnrepository.com/artifact/cn.someget/cache-anno --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;cn.someget&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;cache-anno&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.0.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Gradle&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// https://mvnrepository.com/artifact/cn.someget/cache-anno&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;implementation&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;group:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;cn.someget&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;cache-anno&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;version:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;2.0.0&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;导入jar包就好了，除此之外无需为本库做任何配置，所有bean也已经spring.factories暴露出来，可以直接被启动类扫描到。&lt;/p&gt;

&lt;h2 id=&quot;使用说明&quot;&gt;使用说明&lt;/h2&gt;

&lt;h3 id=&quot;一-注意事项&quot;&gt;一. 注意事项&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;导入本库后配置中必须要有redis相关的配置，支持jedis和lettuce，只要spring自动装配或者你手动装配一个redisTemplate就行(注意如果你有多个RedisTemplate&amp;lt;String, String&amp;gt;你需要给其中一个指定@Primary)，不然无法使用本库。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;使用注解存入的数据，序列化方式均为String，当然注解自动读数据反序列化也是String，所以如果你有使用注解存入数据，但是不适用注解读取数据的需求，请使用String反序列化读取。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;所有redis的io异常都已经捕获，有异常只会打日志，不会影响你的业务，不会影响你的数据读取，兜底会走db查询。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;二--使用方法&quot;&gt;二.  使用方法&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;查询一个数据，例如下面，通过uid查询用户的详细数据，加上@Cache(注意不要导错包哈)注解，prefix是你缓存数据Redis的Key前缀，注意需要加一个占位符，我自动写入的时候会把入参拼接到这个prefix上。&lt;strong&gt;查询之前会根据注解中的prefix拼接上入参uid从Redis中尝试获取数据，如果没有数据则执行方法，执行完方法再自动写入缓存&lt;/strong&gt;。那么下次在执行这个方法的时候，又会执行上面步骤prefix拼接入参尝试从Redis获取数据，但是因为上次自动写入了，所以拿到数据就直接返回了，不会再执行方法走DB查询了。&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 建议prefix定义成常量，便于复用, one to one可以不用传clazz参数&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user:info:%d&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserInfoBO&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getIpUserInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;UserInfo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userInfoMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;selectByUid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
     	 &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;UserInfoBO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserInfoBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;BeanUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;查询多个数据，例如下面，通过一堆itemId获取多个item的详细数据，和上面一样你需要指定prefix，查询之前会通过把入参所有的itemId进行和注解里面的prefix拼接，然后&lt;strong&gt;批量一次性尝试从redis里面获取，如果所有itemId都获取到则直接返回，但是如果有未命中的itemId则会把这未命中的itemId再统一走方法进行远程获取最后和Redis里面的汇总(远程获取完 会自动写入Redis,方便你下次命中)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 返回值是集合类型，clazz必须要传&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mall:item:%d&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;listItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemsIds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;BaseResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MallItemsDTO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemsRemoteClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;listItems&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ItemsReqDTO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CollectionUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
     	 &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;emptyMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mallItemsDTO&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       	 &lt;span class=&quot;nc&quot;&gt;MallItemsBO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mallItemsBO&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
       	 &lt;span class=&quot;nc&quot;&gt;BeanUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mallItemsDTO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       	 &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mallItemsBO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;MallItemsBO:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getItemId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;identity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;三-实现原理&quot;&gt;三. 实现原理&lt;/h3&gt;

&lt;p&gt;核心原理是利用AOP对你为注解设置的参数和入参进行拼接成一个key，每次在方法执行前走缓存去查询一遍，如果缓存有数据则直接返回不再执行方法，如果缓存没有查数据，则执行方法拿到数据取写入缓存。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20220514204948.png&quot; alt=&quot;image-20220514204948154&quot; /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;单个数据获取&lt;/p&gt;

&lt;p&gt;如果是数据批量获取，还会看Redis命中的数量来判断是全部返回，还是把未命中的去执行方法，最后把缓存和方法查询结果一起汇总返回给方法调用方&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20220514204802.png&quot; alt=&quot;image-20220514204802863&quot; /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;多个数据获取&lt;/p&gt;

&lt;h3 id=&quot;四-所有支持的方法类型&quot;&gt;四. 所有支持的方法类型&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;prefix&lt;/th&gt;
      &lt;th&gt;入参&lt;/th&gt;
      &lt;th&gt;出参&lt;/th&gt;
      &lt;th&gt;备注&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;one to one&lt;/td&gt;
      &lt;td&gt;自定义:占位符&lt;/td&gt;
      &lt;td&gt;包装类型或者String&lt;/td&gt;
      &lt;td&gt;? extends Object&lt;/td&gt;
      &lt;td&gt;有几个入参就要有几个占位符，不然无法使用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ont to list&lt;/td&gt;
      &lt;td&gt;自定义:占位符&lt;/td&gt;
      &lt;td&gt;包装类型或者String&lt;/td&gt;
      &lt;td&gt;List&amp;lt;? extends Object&amp;gt;&lt;/td&gt;
      &lt;td&gt;同上，理论上来说List和object对于本库是一个东西，因为我是用的是String的序列化，相同理解就好。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;list to map_one&lt;/td&gt;
      &lt;td&gt;自定义:占位符&lt;/td&gt;
      &lt;td&gt;List&lt;包装类型或者String&gt;&lt;/包装类型或者String&gt;&lt;/td&gt;
      &lt;td&gt;Map&amp;lt;对应入参包装类型或者String,  ? extends Object&amp;gt;&lt;/td&gt;
      &lt;td&gt;如果是批量查询，第一个入参一定要是对应的查询List。list里面的每一个元素都会与prefix拼接，所以prefix的占位符是List里面的元素对应的占位符。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;list to map_map&lt;/td&gt;
      &lt;td&gt;自定义:占位符&lt;/td&gt;
      &lt;td&gt;List&lt;包装类型或者String&gt;&lt;/包装类型或者String&gt;&lt;/td&gt;
      &lt;td&gt;Map&amp;lt;对应入参包装类型或者String,  List&amp;lt;? extends Object»&lt;/td&gt;
      &lt;td&gt;本类型其实也同上，上类型List中每一个元素对应的是一个对象，这个类型List每个元素对应的是一个list，我反序列化都是以一样的，所以本质一样。&lt;br /&gt;&lt;strong&gt;因为java的泛型擦除的限制我无法判断Map的value泛型具体是什么， 请@Cache中的参数hasMoreValue需要设置成true，请切记&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;其中占位符要注意，如果是String占位符要%s，整型占位符%d，浮点型占位符%f  &lt;a href=&quot;https://www.cnblogs.com/happyday56/p/3996498.html&quot;&gt;详情请参考这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;其实总的来说就是分为两类，入参是对象或者List，也就是单个获取和批量获取，如果是批量获取的话切记List要在一号位，并且方法入参不能超过两个，否则会报错提示不支持。&lt;/p&gt;

&lt;h3 id=&quot;五-注解里面的参数含义&quot;&gt;五. 注解里面的参数含义&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;名称&lt;/th&gt;
      &lt;th&gt;含义&lt;/th&gt;
      &lt;th&gt;备注&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;prefix&lt;/td&gt;
      &lt;td&gt;Redis中key的前缀,&lt;/td&gt;
      &lt;td&gt;注意要使用占位符，如入参是long, 占位符就是prefixKey:%d&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;expire&lt;/td&gt;
      &lt;td&gt;过期时间(单位秒)&lt;/td&gt;
      &lt;td&gt;如果使用注解的时候不设置则默认10分钟，注意本库写入缓存都有过期时间，因为我想不到你为啥要不设置TTL&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;missExpire&lt;/td&gt;
      &lt;td&gt;空缓存过期时间(单位秒)&lt;/td&gt;
      &lt;td&gt;如果是0则表示不开启空缓存(默认是0)，空缓存过期时间表示如果从db也没查到生成空缓存到Redis，这个空缓存的过期时间(肯定正常缓存短，推荐3-10秒)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;hasMoreValue&lt;/td&gt;
      &lt;td&gt;是否list to map_map类型&lt;/td&gt;
      &lt;td&gt;因为java的泛型擦除的限制我无法判断Map的value泛型具体是什么， 请@Cache中的参数hasMoreValue需要设置成true，请切记&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;clazz&lt;/td&gt;
      &lt;td&gt;集合类返回值对应类型&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;如果返回值是List或者Map&lt;/strong&gt;，&lt;strong&gt;这个必传&lt;/strong&gt;，因为java泛型擦除我不知道你集合泛型，反序列化需要使用。如果是one to one类型的话，这个可以省略。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;usingLocalCache&lt;/td&gt;
      &lt;td&gt;是否使用本地缓存&lt;/td&gt;
      &lt;td&gt;设置true以后从Redis读取之前会查询一遍本地缓存(使用caffeine)，同理拿完数据也会回写到caffeine&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;六--其他功能详细说明&quot;&gt;六.  其他功能详细说明&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;启用空缓存写入&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;@Cache含有missExpire的属性，属性含义是DB中不存在的值的过期时间(单位是秒)，默认值是0，如果是0则表示如果查询的值DB中不存在的话，不进行空缓存处理。如果是非0，那么从Redis中查询值未命中会走方法查询，方法查询返回结果也是不存在那么会储存一个空值(如果是对象的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;{}&quot;&lt;/code&gt;，如果是集合则是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt;)，过期时间是missExpire这个值(推荐3-10秒左右)，支持上述表格中的所有类型。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20220515180859.png&quot; alt=&quot;image-20220515180859240&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;开启空缓存以后插入记录后也要进行删除缓存处理，因为可能对应值DB中已经有了，但是Redis还存在空值正处于TTL中。&lt;/li&gt;
  &lt;li&gt;&lt;del&gt;空缓存如果是对象是会缓存id为-1的对象，如果是集合会缓存一个空集合，id为-1的对象不会返回给方法调用方，会直接被过滤掉，符合大家编码习惯。&lt;strong&gt;要注意的是缓存对象必须要有id字段(Integer和Long都可以)，否则无法过滤会返回的一个所有属性都是null的空对象。&lt;/strong&gt;&lt;/del&gt;2.0.1版已经支持设置对象空缓存为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;{}&quot;&lt;/code&gt;所以不用必须含有id字段了，如果命中空缓存方法调用方拿到的是null，符合大家编码习惯，&lt;strong&gt;但是所有空缓存对象一定要有无参构造，否则反序列化无法生成空对象。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;启用本地缓存&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;@Cache含有usingLocalCache的属性，属性含义是是否启用本地缓存，在一些场景下电商营销域要频繁通过RPC获取商品域的商品数据，因为营销场景QPS非常高，即便在RPC之前有一层Redis，但是频繁获取商品导致Redis的QPS非常高。而商品数据是不怎么变化的，所以在Redis之前再加一层本地缓存就显得非常有必要，Redis的QPS能降低不少。&lt;/p&gt;

&lt;p&gt;有不少场景进行本地缓存提升都非常大，本库也支持进行本地缓存，只需使用注解是把usingLocalCache的属性设置为true(默认是false)，本库使用的本地缓存是最近风头压过guava的caffeine，这样获取数据之前先从本地缓存进行查询，如果本地缓存没有命中则再去查询Redis。&lt;/p&gt;

&lt;p&gt;注意：多层缓存会增加Cache-DB不一致可能，一定程度抗流可以用，但是不要过分依赖，这里本地缓存默认TTL是3秒，暂时不支持修改。&lt;/p&gt;

&lt;h2 id=&quot;后言&quot;&gt;后言&lt;/h2&gt;

&lt;p&gt;下一步计划是&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;写好单元测试，这个库我还会写一篇文章，基于单测的code更加直观来介绍它是怎么的好用。&lt;/li&gt;
  &lt;li&gt;防止缓存击穿已经有好的方案，下一个版本会迭代进去，对于高风险接口可以开启。&lt;/li&gt;
  &lt;li&gt;目前没有配套的缓存逐出注解，这个我想一下怎么做到最好&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果你感兴趣或者有相同的需求欢迎使用本库，更加&lt;a href=&quot;https://github.com/oreoft/cache-anno/issues/new&quot;&gt;提一个 Issue&lt;/a&gt; 或者提交一个 Pull Request。&lt;/p&gt;
</description>
        <pubDate>Sun, 15 May 2022 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/java/2022/05/15/cache-anno01.html</link>
        <guid isPermaLink="true">https://www.someget.cn/java/2022/05/15/cache-anno01.html</guid>
        
        
        <category>java</category>
        
      </item>
    
      <item>
        <title>Transaction Isolation Levels in Databases</title>
        <description>&lt;h2 id=&quot;preface&quot;&gt;Preface&lt;/h2&gt;

&lt;p&gt;Transaction isolation levels used to be one of those “must-memorize interview boilerplate” topics back when I was looking for internships and jobs. At the time I could recite them, but I didn’t really understand what they meant or how they were implemented. As I got deeper into engineering practice and accumulated more experience using databases, I started to see transaction isolation levels differently. Recently I’ve been asked about them a lot again, so I deliberately went back and reviewed Ding Qi’s &lt;em&gt;MySQL 45 Lectures in Practice&lt;/em&gt;, and decided to summarize it once.&lt;/p&gt;

&lt;p&gt;Everyone knows the ACID properties of transactions. In order to ensure these properties—especially isolation and consistency—databases generally rely on locking. Locks in databases exist to build these isolation levels. This post only introduces transaction isolation levels; the next post will analyze how MySQL’s InnoDB engine implements them.&lt;/p&gt;

&lt;h2 id=&quot;database-isolation-levels&quot;&gt;Database Isolation Levels&lt;/h2&gt;

&lt;p&gt;Let’s start with a table. Note: this is the isolation-level definition from textbooks. Different database implementations may vary—for example, MySQL’s InnoDB prevents phantom reads under Repeatable Read.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Isolation Level&lt;/th&gt;
      &lt;th&gt;Dirty Read&lt;/th&gt;
      &lt;th&gt;Non-repeatable Read&lt;/th&gt;
      &lt;th&gt;Phantom Read&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Read Uncommitted&lt;/td&gt;
      &lt;td&gt;Possible&lt;/td&gt;
      &lt;td&gt;Possible&lt;/td&gt;
      &lt;td&gt;Possible&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Read Committed&lt;/td&gt;
      &lt;td&gt;Impossible&lt;/td&gt;
      &lt;td&gt;Possible&lt;/td&gt;
      &lt;td&gt;Possible&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Repeatable Read&lt;/td&gt;
      &lt;td&gt;Impossible&lt;/td&gt;
      &lt;td&gt;Impossible&lt;/td&gt;
      &lt;td&gt;Possible (but impossible in MySQL InnoDB)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Serializable&lt;/td&gt;
      &lt;td&gt;Impossible&lt;/td&gt;
      &lt;td&gt;Impossible&lt;/td&gt;
      &lt;td&gt;Impossible&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Read Uncommitted → dirty reads&lt;/strong&gt;: Two transactions start at the same time. Transaction A can read changes made by transaction B that are &lt;strong&gt;not committed&lt;/strong&gt;. That’s a &lt;strong&gt;dirty read&lt;/strong&gt;. You don’t really need to memorize what a dirty read is—the name “Read Uncommitted” basically &lt;em&gt;is&lt;/em&gt; dirty read, because it breaks isolation: two transactions should not affect each other directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read Committed → non-repeatable reads&lt;/strong&gt;: Two transactions start at the same time. Transaction A can read changes made by transaction B that are &lt;strong&gt;already committed&lt;/strong&gt;. That’s a &lt;strong&gt;non-repeatable read&lt;/strong&gt;. Same idea: you don’t need to force-memorize the definition—if you can read other transactions’ committed changes, isolation still isn’t guaranteed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeatable Read → phantom reads&lt;/strong&gt;: Two transactions start at the same time. Under this isolation level, changes made by transaction B (&lt;strong&gt;update existing rows and commit&lt;/strong&gt;) won’t be visible to transaction A’s queries, meaning it can &lt;strong&gt;solve the non-repeatable read problem&lt;/strong&gt;. &lt;strong&gt;But if transaction B inserts new rows and commits, transaction A may be able to query those newly inserted rows—this is a phantom read.&lt;/strong&gt; The root cause is the locking mechanism. However, MySQL’s InnoDB can ensure those rows are not visible under Repeatable Read; we’ll discuss that in the next post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serializable → no issues due to serialized locking&lt;/strong&gt;: Transactions execute serially. Each read needs to acquire a shared lock, and reads/writes block each other.&lt;/p&gt;

&lt;p&gt;MySQL InnoDB defaults to &lt;strong&gt;REPEATABLE READ&lt;/strong&gt; (RR below), while Alibaba Cloud RDS defaults to &lt;strong&gt;Read Committed&lt;/strong&gt; (RC below). Next, let’s look at isolation levels through concrete SQL.&lt;/p&gt;

&lt;h2 id=&quot;demo&quot;&gt;Demo&lt;/h2&gt;

&lt;p&gt;For the database client I’m using DataGrip (DG below). In DG, open two sessions and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;begin&lt;/code&gt; in each to start two transactions.&lt;/p&gt;

&lt;p&gt;The database is MySQL 8.0. The test table DDL is:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;   &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tinyint&lt;/span&gt;      &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Test data:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;zhangsan&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;wangwu&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;xiaohong&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;xiaohua&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check the current session isolation level:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;查看当前会话隔离级别&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MySQL8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction_isolation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;查看当前会话隔离级别&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MySQL5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tx_isolation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Set the current session isolation level:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;设置当前会话的隔离级别&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;isolation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;uncommitted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;isolation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;committed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;isolation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;repeatable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;isolation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;serializable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;read-uncommitted&quot;&gt;Read Uncommitted&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225214238.png&quot; alt=&quot;image-20211225214237970&quot; /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;Set to Read Uncommitted&lt;/p&gt;

&lt;p&gt;Both windows start a transaction with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;begin&lt;/code&gt;. Then transaction 1 queries the row where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name=&apos;zhangsan&apos;&lt;/code&gt; and sees &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt; is 1. Next, transaction 2 updates zhangsan’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt; to 0. Then transaction 1 queries &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name=&apos;zhangsan&apos;&lt;/code&gt; again:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225214836.png&quot; alt=&quot;image-20211225214836782&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In transaction 1’s second query result, zhangsan’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt; is already the value modified by transaction 2. In other words, after transaction 2 modifies the data, transaction 1 can immediately perceive it—even though transaction 2 hasn’t committed yet. This is Read Uncommitted: &lt;strong&gt;reading data that other transactions haven’t committed&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;read-committed&quot;&gt;Read Committed&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225215431.png&quot; alt=&quot;image-20211225215431583&quot; /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;Set to Read Committed&lt;/p&gt;

&lt;p&gt;Both windows &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;begin&lt;/code&gt; at the same time. Transaction 1 queries zhangsan and sees &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt; is 1. Then transaction 2 changes zhangsan’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt; to 0 but does not commit. Transaction 1 queries zhangsan again and still sees &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt; is 1. After that, transaction 2 commits. Finally, transaction 1 queries zhangsan again and finds &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt; has become 0:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225220238.png&quot; alt=&quot;image-20211225220238740&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Overall, isolation is a bit better than &lt;strong&gt;Read Uncommitted&lt;/strong&gt;, but once another transaction commits, it can still break isolation—i.e., you still get non-repeatable reads.&lt;/p&gt;

&lt;h3 id=&quot;repeatable-read&quot;&gt;Repeatable Read&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225220455.png&quot; alt=&quot;image-20211225220455896&quot; /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;Set to Repeatable Read&lt;/p&gt;

&lt;p&gt;Let’s first verify whether &lt;strong&gt;Repeatable Read&lt;/strong&gt; solves the non-repeatable read problem:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225220712.png&quot; alt=&quot;image-20211225220712641&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can see that under RR, the non-repeatable read problem is solved. In database specifications, RR can still cause phantom reads, but in this InnoDB experiment, transaction 1’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select&lt;/code&gt; uses MVCC snapshot reads to read historical data, ensuring isolation.&lt;/p&gt;

&lt;p&gt;Finally, let’s distinguish &lt;strong&gt;non-repeatable reads vs phantom reads&lt;/strong&gt;. On the surface, phantom reads focus on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;insert&lt;/code&gt;, while non-repeatable reads focus on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delete&lt;/code&gt;. Essentially, it’s about &lt;em&gt;what data is locked&lt;/em&gt; at different isolation levels. Under Repeatable Read, after the first time a SQL statement reads data, those rows are locked so other transactions can’t modify them, enabling repeatable reads. But it can’t lock rows that don’t exist yet (i.e., inserts). So when transaction 1 previously read data (or even modified all existing data), transaction 2 can still &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;insert&lt;/code&gt; new rows and commit. Then transaction 1 will suddenly find an extra row that didn’t exist before—this is a phantom read, and it can’t be avoided with row locks alone.&lt;/p&gt;

&lt;h3 id=&quot;serializable&quot;&gt;Serializable&lt;/h3&gt;

&lt;p&gt;I won’t demo Serializable, because it solves everything by serializing execution: reads use read locks, writes use write locks, and read/write locks are mutually exclusive. It effectively avoids phantom reads, non-repeatable reads, dirty reads, etc.—simple and brute-force.&lt;/p&gt;

&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;

&lt;p&gt;At their core, the four transaction isolation levels are about different degrees of isolation caused by different locking mechanisms. This post summarized what isolation levels exist and what problems each level can cause. Memorize the basics first—&lt;strong&gt;in the next post I’ll share how different isolation levels are achieved, and what locking mechanisms are used to solve dirty reads, non-repeatable reads, phantom reads, and so on.&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 25 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/en/middleware/2021/12/25/db_isolation01.html</link>
        <guid isPermaLink="true">https://www.someget.cn/en/middleware/2021/12/25/db_isolation01.html</guid>
        
        
        <category>en</category>
        
        <category>middleware</category>
        
      </item>
    
      <item>
        <title>数据库的事务隔离级别</title>
        <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;

&lt;p&gt;事务隔离级别原来实习找工作的时候是必背八股文了，当时只会背却没深刻理解里面的意思和具体实现，随着工程实践的深入，对数据库使用的积累，对事务隔离级别有不一样的认识。最近又常常被问到，然后特意去复习了一遍丁奇大大的《MySQL实战45讲》，就想着总结一遍。&lt;/p&gt;

&lt;p&gt;大家都知道事务的ACID性质，数据库为了让事务能保证这些性质尤其是隔离性和一致性，一般都会采用加锁的方式来做到。数据库中的锁是为了构建这些隔离级别来存在的。本篇只介绍事务隔离级别，下一篇会分析MySQL中InnoDB引擎中事务隔离级别的实现。&lt;/p&gt;

&lt;h2 id=&quot;数据库隔离级别&quot;&gt;数据库隔离级别&lt;/h2&gt;

&lt;p&gt;先上表格，要说明的是这是教材中数据库隔离级别，不同的数据库实现会有不一样，例如MySQL的innoDB就在可重复读的情况下防止了幻读。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;隔离级别&lt;/th&gt;
      &lt;th&gt;脏读&lt;/th&gt;
      &lt;th&gt;不可重复读&lt;/th&gt;
      &lt;th&gt;幻读&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;读未提交(Read Uncommitted)&lt;/td&gt;
      &lt;td&gt;可能&lt;/td&gt;
      &lt;td&gt;可能&lt;/td&gt;
      &lt;td&gt;可能&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;读已提交(Read Committed)&lt;/td&gt;
      &lt;td&gt;不可能&lt;/td&gt;
      &lt;td&gt;可能&lt;/td&gt;
      &lt;td&gt;可能&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;可重复读(Repeated Read)&lt;/td&gt;
      &lt;td&gt;不可能&lt;/td&gt;
      &lt;td&gt;不可能&lt;/td&gt;
      &lt;td&gt;可能(若MySQL的InnoDB则不可能)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;可串行化(Serializable)&lt;/td&gt;
      &lt;td&gt;不可能&lt;/td&gt;
      &lt;td&gt;不可能&lt;/td&gt;
      &lt;td&gt;不可能&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;读未提交-导致脏读&lt;/strong&gt;：两个事务同时开启，事务a可以读到另外一个事务b的&lt;strong&gt;未提交&lt;/strong&gt;的修改内容，这个其实就是&lt;strong&gt;脏读&lt;/strong&gt;，不用特意去记脏读是什么，读未提交的名字其实就是脏读，因为它破坏了隔离性，两个事务直接应该是互不影响的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;读已提交-导致不可重复读&lt;/strong&gt;：两个事务同时开启，事务a可以读到另外一个事务b的&lt;strong&gt;已提交&lt;/strong&gt;的修改内容，这个其实就是&lt;strong&gt;不可重复读&lt;/strong&gt;，也不用特意去记不可重复读是什么，读到其他事务已提交的内容也没有保证隔离性。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;可重复读-导致幻读&lt;/strong&gt;：两个事务同时开启，在这个隔离级别下事务b&lt;strong&gt;更改记录并且提交&lt;/strong&gt;不会被事务a查询到，也就是说可以&lt;strong&gt;解决重复读问题&lt;/strong&gt;。&lt;strong&gt;但是当事务b对数据进行插入新增数据并且提交，这条新数据会被事务a所查询到，这成为是幻读&lt;/strong&gt;。具体原因是因为加锁机制导致的，但是MySQL的innoDB在可重复读的情况下可以可以保证不被查询到，下一篇会讨论。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;串行化读-串行化加锁不存在任何问题&lt;/strong&gt;：两个事务是串行执行的，每次读都需要获取标记的共享锁，读写相互都会堵塞。&lt;/p&gt;

&lt;p&gt;MySQL的innoDB默认是&lt;strong&gt;REPEATABLE READ&lt;/strong&gt;(下称RR)，阿里云的RDB默认是&lt;strong&gt;Read Committed&lt;/strong&gt;(下称RC)，下面通过具体的sql来看事务隔离级别。&lt;/p&gt;

&lt;h2 id=&quot;演示&quot;&gt;演示&lt;/h2&gt;

&lt;p&gt;数据库连接工具我是用的是DataGrip(下称DG)，在DG开两个会话分别开始begin表示开启两个事务。&lt;/p&gt;

&lt;p&gt;数据库使用的MySQL8.0版本，测试的表DDL如下&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;   &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tinyint&lt;/span&gt;      &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;产生的测试数据如下&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;zhangsan&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;wangwu&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;xiaohong&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;xiaohua&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;查看当前会话隔离级别&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;查看当前会话隔离级别&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MySQL8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction_isolation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;查看当前会话隔离级别&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MySQL5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tx_isolation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;设置当前会话隔离级别&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;设置当前会话的隔离级别&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;isolation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;uncommitted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;isolation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;committed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;isolation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;repeatable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;isolation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;serializable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;读未提交&quot;&gt;读未提交&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225214238.png&quot; alt=&quot;image-20211225214237970&quot; /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;设置成读未提交&lt;/p&gt;

&lt;p&gt;两个窗口的事务都开始&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;begin&lt;/code&gt;，然后事务1先查询&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name=&apos;zhangsan&apos;&lt;/code&gt;的数据，发现zhangsan的gender是1，然后事务2在更新zhagnsan的gender为0，然后事务1再查询&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name=&apos;zhangsan&apos;&lt;/code&gt;的数据&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225214836.png&quot; alt=&quot;image-20211225214836782&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最后的事务1中的第二次查询结果，zhangshan的gender是事务2修改以后的数据，也就是说事务2修改数据后，在事务1中查询可以立马感知到，哪怕是事务2都没有提交这个事务。这就是读未提交，&lt;strong&gt;也就是读到其他事务未提交的数据&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&quot;读已提交&quot;&gt;读已提交&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225215431.png&quot; alt=&quot;image-20211225215431583&quot; /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;设置成读已提交&lt;/p&gt;

&lt;p&gt;两个窗口的事务同时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;begin&lt;/code&gt;，然后事务1先查询zhangsan发现gender是1，然后事务2对zhangsan的gender修改成0后不提交事务，然后事务1再次查询zhangsan的gender还是1，随后事务2对事务进行提交，最后事务1再次查询zhangsan的gender发现已经被修改成0了&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225220238.png&quot; alt=&quot;image-20211225220238740&quot; /&gt;&lt;/p&gt;

&lt;p&gt;总的来说隔离性比&lt;strong&gt;读未提交&lt;/strong&gt;稍好一些，但是其他事务提交事务请求后依然会破坏隔离性，也就是没有达到不可重复读。&lt;/p&gt;

&lt;h3 id=&quot;可重复度&quot;&gt;可重复度&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225220455.png&quot; alt=&quot;image-20211225220455896&quot; /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;设置成可重复度&lt;/p&gt;

&lt;p&gt;我们先来验证这个&lt;strong&gt;可重复度&lt;/strong&gt;是否解决了不可重复读的问题&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211225220712.png&quot; alt=&quot;image-20211225220712641&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到RR的隔离级别下已经解决了不可重复读的问题，在数据库规范中RR隔离级别下其实还会导致幻读，但是实验中的InnoDB中事务1进行select会读取MVCC的快照读进行历史数据的读取，来保证隔离性。&lt;/p&gt;

&lt;p&gt;最后做一个区分，&lt;strong&gt;不可重复读和幻读的区别&lt;/strong&gt;。表面上的区别在于幻读的重点在insert，但不可重复读重点在于update和delete，本质上是隔离级别锁住的数据不同，在可重复读中，执行SQL第一次读取到数据后，就将这些数据加锁，其它事务无法修改这些数据，然后就可以进行重复读了。但是却无法锁住insert的数据，所以当事务1先前读取了数据，或者修改了全部数据，事务2还是可以insert数据提交，这时事务1就会发现莫名其妙多了一条之前没有的数据，这就是幻读，不能通过行锁来避免。&lt;/p&gt;

&lt;h3 id=&quot;串行化&quot;&gt;串行化&lt;/h3&gt;

&lt;p&gt;可串行化(Serializable)就不演示了，因为它通过串行来解决所有问题，读用读锁，写用写锁，读锁和写锁互斥，来有效的避免幻读、不可重复读、脏读等问题，简单粗暴。&lt;/p&gt;

&lt;h2 id=&quot;后言&quot;&gt;后言&lt;/h2&gt;

&lt;p&gt;四种事务隔离级别本质就在于通过锁机制导致的隔离性不一样，这篇总结了有哪些隔离级别，不同隔离级别会导致哪些问题。先背好，&lt;strong&gt;下一篇将分享不同事务隔离级别是怎么做到的，脏读、不可重复读、幻读等问题是使用什么锁机制来解决。&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 25 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/middleware/2021/12/25/db_isolation01.html</link>
        <guid isPermaLink="true">https://www.someget.cn/middleware/2021/12/25/db_isolation01.html</guid>
        
        
        <category>middleware</category>
        
      </item>
    
      <item>
        <title>MyBatis-Plus SQL Performance Analysis Log Output</title>
        <description>&lt;h2 id=&quot;preface&quot;&gt;Preface&lt;/h2&gt;

&lt;p&gt;In the last project I introduced Mybatis-Plus (the name is way too long, so I’ll &lt;strong&gt;call it MP&lt;/strong&gt; from now on). I habitually enable the performance analysis plugin locally so I can print SQL info, but this time I couldn’t find the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PerformanceInterceptor&lt;/code&gt; class. After checking MP’s &lt;a href=&quot;https://github.com/baomidou/mybatis-plus/releases&quot;&gt;release notes&lt;/a&gt;, I found that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PerformanceInterceptor&lt;/code&gt; was actually removed in version &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.2&lt;/code&gt;. And our project is using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.4.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211211161132.png&quot; alt=&quot;image-20211211161132263&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Release notes on MP&apos;s GitHub&lt;/center&gt;

&lt;p&gt;It also mentions using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p6spy&lt;/code&gt; as a replacement. After some digging, I summarized three ways to print SQL logs, and I’m sharing them here.&lt;/p&gt;

&lt;p&gt;The code for this project has been uploaded to GitHub, &lt;a href=&quot;https://github.com/oreoft/project-store/tree/master/mybatis-sql-log&quot;&gt;click here&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;three-approaches&quot;&gt;Three approaches&lt;/h2&gt;

&lt;p&gt;Also! Also! Also! Printing SQL logs has a pretty big impact on performance, so &lt;strong&gt;never enable it in production&lt;/strong&gt;. I usually turn it on when debugging locally—especially for cross-module calls in microservices, it’s insanely useful. You only need to set a breakpoint on the caller side; on the callee side you just print the SQL. If the data isn’t what you expect, check the SQL printed in the console, or paste it into DataGrip and run it.&lt;/p&gt;

&lt;p&gt;Create a table in the DB for testing. Here I’m creating a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t_user&lt;/code&gt; table under the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; database, with the DDL below:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pk&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;用户名&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;123456&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;用户密码&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;char&lt;/span&gt;         &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;0&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;性别&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;phone&lt;/span&gt;       &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;手机&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;datetime&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;创建时间&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;用户表&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Import dependencies in advance 🔗&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;mysql&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mysql-connector-java&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;8.0.19&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.baomidou&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mybatis-plus-boot-starter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Just import any version for now --&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.1.2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then create a BO&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Data&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@TableName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;t_user&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * pk
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@TableId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IdType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;AUTO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * Mock username
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * Mock password, stored in plaintext for demo purposes
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * Mock gender
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * Mock phone number
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;phone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * Created time
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then write a Mapper&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Mapper&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TUserMapper&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, write a &lt;strong&gt;Test runner class&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MybatisSqlLogTest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * Being lazy here—using the mapper to demonstrate how query logs look
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Resource&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TUserMapper&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tUserMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;selectPrintLogTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LambdaQueryWrapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LambdaQueryWrapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getPhone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getGender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getGender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;likeRight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;小&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tUserMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;selectOne&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The queried id is %s, phone is %s\n&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPhone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;enable-mybatis-built-in-log-output-sql--params&quot;&gt;Enable MyBatis built-in log output (SQL + params)&lt;/h3&gt;

&lt;p&gt;This is a built-in MyBatis feature. Enabling it is super simple: just add the following line to your config file. Usually we have multiple config files, so I recommend enabling it only in dev—just add it to your dev config.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It starts with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mybatis-plus&lt;/code&gt; because MP is compatible with MyBatis config. You can also change the top-level key to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mybatis: &lt;/code&gt; and it will still work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, run the test class (the setup demo is above—scroll up and check it)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The downside is that the output isn’t a final executable SQL log; you need to assemble/convert the format yourself. The output looks like this:&lt;/p&gt;

&lt;div class=&quot;language-tex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16132f21] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1271323139 wrapping com.mysql.cj.jdbc.ConnectionImpl@4f59a516] will not be managed by Spring
==&amp;gt;  Preparing: SELECT id,phone,gender FROM t&lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;user WHERE gender = ? AND username LIKE ? 
==&amp;gt; Parameters: 0(Integer), 小&lt;span class=&quot;c&quot;&gt;%(String)&lt;/span&gt;
&amp;lt;==    Columns: id, phone, gender
&amp;lt;==        Row: 1, 13333333333, 0
&amp;lt;==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16132f21]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You need to take the two lines containing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&amp;gt;&lt;/code&gt; and convert them using some plugin or third-party tool. For example, I use the mybatisLog tool inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uTools&lt;/code&gt;, and the converted result looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211211165040.png&quot; alt=&quot;image-20211211165040675&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Third-party tool&lt;/center&gt;

&lt;p&gt;This is “native”, but kind of annoying and not very intuitive. If the SQL is long and a single request session has lots of statements, it’s basically unreadable—might as well not look at it.&lt;/p&gt;

&lt;h3 id=&quot;use-the-plugin-in-mp-versions-below-32&quot;&gt;Use the plugin in MP versions below 3.2&lt;/h3&gt;

&lt;p&gt;Use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PerformanceInterceptor&lt;/code&gt; plugin provided by MP before 3.2. This is the approach I used the most before, and it’s the most convenient. The configuration is also very straightforward, and it’s easy to separate by environment.&lt;/p&gt;

&lt;p&gt;Just like other MP plugins (e.g., pagination), you create a bean, set it up, and put it into the IoC container. When MP runs, it will use your bean to apply the plugin behavior.&lt;/p&gt;

&lt;p&gt;First, make sure your MP version is below 3.2:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.baomidou&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mybatis-plus-boot-starter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Make sure the version is below 3.2 --&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.1.2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then create a config class and inject a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PerformanceInterceptor&lt;/code&gt; bean:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MybatisPlusSqlLogConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;


    &lt;span class=&quot;cm&quot;&gt;/**
     * Print SQL, performance analysis interceptor
     * Since every SQL needs to be intercepted, there will be performance overhead
     * So it&apos;s recommended to enable it only in dev or test for debugging
     * If multiple environments -&amp;gt; @Profile({&quot;dev&quot;, &quot;test&quot;})
     * @return PerformanceInterceptor bean
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Profile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mp&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PerformanceInterceptor&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;performanceInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;PerformanceInterceptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;performanceInterceptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PerformanceInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Enable formatting, similar to DataGrip&apos;s SQL beautification, looks nicer in console&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;performanceInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setFormat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;performanceInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One thing worth noting: you can add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Profile(&quot;dev&quot;)&lt;/code&gt; after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Bean&lt;/code&gt;. As long as your dev config file is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application-dev.yml&lt;/code&gt;, this bean will only be injected in the dev environment. So you don’t have to worry about performance impact in production. In my code I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mp&lt;/code&gt; as the profile for the plugin demo, so here it only takes effect under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, run the test class (the setup demo is above—scroll up and check it)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result looks like this—very intuitive, and it’s also in red text:&lt;/p&gt;

&lt;div class=&quot;language-tex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; Time：22 ms - ID：cn.someget.mybatis.sqllog.mapper.TUserMapper.selectOne
Execute SQL：
    SELECT
        id,
        phone,
        gender 
    FROM
        t&lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;user 
    WHERE
        gender = 0 
        AND username LIKE &apos;小&lt;span class=&quot;c&quot;&gt;%&apos;&lt;/span&gt;

查询到的id是1, 电话是13333333333
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211211171904.png&quot; alt=&quot;image-20211211171904411&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Eye-catching red text&lt;/center&gt;

&lt;p&gt;It used to be super handy. Maybe the author thought the performance impact was too big, and to prevent misuse, they removed it in version 3.2….&lt;/p&gt;

&lt;h3 id=&quot;use-p6spy-in-mp-32-and-above&quot;&gt;Use p6spy in MP 3.2 and above&lt;/h3&gt;

&lt;p&gt;So now I know for sure this isn’t suitable for production, but I still want something convenient for local debugging. Besides downgrading MP, what else can I do? The author also recommends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p6spy&lt;/code&gt; in the release notes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P6Spy&lt;/em&gt; is an open-source framework that can intercept and modify data operation statements in your application. Its GitHub repo is &lt;a href=&quot;https://github.com/p6spy/p6spy&quot;&gt;here&lt;/a&gt;. It’s very powerful; here I’ll only cover the parts that meet the needs of SQL performance analysis and log output.&lt;/p&gt;

&lt;p&gt;First, add the dependency. I’ll just pick a random version here—see all versions &lt;a href=&quot;https://mvnrepository.com/artifact/p6spy/p6spy&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;p6spy&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;p6spy&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.8.2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then in your config file, when configuring the datasource connection, change the driver class name and the URL:&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Configure MySQL connection info&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;datasource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   driver-class-name: com.mysql.cj.jdbc.Driver  this was the original&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;driver-class-name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;com.p6spy.engine.spy.P6SpyDriver&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   url: jdbc:mysql://${mysql.host}  this was the original URL prefix&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jdbc:p6spy:mysql://${mysql.host}:3306/test?useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;autoReconnect=true&amp;amp;failOverReadOnly=false&amp;amp;useSSL=false&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;root&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;123456&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spy.properties&lt;/code&gt; file under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resources&lt;/code&gt; and write some basic config:&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Output logs to console&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Formatting (without it, it&apos;s a long unreadable line; this is a built-in formatter.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# You can also implement your own based on the source code and configure it here)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Remove JDBC URL prefix&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;useprefix=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;p.s. Note that p6spy only requires adding the dependency and configuring it—no code needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, run the test class (the setup demo is above—scroll up and check it)&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-tex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; Consume Time：10 ms 1639215721188
 Execute SQL：SELECT id,phone,gender FROM t&lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;user WHERE gender = 0 AND username LIKE &apos;小&lt;span class=&quot;c&quot;&gt;%&apos;&lt;/span&gt;

查询到的id是1, 电话是13333333333
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211211174242.png&quot; alt=&quot;image-20211211174242855&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It looks pretty straightforward. But after skimming the source code, it seems there isn’t a built-in formatting config class. If you need formatting, you can implement your own by extending &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MessageFormattingStrategy&lt;/code&gt; and writing it similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P6SpyLogger&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;

&lt;p&gt;For foundational components, when creating a project you usually pick a stable version to start with. After it goes to production and stabilizes, you typically lock the versions. I only noticed recently—when introducing MP into a new project—that the performance analysis plugin had been removed, so I’m writing this down as a reminder for anyone who also likes to enable SQL log output locally.&lt;/p&gt;

&lt;p&gt;And yeah, using this for local debugging is genuinely awesome—just don’t turn it on in production.&lt;/p&gt;
</description>
        <pubDate>Sat, 11 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/en/java/2021/12/11/mybatis_log01.html</link>
        <guid isPermaLink="true">https://www.someget.cn/en/java/2021/12/11/mybatis_log01.html</guid>
        
        
        <category>en</category>
        
        <category>java</category>
        
      </item>
    
      <item>
        <title>Mybatis-Plus的Sql性能分析日志打印</title>
        <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;

&lt;p&gt;上次项目中引入Mybatis-Plus(名字时间太长了，后面&lt;strong&gt;简称MP&lt;/strong&gt;)，我习惯性在本地开启性能分析插件方便打印sql的信息，但是这一次居然找不到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PerformanceInterceptor&lt;/code&gt;这个类，后面查看MP的&lt;a href=&quot;https://github.com/baomidou/mybatis-plus/releases&quot;&gt;更新日志&lt;/a&gt;发现，居然在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.2&lt;/code&gt;版本移除了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PerformanceInterceptor&lt;/code&gt;，我们项目中引入的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.4.0&lt;/code&gt;版本。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211211161132.png&quot; alt=&quot;image-20211211161132263&quot; /&gt;&lt;/p&gt;

&lt;center&gt;MP的GIthub上release日志&lt;/center&gt;

&lt;p&gt;其中提到建议使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p6spy&lt;/code&gt;替代，经过我一番研究我总结了三个打印sql日志的方法，特意来分享一下。&lt;/p&gt;

&lt;p&gt;本项目代码已经上传至Github，&lt;a href=&quot;https://github.com/oreoft/project-store/tree/master/mybatis-sql-log&quot;&gt;点击这里&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;三种方法&quot;&gt;三种方法&lt;/h2&gt;

&lt;p&gt;另外！另外！另外！打印Sql日志对执行的性能影响还算挺大的，&lt;strong&gt;生产环境上可千万不要启用&lt;/strong&gt;，我一般在本地Debug的时候开启，尤其是微服务的跨模块调用真的是太太太好用了，只需要调用方打断点，被调用方的话只需要把这个Sql打出来。如果数据不符合预期，则看一下控制台打印的Sql或者把Sql放到DataGrip里面跑一下看看。&lt;/p&gt;

&lt;p&gt;在db里面创建一张方便测试的表，我这里是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt;的数据库下创建一张&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t_user&lt;/code&gt;表附上DDL&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t_user&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;          &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auto_increment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pk&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;用户名&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;123456&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;用户密码&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;char&lt;/span&gt;         &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;0&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;性别&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;phone&lt;/span&gt;       &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;手机&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;datetime&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;创建时间&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;用户表&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;提前导入🔗&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;mysql&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mysql-connector-java&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;8.0.19&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.baomidou&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mybatis-plus-boot-starter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 先随便导入一个版本 --&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.1.2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后创建一个BO&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Data&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@TableName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;t_user&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * pk
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@TableId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IdType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;AUTO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 模拟用户名字
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 模拟用户密码, 方便演示 明文存储
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 模拟用户性别
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 模拟用户手机
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;phone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 创建时间
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后写一个Mapper&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Mapper&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TUserMapper&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后写一个&lt;strong&gt;Test执行类&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MybatisSqlLogTest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 偷懒, 用mapper来演示查询日志展示效果
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Resource&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TUserMapper&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tUserMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;selectPrintLogTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LambdaQueryWrapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LambdaQueryWrapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getPhone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getGender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getGender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;likeRight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;TUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;小&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;TUser&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tUserMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;selectOne&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;查询到的id是%s, 电话是%s\n&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPhone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;开启mybatis的日志拼接功能&quot;&gt;开启Mybatis的日志拼接功能&lt;/h3&gt;

&lt;p&gt;这个是mybatis就自带的功能，开启方法也非常简单，只需要在你的配置文件里面把下面这行加上即可，一般我们有多个配置文件，建议只在dev开启，那么这一行只需要加到dev的配置文件里面就好啦。&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mybatis-plus&lt;/code&gt;开头的原因是因为MP也兼容mybatis的配置，顶层的key换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mybatis: &lt;/code&gt;也可以生效。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;最后测试类来执行一下(上面的准备工作有demo，翻上去看一下)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这个缺点就是，输出的日志并非执行日志，日志格式需要自己拼接。输出的结果如下&lt;/p&gt;

&lt;div class=&quot;language-tex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16132f21] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1271323139 wrapping com.mysql.cj.jdbc.ConnectionImpl@4f59a516] will not be managed by Spring
==&amp;gt;  Preparing: SELECT id,phone,gender FROM t&lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;user WHERE gender = ? AND username LIKE ? 
==&amp;gt; Parameters: 0(Integer), 小&lt;span class=&quot;c&quot;&gt;%(String)&lt;/span&gt;
&amp;lt;==    Columns: id, phone, gender
&amp;lt;==        Row: 1, 13333333333, 0
&amp;lt;==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16132f21]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;你需要把含有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&amp;gt;&lt;/code&gt;的两行放到一些插件或者三方工具里面进行转换，例如我是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uTools&lt;/code&gt;里面的mybatisLog工具转换结果如下&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211211165040.png&quot; alt=&quot;image-20211211165040675&quot; /&gt;&lt;/p&gt;

&lt;center&gt;三方工具&lt;/center&gt;

&lt;p&gt;这种虽然是原生，但是略显麻烦，不是很直观，如果是sql特别长，一次请求会话特别多，看了和没看一个样。&lt;/p&gt;

&lt;h3 id=&quot;mp-32以下版本使用插件&quot;&gt;MP 3.2以下版本使用插件&lt;/h3&gt;

&lt;p&gt;使用MP在3.2以下版本提供的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PerformanceInterceptor&lt;/code&gt;插件，这一种方式是我原来用的最多，也用的最顺手的，配置起来也非常方便，也可以很方便的做环境区分。&lt;/p&gt;

&lt;p&gt;使用方法和MP其他插件例如分页插件一样，只需要创建一个bean，设置好输入然后放入ioc中即可，MP运行的时候就会使用你的bean完成相应的插件功能。&lt;/p&gt;

&lt;p&gt;首先确认好你的MP版本是在3.2一下&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.baomidou&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mybatis-plus-boot-starter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 确定版本号是3.2以下 --&amp;gt;&lt;/span&gt;
  	&lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.1.2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后创建一个配置类， 然后在里面注入一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PerformanceInterceptor&lt;/code&gt;的bean&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MybatisPlusSqlLogConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;


    &lt;span class=&quot;cm&quot;&gt;/**
     * 打印 sql，性能分析拦截器
     * 因为每次sql都需要做拦截, 会有性能损耗
     * 所以建议只在dev或者test环境下方便debug的时候查看
     * 如果多个环境 -&amp;gt; @Profile({&quot;dev&quot;, &quot;test&quot;})
     * @return PerformanceInterceptor bean
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Profile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mp&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PerformanceInterceptor&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;performanceInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;PerformanceInterceptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;performanceInterceptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PerformanceInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 设置格式化, 类似于datagrip的美化sql语句, 在控制台好看一些&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;performanceInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setFormat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;performanceInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;值得注意的是在@Bean后面可以再打一个注解&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Profile(&quot;dev&quot;)&lt;/code&gt;，只要你的dev配置文件是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application-dev.yml&lt;/code&gt;，那么这个bean只会在dev环境里面注入。在生产上你也不用担心这个会影响性能啦，因为我代码中是mp-表示的插件demo，所以我这里只在mp下生效。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;最后测试类来执行一下(上面的准备工作有demo，翻上去看一下)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;然后结果如下，这样的效果就非常的直观，并且还是红色字体&lt;/p&gt;

&lt;div class=&quot;language-tex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; Time：22 ms - ID：cn.someget.mybatis.sqllog.mapper.TUserMapper.selectOne
Execute SQL：
    SELECT
        id,
        phone,
        gender 
    FROM
        t&lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;user 
    WHERE
        gender = 0 
        AND username LIKE &apos;小&lt;span class=&quot;c&quot;&gt;%&apos;&lt;/span&gt;

查询到的id是1, 电话是13333333333
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211211171904.png&quot; alt=&quot;image-20211211171904411&quot; /&gt;&lt;/p&gt;

&lt;center&gt;红色醒目字体&lt;/center&gt;

&lt;p&gt;本来是非常好用的，可能是作者觉得这个对性能影响非常大，为了防止这个插件大家误用就在3.2版本中下架了….&lt;/p&gt;

&lt;h3 id=&quot;mp-32及以上版本使用p6spy&quot;&gt;MP 3.2及以上版本使用p6spy&lt;/h3&gt;

&lt;p&gt;那我已经明确知道这个是不适合上生产，但是我现在本地方便debug看，除了降低MP版本，那还有什么解决办法呢。作者也在更新日志中提到推荐使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p6spy&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P6Spy&lt;/em&gt;是一个可以用来在应用程序中拦截和修改数据操作语句的开源框架，GitHub地址请&lt;a href=&quot;https://github.com/p6spy/p6spy&quot;&gt;点击这里&lt;/a&gt;。它的功能很强大，这里只介绍它满足sql性能分析和日志输出的需求。&lt;/p&gt;

&lt;p&gt;首先先导入他的依赖，我这里随便导入一个版本，所有版本请&lt;a href=&quot;https://mvnrepository.com/artifact/p6spy/p6spy&quot;&gt;点击这里&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;p6spy&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;p6spy&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.8.2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后再配置文件里面配置datasource连接的时候，更换驱动类名以及url&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 配置一下mysql的连接信息&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;datasource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   driver-class-name: com.mysql.cj.jdbc.Driver 这是原来的&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;driver-class-name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;com.p6spy.engine.spy.P6SpyDriver&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   url: jdbc:mysql://${mysql.host} 这是原来的url头部&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jdbc:p6spy:mysql://${mysql.host}:3306/test?useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;autoReconnect=true&amp;amp;failOverReadOnly=false&amp;amp;useSSL=false&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;root&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;123456&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resources&lt;/code&gt;创建一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spy.properties&lt;/code&gt;的文件里面写入一些基本配置&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#日志输出到控制台&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 格式化(不设置的话一大串阅读性不强, 这个是内置的格式器可以按照源码自己写一个然后配置)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 取消JDBC的url前缀&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;useprefix=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;p.s. 注意p6spy只需要导包和在配置文件中配置即可，不需要编写代码&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;最后测试类来执行一下(上面的准备工作有demo，翻上去看一下)&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-tex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; Consume Time：10 ms 1639215721188
 Execute SQL：SELECT id,phone,gender FROM t&lt;span class=&quot;p&quot;&gt;_&lt;/span&gt;user WHERE gender = 0 AND username LIKE &apos;小&lt;span class=&quot;c&quot;&gt;%&apos;&lt;/span&gt;

查询到的id是1, 电话是13333333333
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211211174242.png&quot; alt=&quot;image-20211211174242855&quot; /&gt;&lt;/p&gt;

&lt;p&gt;看起来也很直观，但是我翻了一下源码，好像没有自带格式化的配置类，如果大家有格式化需求，可以继承&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MessageFormattingStrategy&lt;/code&gt;自己照着&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P6SpyLogger&lt;/code&gt;写一个。&lt;/p&gt;

&lt;h2 id=&quot;后言&quot;&gt;后言&lt;/h2&gt;

&lt;p&gt;一般基础组件，创建项目的时候就会选择一个稳定版开始搭建。后期上生产稳定以后一般都是锁版本的，我也是最近新项目引入MP才发现居然性能分析的插件已经被移除了，特意记录一下，给也习惯在本地开启sql日志输出的人提个醒。&lt;/p&gt;

&lt;p&gt;最后这个本地debug看一下是真的爽，不能在生产环境打开哈。&lt;/p&gt;
</description>
        <pubDate>Sat, 11 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/java/2021/12/11/mybatis_log01.html</link>
        <guid isPermaLink="true">https://www.someget.cn/java/2021/12/11/mybatis_log01.html</guid>
        
        
        <category>java</category>
        
      </item>
    
      <item>
        <title>A Simple Vim Makeover</title>
        <description>&lt;h2 id=&quot;preface&quot;&gt;Preface&lt;/h2&gt;

&lt;p&gt;A coworker picked up a Magic Keyboard Gen1 on Xianyu. After just a few days, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;escape&lt;/code&gt; key broke, and he’d already confirmed receipt… He didn’t want to waste the money and still wanted to keep using it, so he listed out the scenarios where he needed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;escape&lt;/code&gt; and found the most frequent one was switching modes in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim&lt;/code&gt;. With that as a trigger, I’m sharing a few simple tweaks I use for vim on macOS—super handy, and you can basically ditch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;escape&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Quick intro to vim: it’s a text editor—honestly kind of like the TextEdit app on macOS—but its interaction philosophy is “do everything with the keyboard.” I’m not here to hype vim though. As a Java dev, is there really anything better than IntelliJ IDEA? Let alone a text editor 🤡 (runs away~). That said, I &lt;em&gt;have&lt;/em&gt; seen real vim masters. At my previous company we had a PHP team, and some seniors wrote code directly on servers most of the time and barely used an IDE—hands on the keyboard like playing the piano, tap tap tap~.&lt;/p&gt;

&lt;p&gt;Why do I call it a &lt;strong&gt;simple tweak&lt;/strong&gt;? Because I used to be obsessed with vim too. I thought writing code in vim looked insanely cool, and I did all the tinkering—highlighting, hints, search, jumps, all the flashy stuff. But when it comes to writing JVM code, I still need IntelliJ. The learning/onboarding cost goes up, and the payoff is tiny. Still, even if you don’t code on servers, you’ll definitely run into cases where you need to edit config files, so I’d recommend &lt;strong&gt;doing a small quality-of-life setup first&lt;/strong&gt;. Later, if you actually need server-side coding, you can &lt;strong&gt;add plugins and custom key mappings&lt;/strong&gt; then.&lt;/p&gt;

&lt;h2 id=&quot;configuration-and-plugins&quot;&gt;Configuration and Plugins&lt;/h2&gt;

&lt;p&gt;Quick note: macOS ships with vim. Some folks online recommend replacing the system vim with &lt;a href=&quot;https://github.com/macvim-dev/macvim&quot;&gt;MacVim&lt;/a&gt;. I don’t recommend it—partly because I’m lazy, and partly because MacVim’s biggest feature (besides internal vim improvements) is that it comes with a GUI. But vim is all about ditching the mouse, so that feels a bit against the philosophy. And it’s 2021 and MacVim’s shell support is still pretty lacking. If, like me, you only need a simple CLI setup, &lt;strong&gt;I’d say don’t switch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Also, starting from Mojave (10.14.x), the built-in vim on macOS is already 8.x. Whatever you need is basically covered by the stock vim.&lt;/p&gt;

&lt;h3 id=&quot;how-to-modify-the-config&quot;&gt;How to modify the config&lt;/h3&gt;

&lt;p&gt;The default config file for the built-in vim on macOS is at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/share/vim/vimrc&lt;/code&gt;. This is a system-level (global) vimrc file. To keep vim running normally, you generally don’t edit this file. Instead, you create a new user-level vimrc file under your home directory. In Terminal, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touch ~/.vimrc&lt;/code&gt; to create a config file manually (don’t worry—vim will automatically load it). Note that I’m creating a hidden file here, while the global config file is a normal non-hidden file. Hidden vs non-hidden is up to personal preference (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.xxx&lt;/code&gt; is hidden, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xxx&lt;/code&gt; is not).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205115527.png&quot; alt=&quot;image-20211205115527667&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Make sure there is a .vimrc file under your home directory&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;After that, if you want to tweak built-in vim settings or add custom key mappings, just put them in here. No restart needed—changes take effect immediately.&lt;/p&gt;

&lt;h3 id=&quot;how-to-install-plugins&quot;&gt;How to install plugins&lt;/h3&gt;

&lt;p&gt;For installing vim plugins, you &lt;em&gt;can&lt;/em&gt; follow the official &lt;a href=&quot;https://github.com/vim/vim/blob/03c3bd9fd094c1aede2e8fe3ad8fd25b9f033053/runtime/doc/repeat.txt#L515&quot;&gt;vim guide&lt;/a&gt; and install manually. But I strongly recommend using a package manager—it makes updates much easier later. Here I recommend &lt;a href=&quot;https://github.com/junegunn/vim-plug&quot;&gt;vim-plug&lt;/a&gt;. You might see people recommend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vundle&lt;/code&gt;, but it’s no longer maintained. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim-plug&lt;/code&gt; not only promises ongoing maintenance, it also has very solid documentation.&lt;/p&gt;

&lt;p&gt;Installation is super simple. Their &lt;a href=&quot;https://github.com/junegunn/vim-plug&quot;&gt;github&lt;/a&gt; has guides for different platforms. For Unix (macOS and Linux), just run this in your shell:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-fLo&lt;/span&gt; ~/.vim/autoload/plug.vim &lt;span class=&quot;nt&quot;&gt;--create-dirs&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it. If the progress bar hangs forever, you can check my post on &lt;a href=&quot;https://www.someget.cn/other/2020/12/05/mac_proxy.html&quot;&gt;Terminal proxy&lt;/a&gt;, or download the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vim&lt;/code&gt; script from their site and manually place it under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vim/autoload/&lt;/code&gt; (create the directory if it doesn’t exist).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205120206.png&quot; alt=&quot;image-20211205120206489&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Successful installation screen (this curl is basically just downloading the script to a specific local path)&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;After installing, you’ll need a &lt;strong&gt;plugin marketplace&lt;/strong&gt; to find the package name—kind of like Maven coordinates. vim-plug is like Maven managing your dependencies, and Maven Central is like the plugin marketplace. Unless you can memorize all coordinates, you go to the central repo, copy the coordinates, and put them into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pom.xml&lt;/code&gt;. For vim plugins, you go to the marketplace, find the corresponding command, put it into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt;, and then run the install command (kind of like…).&lt;/p&gt;

&lt;p&gt;The marketplace I use is &lt;a href=&quot;https://vimawesome.com/&quot;&gt;vimawesome&lt;/a&gt;. It has a huge collection, and it supports not only vim-plug but also other package managers.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205121905.png&quot; alt=&quot;image-20211205121905833&quot; /&gt;&lt;/p&gt;

&lt;center&gt;The command provided for a plugin on vimawesome&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Once you have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim-plug&lt;/code&gt; and have bookmarked &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vimawesome&lt;/code&gt;, just follow the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim-plug&lt;/code&gt; docs to install plugins. I’ll also share a plugin below and show you how to install it.&lt;/p&gt;

&lt;h2 id=&quot;recommended-config-tweaks&quot;&gt;Recommended config tweaks&lt;/h2&gt;

&lt;h3 id=&quot;enable-syntax-highlighting&quot;&gt;Enable syntax highlighting&lt;/h3&gt;

&lt;p&gt;By default, vim on macOS doesn’t have syntax highlighting. I might not understand the code, but the code &lt;em&gt;must&lt;/em&gt; be highlighted. I randomly edited a startup file with some default and custom configs to show the difference between highlighted vs not.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205154829.gif&quot; alt=&quot;2021-12-05 15.47.41&quot; /&gt;&lt;/p&gt;

&lt;center&gt;No highlighting&lt;/center&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205155059.gif&quot; alt=&quot;2021-12-05 15.50.31&quot; /&gt;&lt;/p&gt;

&lt;center&gt;With highlighting&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;I don’t think I need to say much about this comparison. Every time I see the Android folks in my team with a screen full of colorful code, I’m like “wow so pro”—more colors must mean better skills, right? 🤡&lt;/p&gt;

&lt;p&gt;Enabling highlighting is super easy: in your shell, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim ~/.vimrc&lt;/code&gt;, then add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;syntax on&lt;/code&gt;, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:wq&lt;/code&gt; to save. After that, open vim again and check your code—highlighting should be there.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205160300.png&quot; alt=&quot;image-20211205160300407&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Just add one line&lt;/center&gt;

&lt;h3 id=&quot;highlight-the-current-line&quot;&gt;Highlight the current line&lt;/h3&gt;

&lt;p&gt;In your shell, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim ~/.vimrc&lt;/code&gt;, then add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set cursorline&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205161017.png&quot; alt=&quot;image-20211205161016943&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Split config by lines&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Then you’ll get the effect below: the line where your cursor is will be underlined/highlighted. This is super useful when editing configs—especially when you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt; to search and your eyes have to scan around to figure out where you landed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205160949.png&quot; alt=&quot;image-20211205160949758&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Cursor positioning effect&lt;/center&gt;

&lt;h3 id=&quot;keep-indentation-consistent&quot;&gt;Keep indentation consistent&lt;/h3&gt;

&lt;p&gt;Most of the time I use vim to edit server configs. Sometimes I add comments before modifying things. If I indent the first line, when I go to the next line it won’t keep the indentation automatically—I have to type it again. Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set autoindent&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt; and the next line will automatically keep the same indentation as the previous line.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205162222.png&quot; alt=&quot;image-20211205162222121&quot; /&gt;&lt;/p&gt;

&lt;center&gt;Remember: one config per line&lt;/center&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205164049.gif&quot; alt=&quot;2021-12-05 16.37.48&quot; /&gt;&lt;/p&gt;

&lt;center&gt;The effect: new lines keep the previous line’s indentation&lt;/center&gt;

&lt;h3 id=&quot;replace-the-escape-key&quot;&gt;Replace the escape key&lt;/h3&gt;

&lt;p&gt;The most common use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;escape&lt;/code&gt; is switching from insert mode back to normal mode. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;escape&lt;/code&gt; is far away, and the most comfortable “core” keys are already taken. My solution is to use a combo key: quickly type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;j+k&lt;/code&gt; to act as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;escape&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The change is simple: add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imap jk &amp;lt;Esc&amp;gt;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt;. You can replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jk&lt;/code&gt; with any other keys you like—I’m just used to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Final effect: when typing, use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jk&lt;/code&gt; combo to quickly switch back to normal mode.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205164923.gif&quot; alt=&quot;2021-12-05 16.48.34&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;replace-the--key&quot;&gt;Replace the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:&lt;/code&gt; key&lt;/h3&gt;

&lt;p&gt;To type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:&lt;/code&gt;, you have to press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shift+;&lt;/code&gt;. As a lazy person, I really don’t want to press two keys. But entering command-line mode from normal mode is something you do constantly when interacting with vim. After thinking about it, I realized the space key is basically wasted in normal mode, because editing in normal mode relies on letter keys and doesn’t use space. So I mapped space to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:&lt;/code&gt; to enter command mode. It’s also super simple: add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nmap &amp;lt;space&amp;gt; :&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Effect:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205165846.gif&quot; alt=&quot;2021-12-05 16.48.34&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;others&quot;&gt;Others&lt;/h3&gt;

&lt;p&gt;You can totally set things based on your own habits. For key mappings, you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xmap&lt;/code&gt;. Here &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; is a dynamic value—different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; means different modes/behaviors. I made a table for you; feel free to get creative and customize your mappings.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;x&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;nore&lt;/td&gt;
      &lt;td&gt;Non-recursive&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;n&lt;/td&gt;
      &lt;td&gt;Effective in normal mode&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;v&lt;/td&gt;
      &lt;td&gt;Effective in visual mode&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;i&lt;/td&gt;
      &lt;td&gt;Effective in insert mode&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;c&lt;/td&gt;
      &lt;td&gt;Effective in command-line mode&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;un&lt;/td&gt;
      &lt;td&gt;Followed by a key combo; removes the mapping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;clear&lt;/td&gt;
      &lt;td&gt;Clears all mappings in the corresponding mode&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;For other configs, here’s a summary as well:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 自动语法高亮&lt;/span&gt;
syntax on 
&lt;span class=&quot;c&quot;&gt;# 开启插件&lt;/span&gt;
filetype plugin indent on 
&lt;span class=&quot;c&quot;&gt;# 关闭 vi 兼容模式&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;nocompatible
&lt;span class=&quot;c&quot;&gt;# 显示行号&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;number 
&lt;span class=&quot;c&quot;&gt;# 突出显示当前行&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;cursorline  
&lt;span class=&quot;c&quot;&gt;# 打开状态栏标尺&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;ruler  
&lt;span class=&quot;c&quot;&gt;# 设定 &amp;lt;&amp;lt; 和 &amp;gt;&amp;gt; 命令移动时的宽度为 4&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;shiftwidth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 
&lt;span class=&quot;c&quot;&gt;# 使得按退格键时可以一次删掉 4 个空格&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;softtabstop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 
&lt;span class=&quot;c&quot;&gt;# 设定 tab 长度为 4&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tabstop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 
&lt;span class=&quot;c&quot;&gt;# 覆盖文件时不备份&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;nobackup 
&lt;span class=&quot;c&quot;&gt;# 自动切换当前目录为当前文件所在的目录&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;autochdir 
&lt;span class=&quot;c&quot;&gt;# 设置备份时的行为为覆盖&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;backupcopy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;yes&lt;/span&gt; 
&lt;span class=&quot;c&quot;&gt;# 搜索时忽略大小写，但在有一个或以上大写字母时仍保持对大小写敏感&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;ignorecase smartcase 
&lt;span class=&quot;c&quot;&gt;# 禁止在搜索到文件两端时重新搜索&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;nowrapscan 
&lt;span class=&quot;c&quot;&gt;# 输入搜索内容时就显示搜索结果&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;incsearch 
&lt;span class=&quot;c&quot;&gt;# 搜索时高亮显示被找到的文本&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;hlsearch 
&lt;span class=&quot;c&quot;&gt;# 关闭错误信息响铃&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;noerrorbells 
&lt;span class=&quot;c&quot;&gt;# 关闭使用可视响铃代替呼叫&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;novisualbell 
&lt;span class=&quot;c&quot;&gt;# 置空错误铃声的终端代码&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;t_vb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
&lt;span class=&quot;c&quot;&gt;# 插入括号时，短暂地跳转到匹配的对应括号&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;showmatch 
&lt;span class=&quot;c&quot;&gt;# 短暂跳转到匹配括号的时间&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;matchtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 
&lt;span class=&quot;c&quot;&gt;# 设置魔术&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;magic 
&lt;span class=&quot;c&quot;&gt;# 允许在有未保存的修改时切换缓冲区，此时的修改由 vim 负责保存&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;hidden 
&lt;span class=&quot;c&quot;&gt;# 隐藏工具栏&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;guioptions-&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;T 
&lt;span class=&quot;c&quot;&gt;# 隐藏菜单栏&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;guioptions-&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;m 
&lt;span class=&quot;c&quot;&gt;# 开启新行时使用智能自动缩进&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;smartindent 
&lt;span class=&quot;c&quot;&gt;# 不设定在插入状态无法用退格键和 Delete 键删除回车符&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;backspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;indent,eol,start
&lt;span class=&quot;c&quot;&gt;# 设定命令行的行数为 1&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cmdheight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 
&lt;span class=&quot;c&quot;&gt;# 显示状态栏 (默认值为 1, 无法显示状态栏)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;laststatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 
&lt;span class=&quot;c&quot;&gt;# 设置在状态行显示的信息&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;statusline&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%&amp;lt;%F[%1&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%M%&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%n%R%H]%&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%y&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%0&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;%&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&amp;amp;fileformat&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&amp;amp;encoding&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%c:%l/%L%&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 开始折叠&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;foldenable 
&lt;span class=&quot;c&quot;&gt;# 设置语法折叠&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;foldmethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;syntax 
&lt;span class=&quot;c&quot;&gt;# 设置折叠区域的宽度&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;foldcolumn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 
&lt;span class=&quot;c&quot;&gt;# 设置折叠层数为1&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set local &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;foldlevel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;recommended-plugins&quot;&gt;Recommended plugins&lt;/h2&gt;

&lt;p&gt;I’m only recommending one plugin here—mainly to demonstrate how to install plugins with vim-plug. You can go treasure-hunting on &lt;a href=&quot;https://vimawesome.com/&quot;&gt;vimawesome&lt;/a&gt; yourself. After you find something, remember to share it in the comments.&lt;/p&gt;

&lt;p&gt;The plugin I recommend is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;The NERD tree&lt;/code&gt;. It shows a basic file explorer tree like an IDE does, for example in VS Code:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205174946.png&quot; alt=&quot;image-20211205174946690&quot; /&gt;&lt;/p&gt;

&lt;center&gt;VS Code directory tree&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;After using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;The NERD tree&lt;/code&gt;, in vim it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175029.png&quot; alt=&quot;image-20211205175029546&quot; /&gt;&lt;/p&gt;

&lt;center&gt;The NERD tree UI&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;heres-how-to-install-it&quot;&gt;Here’s how to install it&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Go to the plugin marketplace vimawesome and find &lt;a href=&quot;https://vimawesome.com/plugin/nerdtree-red&quot;&gt;The NERD tree&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Copy this one-line command&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175144.png&quot; alt=&quot;image-20211205175144192&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Then open your shell and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim ~/.vimrc&lt;/code&gt;. Yes!!! This config file again. Then add:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;call plug#begin&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;~/.vim/autoload&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
call plug#end&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Then paste the command you found on vimawesome between those two lines, like this:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;call plug#begin&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;~/.vim/autoload&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Plug &lt;span class=&quot;s1&quot;&gt;&apos;scrooloose/nerdtree&apos;&lt;/span&gt;
call plug#end&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175551.png&quot; alt=&quot;image-20211205175551695&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Then switch to command mode and enter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PlugInstall&lt;/code&gt; (note: you must have vim-plug installed, otherwise it’ll say the command can’t be found)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175831.png&quot; alt=&quot;image-20211205175831177&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175847.png&quot; alt=&quot;image-20211205175847764&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Then in vim you can press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ctrl+t&lt;/code&gt; to view the directory tree for the current file. The plugin’s documentation on vimawesome is way more detailed than what I’m writing here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;At this point, the plugin is installed. If you need other plugins, the process is the same: copy the command you find on vimawesome into the two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call&lt;/code&gt; lines, then run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PlugInstall&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;END&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;recommended-resources&quot;&gt;Recommended resources&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.lanqiao.cn/courses/2840&quot;&gt;Lanqiao Cloud Course: basic vim&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://vim-adventures.com/&quot;&gt;Vim adventure mini-game&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/junegunn/vim-plug&quot;&gt;vim-plug&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/vim/vim/blob/03c3bd9fd094c1aede2e8fe3ad8fd25b9f033053/runtime/doc/repeat.txt#L515&quot;&gt;Vim official manual plugin guide&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://vimawesome.com/&quot;&gt;Recommended plugin repository - vimawesome&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2018/09/vimrc.html&quot;&gt;Ruanyifeng’s intro to vim configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sun, 05 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/en/other/2021/12/05/vim_coustm01.html</link>
        <guid isPermaLink="true">https://www.someget.cn/en/other/2021/12/05/vim_coustm01.html</guid>
        
        
        <category>en</category>
        
        <category>other</category>
        
      </item>
    
      <item>
        <title>简单的对vim进行改造</title>
        <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;

&lt;p&gt;同事在闲鱼淘了一个Magic Keyboard Gen1结果没用几天，escape的按键就坏掉了，但是已经确认收货了…..他舍不得钱浪费还想继续用，就罗列了一下escape的场景，发现最高频的还是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim&lt;/code&gt;中进行模式切换。有这个契机就分享一下我自己在mac下简单对vim的改造，不仅好用而且可以摆脱escape。&lt;/p&gt;

&lt;p&gt;简单介绍一下vim，它是一个文本编辑器，其实就和mac上面的文本编辑.app差不多，只不过它的交互哲学是通过键盘完成一切。在这里我不想吹vim，作为javaer而言难道世界上还有比intellij还更香的IDE吗？更别说文本编辑器🤡(逃~)。当然我也见识过真正的vim大神，原来公司有php组，有前辈大部分时候都是直接在服务器上编写代码很少用IDE，摸键盘和弹琴一样，刷刷刷~。&lt;/p&gt;

&lt;p&gt;为啥说&lt;strong&gt;简单改造&lt;/strong&gt;呢，因为原来也痴迷过vim，也觉得在vim上写代码看起来好厉害，也曾经折腾过高亮，提示，搜索，跳转非常酷炫，可是真要写jvm代码还是得看intellij，增加学习上手成本并且收益非常低。不过即便没有在服务器上编码的需求，肯定会有修改配置文件场景，所以我更加建议&lt;strong&gt;先对vim进行小小的顺手改造&lt;/strong&gt;，待以后有了服务器编码需求，可以&lt;strong&gt;再把相关插件以及自定义键位加上&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;配置和插件&quot;&gt;配置和插件&lt;/h2&gt;

&lt;p&gt;提一嘴，mac是自带vim，网上有大佬建议使用&lt;a href=&quot;https://github.com/macvim-dev/macvim&quot;&gt;MacVim&lt;/a&gt;替换系统vim。我不建议，一方面是懒，另外一方面MacVim除了内部继承vim优化最大的特点就是自带GUI，但是vim本来就是强调丢弃鼠标，感觉和理念有点违背，更别说现在2021年了MacVim对shell的支持还是很不到位，如果你和我一样是这是简单的CLI需求，&lt;strong&gt;我也建议你别换&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;另外从Mojave(10.14.x开始，MacOS内置的vim版本已经是8.x了，你要的需求自带的vim都可以满足。&lt;/p&gt;

&lt;h3 id=&quot;修改配置方法&quot;&gt;修改配置方法&lt;/h3&gt;

&lt;p&gt;Mac自带vim的默认配置文件在/usr/share/vim/vimrc。这个文件是系统级(全局)的vimrc配置文件，为了保证vim的正常运行，一般并不会修改这个文件。而是在～目录下创建一个新的用户级vimrc文件。在终端中键入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touch ~/.vimrc&lt;/code&gt; 手动创建一个配置文件(放心vim会自动读取这个文件)，值得注意的是，我这里创建的是隐藏文件，而全局配置文件是正常的非隐藏文件，隐藏和不隐藏都可以，看个人习惯(.xxx隐藏，xxx非隐藏)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205115527.png&quot; alt=&quot;image-20211205115527667&quot; /&gt;&lt;/p&gt;

&lt;center&gt;保证home目录下面有.vimrc文件&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;然后如果需要对vim做内置配置修改或者自定义键位配置都往这里面加修好了，不用重启就可以生效。&lt;/p&gt;

&lt;h3 id=&quot;安装插件的方法&quot;&gt;安装插件的方法&lt;/h3&gt;

&lt;p&gt;vim安装插件的话，你可以直接按照&lt;a href=&quot;https://github.com/vim/vim/blob/03c3bd9fd094c1aede2e8fe3ad8fd25b9f033053/runtime/doc/repeat.txt#L515&quot;&gt;vim官网的指南&lt;/a&gt;进行手动安装。但是我更加建议使用包管理器，后期更新都很方便，这里推荐&lt;a href=&quot;https://github.com/junegunn/vim-plug&quot;&gt;vim-plug&lt;/a&gt;。如果你可能会看别人推荐&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vundle&lt;/code&gt;，但是它已经不再更新了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim-plug&lt;/code&gt;不仅承诺会一直更新并且它提供非常翔实的文档&lt;/p&gt;

&lt;p&gt;安装也非常简单，它&lt;a href=&quot;https://github.com/junegunn/vim-plug&quot;&gt;github&lt;/a&gt;上有各个版本的安装指南，如果是unix(mac和linux都是)直接在shell里面输入&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-fLo&lt;/span&gt; ~/.vim/autoload/plug.vim &lt;span class=&quot;nt&quot;&gt;--create-dirs&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;即可，如果输入后一直卡进度条，可以看看我这篇文章的&lt;a href=&quot;https://www.someget.cn/other/2020/12/05/mac_proxy.html&quot;&gt;终端代理&lt;/a&gt;或者在它官网下载.vim脚本然后手动把脚本放到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vim/autoload/&lt;/code&gt;下(如果没有则自己创建一下就好了)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205120206.png&quot; alt=&quot;image-20211205120206489&quot; /&gt;&lt;/p&gt;

&lt;center&gt;安装成功界面(其实这个curl也很简单就是把脚本放到指定你本地路径)&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;安装好了以后你需要一个&lt;strong&gt;插件市场&lt;/strong&gt;然后找到对应的包名，有点像maven的坐标，vim-plug就是maven帮你管理各种依赖，maven远程中央仓库就是插件市场。除非你能把坐标背下来不然你需要去中央仓库获取坐标然后写到maven的pom文件中。在vim的插件中你需要去插件市场找到对应的命令然后写到.vimrc中，使用命令再安装(相当于。&lt;/p&gt;

&lt;p&gt;我使用的插件市场&lt;a href=&quot;https://vimawesome.com/&quot;&gt;vimawesome&lt;/a&gt;，插件非常全，并且不只是支持vim-pulg还支持市面上其他的包管理器&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205121905.png&quot; alt=&quot;image-20211205121905833&quot; /&gt;&lt;/p&gt;

&lt;center&gt;vimawesome页面上提供的某插件命令&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;你有了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim-plug&lt;/code&gt;以及收藏了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vimawesome&lt;/code&gt;，安装插件按照&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim-plug&lt;/code&gt;文档上的描述操作就好了。当然下面也会分享一些插件告诉大家怎么装。&lt;/p&gt;

&lt;h2 id=&quot;推荐修改配置&quot;&gt;推荐修改配置&lt;/h2&gt;

&lt;h3 id=&quot;开启高亮&quot;&gt;开启高亮&lt;/h3&gt;

&lt;p&gt;mac默认的vim配置是没有高亮的，我可以看不懂代码，但是代码不能不高亮。我随便编辑一个startup文件，里面有一些默认的配置和自定义配置看一下有高亮和没高亮的对比。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205154829.gif&quot; alt=&quot;2021-12-05 15.47.41&quot; /&gt;&lt;/p&gt;

&lt;center&gt;没有高亮&lt;/center&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205155059.gif&quot; alt=&quot;2021-12-05 15.50.31&quot; /&gt;&lt;/p&gt;

&lt;center&gt;有高亮&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;这个对比我相信不用我多说，我每次看组里面写安卓的同学满屏的花花绿绿就觉得好厉害，代码颜色越多应该技术越好吧🤡&lt;/p&gt;

&lt;p&gt;开启高亮非常简单只需要在shell里面输入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim ~/.vimrc&lt;/code&gt;，然后里面添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;syntax on&lt;/code&gt;，然后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:wq&lt;/code&gt;保存即可，然后你再去vim看看你的代码，是不是有高亮了呢。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205160300.png&quot; alt=&quot;image-20211205160300407&quot; /&gt;&lt;/p&gt;

&lt;center&gt;简单输入一行&lt;/center&gt;

&lt;h3 id=&quot;突出显示行&quot;&gt;突出显示行&lt;/h3&gt;

&lt;p&gt;只需要在shell里面输入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim ~/.vimrc&lt;/code&gt;，然后里面添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set cursorline&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205161017.png&quot; alt=&quot;image-20211205161016943&quot; /&gt;&lt;/p&gt;

&lt;center&gt;配置按照行分割&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;然后就可以获取下面的效果，光标停留在的这一行可以进行下划线的回显，在修改配置的非常好用，尤其是使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;来进行查找经常要眼睛到处瞟一瞟才能知道现在定位到哪里了&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205160949.png&quot; alt=&quot;image-20211205160949758&quot; /&gt;&lt;/p&gt;

&lt;center&gt;定位效果&lt;/center&gt;

&lt;h3 id=&quot;缩进一致&quot;&gt;缩进一致&lt;/h3&gt;

&lt;p&gt;我的vim大部分都是修改服务器上面的配置，有时候修改之前会做注释之类的，我第一行的缩进了，第二行换下来的时候不会自动保持缩进，还需要手动敲。在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt;中添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set autoindent&lt;/code&gt;可以下一行的缩进自动跟上一行的缩进保持一致。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205162222.png&quot; alt=&quot;image-20211205162222121&quot; /&gt;&lt;/p&gt;

&lt;center&gt;记得一个配置一行&lt;/center&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205164049.gif&quot; alt=&quot;2021-12-05 16.37.48&quot; /&gt;&lt;/p&gt;

&lt;center&gt;效果就是你换行会保持上一行的缩进&lt;/center&gt;

&lt;h3 id=&quot;替换escape按键&quot;&gt;替换escape按键&lt;/h3&gt;

&lt;p&gt;escape用的比较多的通常是从编辑模式退回到浏览模式，escape比较远但是用的比较舒服的核心键位都被占用了，我的解决办法是使用组合键，快速敲击j+k相当于代替escape。&lt;/p&gt;

&lt;p&gt;修改方法也很简单，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt;里面添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imap jk &amp;lt;Esc&amp;gt;&lt;/code&gt;就好了，jk可以换成你想要的其他按键，只不过我习惯jk。&lt;/p&gt;

&lt;p&gt;最终效果如下，在里面写内容的时候使用jk组合键就可以快速切回到预览模式&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205164923.gif&quot; alt=&quot;2021-12-05 16.48.34&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;替换按键&quot;&gt;替换&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;：&lt;/code&gt;按键&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;：&lt;/code&gt;必须要使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shift+;&lt;/code&gt;才可以激活，作为懒癌来说实在是不想按两个键，但是从预览模式进入命令模式又是每次和vim交互都需要做的操作。仔细思考发现在预览模式下，空格键其实是浪费的，因为预览模式下面的编辑都是依赖字母键没有占用空格键键。所以我把空格键设置成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;：&lt;/code&gt;用来进入命令模式。设置也非常简单在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt;里面添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nmap &amp;lt;space&amp;gt; :&lt;/code&gt;就好了。&lt;/p&gt;

&lt;p&gt;效果如下&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205165846.gif&quot; alt=&quot;2021-12-05 16.48.34&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;其他&quot;&gt;其他&lt;/h3&gt;

&lt;p&gt;其实大家可以按照自己的习惯去设置，键位映射来说，使用xmap就可以完成。其中x是动态的值，不同的x对应不同的功能，具体给大家做了一个表，大家可以发挥想象去自定义映射&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;x&lt;/th&gt;
      &lt;th&gt;含义&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;nore&lt;/td&gt;
      &lt;td&gt;非递归&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;n&lt;/td&gt;
      &lt;td&gt;普通模式生效&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;v&lt;/td&gt;
      &lt;td&gt;可视模式生效&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;i&lt;/td&gt;
      &lt;td&gt;插入模式生效&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;c&lt;/td&gt;
      &lt;td&gt;命令行模式生效&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;un&lt;/td&gt;
      &lt;td&gt;后面跟组合键, 表示删除这个映射&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;clear&lt;/td&gt;
      &lt;td&gt;清楚相关模式下所有映射&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;其他的配置的话，这里也给大家总结了一下&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 自动语法高亮&lt;/span&gt;
syntax on 
&lt;span class=&quot;c&quot;&gt;# 开启插件&lt;/span&gt;
filetype plugin indent on 
&lt;span class=&quot;c&quot;&gt;# 关闭 vi 兼容模式&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;nocompatible
&lt;span class=&quot;c&quot;&gt;# 显示行号&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;number 
&lt;span class=&quot;c&quot;&gt;# 突出显示当前行&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;cursorline  
&lt;span class=&quot;c&quot;&gt;# 打开状态栏标尺&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;ruler  
&lt;span class=&quot;c&quot;&gt;# 设定 &amp;lt;&amp;lt; 和 &amp;gt;&amp;gt; 命令移动时的宽度为 4&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;shiftwidth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 
&lt;span class=&quot;c&quot;&gt;# 使得按退格键时可以一次删掉 4 个空格&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;softtabstop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 
&lt;span class=&quot;c&quot;&gt;# 设定 tab 长度为 4&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tabstop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4 
&lt;span class=&quot;c&quot;&gt;# 覆盖文件时不备份&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;nobackup 
&lt;span class=&quot;c&quot;&gt;# 自动切换当前目录为当前文件所在的目录&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;autochdir 
&lt;span class=&quot;c&quot;&gt;# 设置备份时的行为为覆盖&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;backupcopy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;yes&lt;/span&gt; 
&lt;span class=&quot;c&quot;&gt;# 搜索时忽略大小写，但在有一个或以上大写字母时仍保持对大小写敏感&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;ignorecase smartcase 
&lt;span class=&quot;c&quot;&gt;# 禁止在搜索到文件两端时重新搜索&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;nowrapscan 
&lt;span class=&quot;c&quot;&gt;# 输入搜索内容时就显示搜索结果&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;incsearch 
&lt;span class=&quot;c&quot;&gt;# 搜索时高亮显示被找到的文本&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;hlsearch 
&lt;span class=&quot;c&quot;&gt;# 关闭错误信息响铃&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;noerrorbells 
&lt;span class=&quot;c&quot;&gt;# 关闭使用可视响铃代替呼叫&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;novisualbell 
&lt;span class=&quot;c&quot;&gt;# 置空错误铃声的终端代码&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;t_vb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
&lt;span class=&quot;c&quot;&gt;# 插入括号时，短暂地跳转到匹配的对应括号&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;showmatch 
&lt;span class=&quot;c&quot;&gt;# 短暂跳转到匹配括号的时间&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;matchtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 
&lt;span class=&quot;c&quot;&gt;# 设置魔术&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;magic 
&lt;span class=&quot;c&quot;&gt;# 允许在有未保存的修改时切换缓冲区，此时的修改由 vim 负责保存&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;hidden 
&lt;span class=&quot;c&quot;&gt;# 隐藏工具栏&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;guioptions-&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;T 
&lt;span class=&quot;c&quot;&gt;# 隐藏菜单栏&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;guioptions-&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;m 
&lt;span class=&quot;c&quot;&gt;# 开启新行时使用智能自动缩进&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;smartindent 
&lt;span class=&quot;c&quot;&gt;# 不设定在插入状态无法用退格键和 Delete 键删除回车符&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;backspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;indent,eol,start
&lt;span class=&quot;c&quot;&gt;# 设定命令行的行数为 1&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cmdheight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 
&lt;span class=&quot;c&quot;&gt;# 显示状态栏 (默认值为 1, 无法显示状态栏)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;laststatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 
&lt;span class=&quot;c&quot;&gt;# 设置在状态行显示的信息&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;statusline&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%&amp;lt;%F[%1&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%M%&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%n%R%H]%&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%y&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%0&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;%&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&amp;amp;fileformat&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&amp;amp;encoding&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;%c:%l/%L%&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 开始折叠&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;foldenable 
&lt;span class=&quot;c&quot;&gt;# 设置语法折叠&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;foldmethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;syntax 
&lt;span class=&quot;c&quot;&gt;# 设置折叠区域的宽度&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;foldcolumn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 
&lt;span class=&quot;c&quot;&gt;# 设置折叠层数为1&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set local &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;foldlevel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;推荐插件&quot;&gt;推荐插件&lt;/h2&gt;

&lt;p&gt;这里只推荐一个插件，主要是演示怎么使用vim-plug来安装插件，大家可以去&lt;a href=&quot;https://vimawesome.com/&quot;&gt;vimawesome&lt;/a&gt;自己淘插件，找到以后记得评论区分享哦。&lt;/p&gt;

&lt;p&gt;我推荐的插件是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;The NERD tree&lt;/code&gt;，它的作用是可以显示和IDE最基本的文件结构树，比如下面的Vscode&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205174946.png&quot; alt=&quot;image-20211205174946690&quot; /&gt;&lt;/p&gt;

&lt;center&gt;vscode的目录树&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;然后使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;The NERD tree&lt;/code&gt;以后，在vim里面看是这样的&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175029.png&quot; alt=&quot;image-20211205175029546&quot; /&gt;&lt;/p&gt;

&lt;center&gt;The NERD tree的界面&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;具体的安装方法如下&quot;&gt;具体的安装方法如下&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;去插件中心-vimawesome找到&lt;a href=&quot;https://vimawesome.com/plugin/nerdtree-red&quot;&gt;The NERD tree&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;复制这一行命令&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175144.png&quot; alt=&quot;image-20211205175144192&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;然后打开shell输入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim ~/.vimrc&lt;/code&gt;，对!!!又是这个配置文件，然后在里面输入&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;call plug#begin&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;~/.vim/autoload&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
call plug#end&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;然后把刚刚在vimawesome查到的命令，复制到这两者直接，变成这样&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;call plug#begin&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;~/.vim/autoload&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Plug &lt;span class=&quot;s1&quot;&gt;&apos;scrooloose/nerdtree&apos;&lt;/span&gt;
call plug#end&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175551.png&quot; alt=&quot;image-20211205175551695&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;然后切换到命令模式，输入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PlugInstall&lt;/code&gt;(注意必须装vim-plug,不然提示找不到命令)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175831.png&quot; alt=&quot;image-20211205175831177&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mypicgogo.oss-cn-hangzhou.aliyuncs.com/tuchuang20211205175847.png&quot; alt=&quot;image-20211205175847764&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;然后你可以在vim下按&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ctrl+t&lt;/code&gt;查看当前文件的所在的目录树，详细的文档vimawesome的该插件详情介绍的会比我详细很多。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;至此，插件就装好啦，如果有其他的插件需求安装，与上面的步骤一直，把在vimawesome找到的命令复制到两个call里面，然后在PlugInstall就好了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;END&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;推荐资料&quot;&gt;推荐资料&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.lanqiao.cn/courses/2840&quot;&gt;蓝桥云课的基础vim&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://vim-adventures.com/&quot;&gt;vim冒险小游戏&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/junegunn/vim-plug&quot;&gt;vim-plug&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/vim/vim/blob/03c3bd9fd094c1aede2e8fe3ad8fd25b9f033053/runtime/doc/repeat.txt#L515&quot;&gt;vim官网的手动插件指南&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://vimawesome.com/&quot;&gt;推荐的插件仓库-vimawesome&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2018/09/vimrc.html&quot;&gt;阮一峰老师vim配置入门&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sun, 05 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://www.someget.cn/other/2021/12/05/vim_coustm01.html</link>
        <guid isPermaLink="true">https://www.someget.cn/other/2021/12/05/vim_coustm01.html</guid>
        
        
        <category>other</category>
        
      </item>
    
  </channel>
</rss>
