<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>简斋</title>
  
  <subtitle>萬物之始，大道至簡，衍化至繁</subtitle>
  <link href="https://blog.yjll.site/atom.xml" rel="self"/>
  
  <link href="https://blog.yjll.site/"/>
  <updated>2026-10-14T06:56:34.584Z</updated>
  <id>https://blog.yjll.site/</id>
  
  <author>
    <name>简斋</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Linux使用TUN模式实现透明代理</title>
    <link href="https://blog.yjll.site/post/8852bf9.html"/>
    <id>https://blog.yjll.site/post/8852bf9.html</id>
    <published>2025-01-08T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<h3 id="树莓派做旁路由"><a href="#树莓派做旁路由" class="headerlink" title="树莓派做旁路由"></a>树莓派做旁路由</h3><p>我的树莓派作为旁路由，代理所有经过的流量，包括自己发出的请求。</p><p>编辑 /etc/sysctl.conf 文件，将 net.ipv4.ip_forward=0 修改为 net.ipv4.ip_forward=1，然后执行 sysctl -p 以使配置生效</p><p>docker中开启<code>privileged: true</code>，并且clash中开启<code>tun</code>模式</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">version: &#39;3&#39;services:  clash:    image: dreamacro&#x2F;clash-premium:latest    container_name: clash    volumes:      - .&#x2F;clash:&#x2F;root&#x2F;.config&#x2F;clash    restart: unless-stopped    network_mode: &quot;host&quot; # or &quot;host&quot; on Linux    privileged: true    environment:      - TZ&#x3D;Asia&#x2F;Shanghai<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="不代理本机数据"><a href="#不代理本机数据" class="headerlink" title="不代理本机数据"></a>不代理本机数据</h3><p>另一个Linux主机不对内部应用进行代理。</p><p>开启网卡混杂模式<br>    ip link set enp3s0 promisc on</p><p>创建macvlan网络</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">  docker network create -d macvlan \                       --subnet&#x3D;192.168.1.0&#x2F;24 \--gateway&#x3D;192.168.1.1 \-o parent&#x3D;enp3s0 \macvlan142<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">version: &#39;3&#39;services:  clash:    image: dreamacro&#x2F;clash-premium:latest    container_name: clash_tun    volumes:      - .&#x2F;clash:&#x2F;root&#x2F;.config&#x2F;clash    restart: unless-stopped    networks:      macvlan142:        ipv4_address: 192.168.1.220        privileged: true    environment:      - TZ&#x3D;Asia&#x2F;Shanghainetworks:  macvlan142:    external: true  <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>其他设备将网关改为192.168.1.220即可使用。</p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://github.com/lululau/clashindocker">clashindocker</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;树莓派做旁路由&quot;&gt;&lt;a href=&quot;#树莓派做旁路由&quot; class=&quot;headerlink&quot; title=&quot;树莓派做旁路由&quot;&gt;&lt;/a&gt;树莓派做旁路由&lt;/h3&gt;&lt;p&gt;我的树莓派作为旁路由，代理所有经过的流量，包括自己发出的请求。&lt;/p&gt;
&lt;p&gt;编辑 /etc/sys</summary>
      
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="Docker" scheme="https://blog.yjll.site/tags/Docker/"/>
    
    <category term="透明代理" scheme="https://blog.yjll.site/tags/%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86/"/>
    
    <category term="Clash" scheme="https://blog.yjll.site/tags/Clash/"/>
    
    <category term="macvlan" scheme="https://blog.yjll.site/tags/macvlan/"/>
    
  </entry>
  
  <entry>
    <title>通过代理服务更改HTTP request的地址</title>
    <link href="https://blog.yjll.site/post/86d57f8b.html"/>
    <id>https://blog.yjll.site/post/86d57f8b.html</id>
    <published>2024-12-21T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>利用更改<a href="https://github.com/abhinavsingh/proxy.py">proxy.py</a>中的<a href="https://github.com/abhinavsingh/proxy.py/tree/develop?tab=readme-ov-file#redirecttocustomserverplugin">Redirect To Custom Server Plugin</a>插件来更改原Android端请求的IP地址。</p><p>更改<code>plugin/redirect_to_custom_server.py</code>文件</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">class</span> <span class="token class-name">RedirectToCustomServerPlugin</span><span class="token punctuation">(</span>HttpProxyBasePlugin<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">def</span> <span class="token function">before_upstream_connection</span><span class="token punctuation">(</span>            self<span class="token punctuation">,</span> request<span class="token punctuation">:</span> HttpParser<span class="token punctuation">,</span>    <span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> Optional<span class="token punctuation">[</span>HttpParser<span class="token punctuation">]</span><span class="token punctuation">:</span>        <span class="token comment"># 原服务器地址</span>        old_server <span class="token operator">=</span> <span class="token string">'http://xxx:8088'</span>        <span class="token comment"># 新服务器地址</span>        new_server <span class="token operator">=</span> <span class="token string">'http://yyy:8088'</span>        url <span class="token operator">=</span> <span class="token builtin">str</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>_url<span class="token punctuation">)</span>        <span class="token keyword">if</span> <span class="token keyword">not</span> request<span class="token punctuation">.</span>is_https_tunnel <span class="token keyword">and</span> old_server <span class="token keyword">in</span> url <span class="token punctuation">:</span>            url <span class="token operator">=</span> url<span class="token punctuation">.</span>replace<span class="token punctuation">(</span>old_server<span class="token punctuation">,</span>new_server<span class="token punctuation">)</span>                        request<span class="token punctuation">.</span>set_url<span class="token punctuation">(</span>url<span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token string">'utf_8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token comment"># Update Host header too, otherwise upstream can reject our request</span>            <span class="token keyword">if</span> request<span class="token punctuation">.</span>has_header<span class="token punctuation">(</span><span class="token string">b'Host'</span><span class="token punctuation">)</span> <span class="token punctuation">:</span>                request<span class="token punctuation">.</span>del_header<span class="token punctuation">(</span><span class="token string">b'Host'</span><span class="token punctuation">)</span>            request<span class="token punctuation">.</span>add_header<span class="token punctuation">(</span>                <span class="token string">b'Host'</span><span class="token punctuation">,</span> urlparse<span class="token punctuation">.</span>urlsplit<span class="token punctuation">(</span>                    new_server<span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                <span class="token punctuation">)</span><span class="token punctuation">.</span>netloc<span class="token punctuation">,</span>            <span class="token punctuation">)</span>        <span class="token keyword">return</span> request<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>启动proxy.py服务</p><figure><div class="code-wrapper"><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">proxy  --enable-web-server <span class="token parameter variable">--plugins</span> proxy.plugin.RedirectToCustomServerPlugin <span class="token parameter variable">--hostname</span> <span class="token number">0.0</span>.0.0  <span class="token parameter variable">--port</span> <span class="token number">1080</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>最后在Android端设置代理为当前服务即可实现IP地址的变更。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;利用更改&lt;a href=&quot;https://github.com/abhinavsingh/proxy.py&quot;&gt;proxy.py&lt;/a&gt;中的&lt;a href=&quot;https://github.com/abhinavsingh/proxy.py/tree/develop?tab=r</summary>
      
    
    
    
    <category term="Python" scheme="https://blog.yjll.site/categories/Python/"/>
    
    
    <category term="代理" scheme="https://blog.yjll.site/tags/%E4%BB%A3%E7%90%86/"/>
    
    <category term="python" scheme="https://blog.yjll.site/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>Centos7升级OpenSSH</title>
    <link href="https://blog.yjll.site/post/489071f8.html"/>
    <id>https://blog.yjll.site/post/489071f8.html</id>
    <published>2024-10-22T14:16:58.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>出于对安全的考虑，需要升级OpenSSH的版本，但是Centos7的源中的OpenSSH版本比较低，需要手动安装。</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">查看可升级的版本yum --showduplicates list openssh<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></div></figure><h3 id="安装开启telnet服务"><a href="#安装开启telnet服务" class="headerlink" title="安装开启telnet服务"></a>安装开启telnet服务</h3><p>先开启telnet防止网络问题导致远程连接失败，无法补救。</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">yum install telnet-server  -yyum install telnet -ysystemctl enable telnet.socketsystemctl start telnet.socket<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>编辑<code>/etc/securetty</code>文件，允许终端使用root用户登录</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">echo &#39;pts&#x2F;0&#39; &gt;&gt;&#x2F;etc&#x2F;securettyecho &#39;pts&#x2F;1&#39; &gt;&gt;&#x2F;etc&#x2F;securetty<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></div></figure><h3 id="升级OpenSSH"><a href="#升级OpenSSH" class="headerlink" title="升级OpenSSH"></a>升级OpenSSH</h3><p>先备份配置文件</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">mv &#x2F;etc&#x2F;ssh&#x2F;sshd_config &#x2F;etc&#x2F;ssh&#x2F;sshd_config.$(date +%Y%m%d)<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>编译的脚本可以参考<a href="https://github.com/boypt/openssh-rpms">boypt/openssh-rpms</a>，在rpm包所在的目录执行<code>rpm -ivh --force --nodeps --replacepkgs --replacefiles openssh-*.rpm</code></p><p>升级成功后，重启sshd服务，使用另外一个终端尝试是否能正常登录。</p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://github.com/boypt/openssh-rpms">boypt/openssh-rpms</a></p><p><a href="https://cikeblog.com/upgrade-openssh-to-openssh-9-9p1-under-centos7.html">Centos7下rpm升级OpenSSH到openssh-9.9p1版本</a></p><p><a href="https://www.cnblogs.com/jianzhan/p/ssh-update.html">记一次手动将OpenSSH从7.4升级到9.3的过程</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;出于对安全的考虑，需要升级OpenSSH的版本，但是Centos7的源中的OpenSSH版本比较低，需要手动安装。&lt;/p&gt;
&lt;figure&gt;&lt;div class=&quot;code-wrapper&quot;&gt;&lt;pre class=&quot;line-numbers language-none&quot;&gt;&lt;</summary>
      
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="Centos" scheme="https://blog.yjll.site/tags/Centos/"/>
    
    <category term="OpenSSH" scheme="https://blog.yjll.site/tags/OpenSSH/"/>
    
    <category term="Linux" scheme="https://blog.yjll.site/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>FFmpeg使用笔记</title>
    <link href="https://blog.yjll.site/post/9539654e.html"/>
    <id>https://blog.yjll.site/post/9539654e.html</id>
    <published>2023-12-08T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>CentOS 7 下安装FFmpeg</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">dnf -y install https:&#x2F;&#x2F;download1.rpmfusion.org&#x2F;free&#x2F;fedora&#x2F;rpmfusion-free-release-$\(rpm -E %fedora).noarch.rpm<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>intel核显也可以直接使用支持qsv的docker<a href="https://hub.docker.com/r/wzhy1234/ffmpeg-qsv">ffmpeg-qsv</a></p><h3 id="检查视频完整性"><a href="#检查视频完整性" class="headerlink" title="检查视频完整性"></a>检查视频完整性</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -v error -i file.avi -f null -  </code></pre></div></figure><p>输出到日志  </p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -v error -i file.avi -f null -  2&gt;error.log</code></pre></div></figure><h3 id="查看信息"><a href="#查看信息" class="headerlink" title="查看信息"></a>查看信息</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i file.mp4</code></pre></div></figure><h3 id="指定码率和分辨率"><a href="#指定码率和分辨率" class="headerlink" title="指定码率和分辨率"></a>指定码率和分辨率</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i A.mp4 -vcodec libx264 -b:v 1500k -vf scale=1280:720 B.mp4</code></pre></div></figure><h3 id="缩放为固定分辨率的命令行："><a href="#缩放为固定分辨率的命令行：" class="headerlink" title="缩放为固定分辨率的命令行："></a>缩放为固定分辨率的命令行：</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i 1.mp4 -vf scale=640:480 output.mp4</code></pre></div></figure><h3 id="缩放为自适应宽高比的命令行："><a href="#缩放为自适应宽高比的命令行：" class="headerlink" title="缩放为自适应宽高比的命令行："></a>缩放为自适应宽高比的命令行：</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i 1.mp4 -vf scale=640:-2 output.mp4</code></pre></div></figure><h3 id="合并"><a href="#合并" class="headerlink" title="合并"></a>合并</h3><p>创建一个文本文件，列出要合并的视频文件。以下是一个示例：</p><p>首先，创建一个文本文件，比如 input.txt，内容如下：</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">file &#39;input1.mp4&#39;file &#39;input2.mp4&#39;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></div></figure><p>可使用脚本生成</p><p>Windows:</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">(for %i in (*.wmv) do @echo file &#39;%i&#39;) &gt; input.txt</code></pre></div></figure><p>Linux:</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">printf &quot;file &#39;%s&#39;\n&quot; *.wmv &gt; input.txt</code></pre></div></figure><p>然后，使用以下命令将这两个视频文件合并成一个：</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -f concat -i input.txt -c copy output.mp4</code></pre></div></figure><p>在这个例子中，input.txt 包含了要合并的视频文件列表。-f concat 告诉 FFmpeg 使用 concat 协议，-i input.txt 指定了输入文件，-c copy 告诉 FFmpeg 只进行复制而不重新编码，这样可以加快处理速度并且保持视频质量，最后的 output.mp4 是合并后的输出文件名。</p><h3 id="提取ISO中的文件，我这里取的都是第一条流"><a href="#提取ISO中的文件，我这里取的都是第一条流" class="headerlink" title="提取ISO中的文件，我这里取的都是第一条流"></a>提取ISO中的文件，我这里取的都是第一条流</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i input.iso -map 0:v:0 -map 0:a:0 -c copy output.mp4</code></pre></div></figure><h3 id="截图功能"><a href="#截图功能" class="headerlink" title="截图功能"></a>截图功能</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -ss 00:12:39.0 -i input.wmv  -frames:v 1 /share/media/output.jpg</code></pre></div></figure><h3 id="截取视频"><a href="#截取视频" class="headerlink" title="截取视频"></a>截取视频</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg  -ss 00:45:35 -to 00:57:47 -i input.mp4 -c copy test.mp4</code></pre></div></figure><h3 id="下载m3u8文件"><a href="#下载m3u8文件" class="headerlink" title="下载m3u8文件"></a>下载m3u8文件</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i 视频地址 -c copy -bsf:a aac_adtstoasc output.mp4</code></pre></div></figure><p>ffmpeg毕竟不是下载工具，下载也可以使用<a href="https://github.com/llychao/m3u8-downloader">m3u8-downloader</a>，支持更多参数。</p><h3 id="合并字幕"><a href="#合并字幕" class="headerlink" title="合并字幕"></a>合并字幕</h3><p>推荐使用外挂字幕形式，合并成mkv格式</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i input.mp4 -i sub.srt -c copy  output.mkv</code></pre></div></figure><p>也可以使用<code>mkvmerge</code>进行处理</p><p>参考<a href="https://ubuntuhandbook.org/index.php/2018/08/mkvtoolnix-26-0-0-released/">MKVToolNix 26.0.0 Released, How to Install it in Ubuntu</a>进行安装</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">sudo sh -c &#39;echo &quot;deb https:&#x2F;&#x2F;mkvtoolnix.download&#x2F;ubuntu&#x2F; $(lsb_release -sc) main&quot; &gt;&gt; &#x2F;etc&#x2F;apt&#x2F;sources.list.d&#x2F;bunkus.org.list&#39;wget -q -O - https:&#x2F;&#x2F;mkvtoolnix.download&#x2F;gpg-pub-moritzbunkus.txt | sudo apt-key add -sudo apt-get updatesudo apt-get install mkvtoolnix<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>合并字幕，并且设置默认字幕为中文</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">mkvmerge -o merge/Game.of.Thrones.S02E08.mkv  Game.of.Thrones.S02E08.mkv --language 0:chi Game.of.Thrones.S02E08.ass </code></pre></div></figure><h3 id="反交错"><a href="#反交错" class="headerlink" title="反交错"></a>反交错</h3><p>隔行扫描的视频可以在转码阶段进行反交错处理</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i input.mp4 -vf yadif output.mp4</code></pre></div></figure><p>若视频包含大量快速运动场景可使用mdeint、bwdif、mcdeint滤镜</p><h3 id="视频质量对比"><a href="#视频质量对比" class="headerlink" title="视频质量对比"></a>视频质量对比</h3><p>VMAF是Netflix开发的视频质量评估算法，VMAF分值在0~100之间，越大越好，20分对应极差，100分对应极好。</p><p>查看libvmaf支持的参数</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -h filter=libvmaf</code></pre></div></figure><p>n_threads为线程数，n_subsample抽样帧的间隔</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -i &#39;硬编码.mp4&#39; -i &#39;2023-10-10 17-06-09.mp4&#39; -lavfi &quot;[0:v][1:v]libvmaf=n_threads=4:n_subsample=10:log_fmt=json:log_path=vmaf_report.json&quot; -f null -</code></pre></div></figure><h3 id="生成预览图"><a href="#生成预览图" class="headerlink" title="生成预览图"></a>生成预览图</h3><p>间隔五分钟</p><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">ffmpeg -skip_frame nokey -i input.mp4 -vf &quot;fps=1/300,scale=320:-1,tile=4x4&quot; -frames:v 1 output.jpg</code></pre></div></figure><p>批量生成<br>    for i in <em>.mp4; do ffmpeg -skip_frame nokey -i “$i” -vf “fps=1/300,scale=320:-1,tile=4x4” -frames:v 1 “${i%.</em>}-thumb.jpg”; done </p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://juejin.cn/post/7034349606704152589">FFMPEG合并视频文件（无损）</a></p><p><a href="https://www.jianshu.com/p/9c07b730d1dc">每天一点FFmpeg</a></p><p><a href="https://sspai.com/post/77442">视频压缩画质对比工具VMAF使用记录</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;CentOS 7 下安装FFmpeg&lt;/p&gt;
&lt;figure&gt;&lt;div class=&quot;code-wrapper&quot;&gt;&lt;pre class=&quot;line-numbers language-none&quot;&gt;&lt;code class=&quot;language-none&quot;&gt;dnf -y insta</summary>
      
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
  </entry>
  
  <entry>
    <title>Java给ID加锁的实现</title>
    <link href="https://blog.yjll.site/post/2b6e6ad5.html"/>
    <id>https://blog.yjll.site/post/2b6e6ad5.html</id>
    <published>2022-07-05T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.583Z</updated>
    
    <content type="html"><![CDATA[<p>服务端开发锁无处不在，有时就是需要对用户或者某个资源加锁，防止被并发访问，我这里分别介绍单体应用和分布式应用对锁ID的实现。</p><h3 id="单体应用"><a href="#单体应用" class="headerlink" title="单体应用"></a>单体应用</h3><p>单体应用，可以使用Jvm的锁，高效简单。</p><h4 id="String-intern确保ID的唯一性，再锁该ID"><a href="#String-intern确保ID的唯一性，再锁该ID" class="headerlink" title="String.intern确保ID的唯一性，再锁该ID"></a>String.intern确保ID的唯一性，再锁该ID</h4><p>stackoverflow上有人提出了简单的解决方案：<a href="https://stackoverflow.com/questions/5639870/simple-java-name-based-locks">simple-java-name-based-locks</a></p><blockquote><p>All those answers I see are way too complicated. Why not simply use:</p></blockquote><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">executeInNamedLock</span><span class="token punctuation">(</span><span class="token class-name">String</span> lockName<span class="token punctuation">,</span> <span class="token class-name">Runnable</span> runnable<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">synchronized</span><span class="token punctuation">(</span>lockName<span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    runnable<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><blockquote><p>The key point is the method intern: it ensures that the String returned is a global unique object, and so it can be used as a vm-instance-wide mutex. All interned Strings are held in a global pool, so that’s your static cache you were talking about in your original question. Don’t worry about memleaks; those strings will be gc’ed if no other thread references it. Note however, that up to and including Java6 this pool is kept in PermGen space instead of the heap, so you might have to increase it.</p></blockquote><blockquote><p>There’s a problem though if some other code in your vm locks on the same string for completely different reasons, but a) this is very unlikely, and b) you can get around it by introducing namespaces, e.g. executeInNamedLock(this.getClass().getName() + “_” + myLockName);</p></blockquote><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">internLock</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">biz</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>使用Java自带的synchronized进行锁ID，由于synchronized是基于对象进行加锁，不能保证同一个ID是一个Java对象，我们需要将ID转换为String，并放入常量池中，保证每个ID都是对应一个唯一的对象。关于intern的详细介绍，可以看美团技术团队写的<a href="https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html">深入解析String#intern</a></p><p>完整代码：</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> list <span class="token operator">=</span> <span class="token class-name">ImmutableList</span><span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span><span class="token number">10001</span><span class="token punctuation">,</span> <span class="token number">10002</span><span class="token punctuation">,</span> <span class="token number">10003</span><span class="token punctuation">,</span> <span class="token number">10004</span><span class="token punctuation">,</span> <span class="token number">10005</span><span class="token punctuation">,</span> <span class="token number">10001</span><span class="token punctuation">,</span> <span class="token number">10003</span><span class="token punctuation">,</span> <span class="token number">10003</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">/**     * 模拟5秒钟耗时操作     */</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">biz</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>            <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"id:&#123;&#125;"</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">/**     * 使用 String.intern     *     * @param id     */</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">internLock</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token function">biz</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testStringIntern</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"使用 String.intern"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        list<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>it <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token function">internLock</span><span class="token punctuation">(</span>it<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>运行结果:</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">15:15:31.355 [Test worker] INFO LockTest - 使用 String.intern15:15:36.369 [Thread-21] INFO LockTest - id:1000215:15:36.369 [Thread-23] INFO LockTest - id:1000415:15:36.369 [Thread-27] INFO LockTest - id:1000315:15:36.369 [Thread-20] INFO LockTest - id:1000115:15:36.369 [Thread-24] INFO LockTest - id:1000515:15:41.377 [Thread-25] INFO LockTest - id:1000115:15:41.377 [Thread-22] INFO LockTest - id:1000315:15:46.387 [Thread-26] INFO LockTest - id:10003<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>可以看出list中的前五个元素<code>10001, 10002, 10003, 10004, 10005</code>没有被阻塞，<code>15:15:36</code>时出的结果，第6个和第7个元素<code>10001,100003</code>被阻塞，耗时5秒钟。最后一个<code>100003</code>又被前一个<code>100003</code>阻塞，耗时5秒钟。从结果上看符合我们的预期。</p><h4 id="使用Guava的Interners-newWeakInterner"><a href="#使用Guava的Interners-newWeakInterner" class="headerlink" title="使用Guava的Interners.newWeakInterner"></a>使用Guava的Interners.newWeakInterner</h4><p>使用 intern()有性能问题,可以用 guava 的 Interners.newWeakInterner()一个弱引用的内部常量池<br>具体代码:</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Interner</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> pool <span class="token operator">=</span> <span class="token class-name">Interners</span><span class="token punctuation">.</span><span class="token function">newWeakInterner</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">guavaInternLock</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>pool<span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">biz</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>运行结果</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">15:15:11.339 [Test worker] INFO LockTest - 使用 Guava Interners15:15:16.357 [Thread-12] INFO LockTest - id:1000115:15:16.357 [Thread-13] INFO LockTest - id:1000215:15:16.358 [Thread-14] INFO LockTest - id:1000315:15:16.360 [Thread-15] INFO LockTest - id:1000415:15:16.360 [Thread-16] INFO LockTest - id:1000515:15:21.372 [Thread-19] INFO LockTest - id:1000315:15:21.372 [Thread-17] INFO LockTest - id:1000115:15:26.385 [Thread-18] INFO LockTest - id:10003<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h4 id="使用Guava的Striped"><a href="#使用Guava的Striped" class="headerlink" title="使用Guava的Striped"></a>使用Guava的Striped</h4><p>如果想实现更细力度的锁，可以使用 <code>JUC</code>中的Lock，但是单纯使用Lock是无法区分id的，需要我们自己实现判重逻辑。可以使用<code>Guava Striped</code>很轻松实现该功能。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">final</span> <span class="token class-name">Striped</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Lock</span><span class="token punctuation">></span></span> striped <span class="token operator">=</span> <span class="token class-name">Striped</span><span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">stripedLock</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">Lock</span> lock <span class="token operator">=</span> striped<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>        <span class="token function">biz</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>运行结果</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">15:14:51.323 [Test worker] INFO LockTest - 使用 Guava Striped15:14:56.339 [Thread-5] INFO LockTest - id:1000215:14:56.339 [Thread-7] INFO LockTest - id:1000415:14:56.339 [Thread-8] INFO LockTest - id:1000515:14:56.339 [Thread-4] INFO LockTest - id:1000115:14:56.339 [Thread-6] INFO LockTest - id:1000315:15:01.354 [Thread-9] INFO LockTest - id:1000115:15:01.354 [Thread-10] INFO LockTest - id:1000315:15:06.357 [Thread-11] INFO LockTest - id:10003<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="分布式应用"><a href="#分布式应用" class="headerlink" title="分布式应用"></a>分布式应用</h3><p>由于分布式环境中，需要部署多个节点，上文提到的锁是基于单个JVM，不能保证同一时间某个资源只有一个线程访问，所以需要借助其他中间件。</p><h4 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h4><p>关于Redis加锁的原理可以看我之前写的<a href="/post/3a12d43e.html" title="Redis进阶">Redis进阶</a>，我这里直接使用<a href="https://github.com/redisson/redisson">redisson</a>封装好的工具。<br><code>redisson</code>借助<code>lua</code>脚本，实现了可重入锁、读写锁等多种锁。具体可参考<a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8">分布式锁和同步器</a></p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">redisLock</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">final</span> <span class="token class-name">RLock</span> lock <span class="token operator">=</span> <span class="token class-name">RedisClient</span><span class="token punctuation">.</span><span class="token constant">INSTANCE</span><span class="token punctuation">.</span><span class="token function">getLock</span><span class="token punctuation">(</span>id<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>        <span class="token function">biz</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>运行结果</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">2022-07-05 09:45:33 INFO  (Thread-6 LockTest:76)- id:100032022-07-05 09:45:33 INFO  (Thread-9 LockTest:76)- id:100012022-07-05 09:45:33 INFO  (Thread-8 LockTest:76)- id:100052022-07-05 09:45:33 INFO  (Thread-5 LockTest:76)- id:100022022-07-05 09:45:33 INFO  (Thread-7 LockTest:76)- id:100042022-07-05 09:45:38 INFO  (Thread-4 LockTest:76)- id:100012022-07-05 09:45:38 INFO  (Thread-10 LockTest:76)- id:100032022-07-05 09:45:43 INFO  (Thread-11 LockTest:76)- id:10003<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h4 id="ZooKeeper"><a href="#ZooKeeper" class="headerlink" title="ZooKeeper"></a>ZooKeeper</h4><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://blog.csdn.net/inrgihc/article/details/119335729">使用 Google Guava Striped 实现基于 Key 的并发锁</a></p><p><a href="https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html">深入解析String#intern</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;服务端开发锁无处不在，有时就是需要对用户或者某个资源加锁，防止被并发访问，我这里分别介绍单体应用和分布式应用对锁ID的实现。&lt;/p&gt;
&lt;h3 id=&quot;单体应用&quot;&gt;&lt;a href=&quot;#单体应用&quot; class=&quot;headerlink&quot; title=&quot;单体应用&quot;&gt;&lt;/a&gt;单体应用</summary>
      
    
    
    
    <category term="Java" scheme="https://blog.yjll.site/categories/Java/"/>
    
    
    <category term="Java" scheme="https://blog.yjll.site/tags/Java/"/>
    
    <category term="锁" scheme="https://blog.yjll.site/tags/%E9%94%81/"/>
    
    <category term="分布式" scheme="https://blog.yjll.site/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>Pandas连接Clickhouse</title>
    <link href="https://blog.yjll.site/post/2cd58774.html"/>
    <id>https://blog.yjll.site/post/2cd58774.html</id>
    <published>2022-06-28T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>我这里介绍两种方法：</p><ol><li><p>使用Pandas的read_sql方法通过sqlalchemy连接Clickhouse，需要安装<a href="https://github.com/xzkostyan/clickhouse-sqlalchemy">clickhouse-sqlalchemy</a></p></li><li><p>使用<a href="https://github.com/kszucs/pandahouse">pandahouse</a>直接读取，返回DataFrame</p></li></ol><p>推荐使用第一种方式，因为与我们平时连接数据库方式一致，无缝衔接，而且<code>clickhouse-sqlalchemy</code>一直在维护，<code>pandahouse</code>已经很久没更新了。</p><h3 id="clickhouse-sqlalchemy"><a href="#clickhouse-sqlalchemy" class="headerlink" title="clickhouse-sqlalchemy"></a>clickhouse-sqlalchemy</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">pip install clickhouse-sqlalchemy</code></pre></div></figure><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> sqlalchemy <span class="token keyword">import</span> create_engine<span class="token keyword">import</span> pandas <span class="token keyword">as</span> pdclick_url <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f'clickhouse+http://admin:paswd@localhost:8123/pro'</span></span>engine <span class="token operator">=</span> create_engine<span class="token punctuation">(</span>click_url<span class="token punctuation">)</span>fin_sql <span class="token operator">=</span> <span class="token triple-quoted-string string">'''select count(code) from finished'''</span>pd<span class="token punctuation">.</span>read_sql<span class="token punctuation">(</span>fin_sql<span class="token punctuation">,</span>con<span class="token operator">=</span>engine<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="pandahouse"><a href="#pandahouse" class="headerlink" title="pandahouse"></a>pandahouse</h3><figure><div class="code-wrapper"><pre class="language-none"><code class="language-none">pip install pandahouse</code></pre></div></figure><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> pandahouse <span class="token keyword">import</span> read_clickhouseconnection <span class="token operator">=</span> <span class="token builtin">dict</span><span class="token punctuation">(</span>database<span class="token operator">=</span><span class="token string">'pro'</span><span class="token punctuation">,</span>                  host<span class="token operator">=</span><span class="token string">'localhost:8123'</span><span class="token punctuation">,</span>                  user<span class="token operator">=</span><span class="token string">'admin'</span><span class="token punctuation">,</span>                  password<span class="token operator">=</span><span class="token string">'paswd'</span><span class="token punctuation">)</span>df <span class="token operator">=</span> read_clickhouse<span class="token punctuation">(</span>fin_sql<span class="token punctuation">,</span>connection<span class="token operator">=</span>connection<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我这里介绍两种方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;使用Pandas的read_sql方法通过sqlalchemy连接Clickhouse，需要安装&lt;a href=&quot;https://github.com/xzkostyan/clickhouse-sqlalchemy&quot;&gt;c</summary>
      
    
    
    
    <category term="Python" scheme="https://blog.yjll.site/categories/Python/"/>
    
    
    <category term="Python" scheme="https://blog.yjll.site/tags/Python/"/>
    
    <category term="Pandas" scheme="https://blog.yjll.site/tags/Pandas/"/>
    
    <category term="Clickhouse" scheme="https://blog.yjll.site/tags/Clickhouse/"/>
    
  </entry>
  
  <entry>
    <title>Java log 不打印异常栈信息</title>
    <link href="https://blog.yjll.site/post/6b05ac2c.html"/>
    <id>https://blog.yjll.site/post/6b05ac2c.html</id>
    <published>2022-01-26T10:35:34.000Z</published>
    <updated>2026-10-14T06:56:34.583Z</updated>
    
    <content type="html"><![CDATA[<p>最近查线上问题时，发现了一个不打印异常栈信息的情况，仔细排查原来和JVM优化有关<span id="more"></span>，log如下</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">2022-01-25 11:57:38,074 ERROR (AgentOutboundController.java:126)- nulljava.lang.NullPointerException: null<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></div></figure><p>查看日志时我以为代码中没有打印异常的详细信息，又查看了一遍代码</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">try</span> <span class="token punctuation">&#123;</span>    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>ex<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ex<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Result</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>发现代码写的没什么问题，那么问题出在哪？<br>原来JVM对一些特定的异常类型做了Fast Throw优化，如果检测到在代码里某个位置连续多次抛出同一类型异常的话，异常栈信息会被清空。</p><p>在启动时配置<code>-XX:-OmitStackTraceInFastThrow</code>参数，即可关闭此优化。</p><p>配置好参数重启服务，日志恢复了正常：</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">2022-01-25 12:53:37,167 ERROR (AgentOutboundController.java:126)- nulljava.lang.NullPointerException: nullat com.example.production.api.service.AgentOutboundService.lambda$addOutboundCode$0(AgentOutboundService.java:388)at java.util.ArrayList.forEach(ArrayList.java:1259)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>将异常栈信息打印出来就可以看到到底是那行代码出现了问题了。</p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://my.oschina.net/LucasZhu/blog/1830044">JVM参数OmitStackTraceInFastThrow导致的异常栈信息不见了</a></p><p><a href="https://juejin.cn/post/6967908573649043492">OmitStackTraceInFastThrow与JVM的C1,C2编译器</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近查线上问题时，发现了一个不打印异常栈信息的情况，仔细排查原来和JVM优化有关</summary>
    
    
    
    <category term="Java" scheme="https://blog.yjll.site/categories/Java/"/>
    
    
    <category term="Java" scheme="https://blog.yjll.site/tags/Java/"/>
    
    <category term="JVM" scheme="https://blog.yjll.site/tags/JVM/"/>
    
    <category term="Log" scheme="https://blog.yjll.site/tags/Log/"/>
    
  </entry>
  
  <entry>
    <title>自建直播流媒体服务</title>
    <link href="https://blog.yjll.site/post/3bc6b560.html"/>
    <id>https://blog.yjll.site/post/3bc6b560.html</id>
    <published>2021-11-12T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.585Z</updated>
    
    <content type="html"><![CDATA[<p>想在web端播放实时监控视频，查看了设备厂商的接口文档，不能直接获取到视频的直播流，需要我们自己实现一下。我们准备调用厂商提供的SDK将视频流拿到后推送给客户端，这中间需要搭建一个推流服务。<a href="https://github.com/ossrs/srs">SRS</a>是一个简单高效的实时视频服务器，支持RTMP/WebRTC/HLS/HTTP-FLV/SRT。</p><span id="more"></span><p>作为测试，我用Docker启动一个临时的容器</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 \    ossrs&#x2F;srs:3 .&#x2F;objs&#x2F;srs -c conf&#x2F;srs.conf<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></div></figure><p>1935是rtmp协议的默认端口，视频推流时需要使用这个端口。<br>8080是HTTP协议的接口。</p><p>启动成功之后，我使用OBS进行推流测试。</p><p><img src="https://img.yjll.site/img/20211112165712.png" alt="20211112165712"></p><p><img src="https://img.yjll.site/img/20211112170021.png" alt="20211112170021"></p><p>推流成功后，可以使用<code>VLC</code>进行测试。</p><p><img src="https://img.yjll.site/img/20211112165910.png" alt="20211112165910"></p><p>我这里使用的是<code>rtmp</code>协议获取数据，<code>SRS</code>也支持其他协议。</p><p><strong>HTTP-FLV</strong></p><p><a href="http://192.168.1.200:8080/live/key.flv">http://192.168.1.200:8080/live/key.flv</a></p><p><strong>HLS</strong></p><p><a href="http://192.168.1.200:8080/live/key.m3u8">http://192.168.1.200:8080/live/key.m3u8</a></p><p>流媒体协议 RTMP, HTTP-FLV, HLS 简单对比</p><p><img src="https://img.yjll.site/img/20211112170754.png" alt="20211112170754"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;想在web端播放实时监控视频，查看了设备厂商的接口文档，不能直接获取到视频的直播流，需要我们自己实现一下。我们准备调用厂商提供的SDK将视频流拿到后推送给客户端，这中间需要搭建一个推流服务。&lt;a href=&quot;https://github.com/ossrs/srs&quot;&gt;SRS&lt;/a&gt;是一个简单高效的实时视频服务器，支持RTMP/WebRTC/HLS/HTTP-FLV/SRT。&lt;/p&gt;</summary>
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="工具" scheme="https://blog.yjll.site/tags/%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>Python更新服务器SSH密钥</title>
    <link href="https://blog.yjll.site/post/65ba80c8.html"/>
    <id>https://blog.yjll.site/post/65ba80c8.html</id>
    <published>2021-11-05T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>出于安全考虑，需要更换服务器上的ssh密钥，但是重复一台一台服务器更改又太浪费时间，这种重复又浪费时间的工作就交给计算机吧。在网上查了查，<code>paramiko</code>正适合，看了看文档，不错，说干就干。</p><span id="more"></span><p>先安装<code>paramiko</code>包</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">pip install paramiko<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>密钥忘生成了，我这里使用git-bash带的命令进行生成，切记不要忘记备份原密钥。</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">ssh-keygen -o<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>下面是对应的脚本，<code>paramiko</code>使用起来还是比较优雅的，简单看看就能理解。</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> paramikoold_key <span class="token operator">=</span> <span class="token string">r'D:\tmp\id_rsa'</span>new_key <span class="token operator">=</span> <span class="token string">r'C:\Users\ROOT\.ssh\id_rsa'</span>new_pub_key <span class="token operator">=</span> <span class="token string">r'C:\Users\ROOT\.ssh\id_rsa.pub'</span><span class="token keyword">def</span> <span class="token function">ssh_key</span><span class="token punctuation">(</span>host<span class="token punctuation">:</span><span class="token builtin">str</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token comment"># 密钥</span>    pkey <span class="token operator">=</span> paramiko<span class="token punctuation">.</span>RSAKey<span class="token punctuation">.</span>from_private_key_file<span class="token punctuation">(</span>old_key<span class="token punctuation">)</span>    <span class="token comment"># 使用transport实现执行多个操作</span>    trans <span class="token operator">=</span> paramiko<span class="token punctuation">.</span>Transport<span class="token punctuation">(</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> <span class="token number">22</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    trans<span class="token punctuation">.</span>connect<span class="token punctuation">(</span>username<span class="token operator">=</span><span class="token string">'root'</span><span class="token punctuation">,</span> pkey<span class="token operator">=</span>pkey<span class="token punctuation">)</span>    ssh <span class="token operator">=</span> paramiko<span class="token punctuation">.</span>SSHClient<span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token comment"># 将sshclient的对象的transport指定为以上的trans</span>    ssh<span class="token punctuation">.</span>_transport <span class="token operator">=</span> trans    <span class="token comment"># 使用sftp上传文件</span>    sftp <span class="token operator">=</span> paramiko<span class="token punctuation">.</span>SFTPClient<span class="token punctuation">.</span>from_transport<span class="token punctuation">(</span>trans<span class="token punctuation">)</span>    sftp<span class="token punctuation">.</span>put<span class="token punctuation">(</span>localpath<span class="token operator">=</span>new_pub_key<span class="token punctuation">,</span> remotepath<span class="token operator">=</span><span class="token string">'/root/.ssh/authorized_keys'</span><span class="token punctuation">)</span>    sftp<span class="token punctuation">.</span>put<span class="token punctuation">(</span>localpath<span class="token operator">=</span>new_key<span class="token punctuation">,</span> remotepath<span class="token operator">=</span><span class="token string">'/root/.ssh/id_rsa'</span><span class="token punctuation">)</span>    stdin<span class="token punctuation">,</span> stdout<span class="token punctuation">,</span> stderr <span class="token operator">=</span> ssh<span class="token punctuation">.</span>exec_command<span class="token punctuation">(</span><span class="token string">'systemctl restart sshd'</span><span class="token punctuation">)</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>stdout<span class="token punctuation">.</span>read<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>decode<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    trans<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span>    ssh_key<span class="token punctuation">(</span><span class="token string">'192.168.1.200'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>脚本运行之后，试试能不能登录上，登不上去那就。。。</p><p><img src="https://img.yjll.site/img/20211105163601.png" alt=""></p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://www.cnblogs.com/wongbingming/articles/12384764.html">使用 Python ssh 远程登陆服务器的最佳方案</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;出于安全考虑，需要更换服务器上的ssh密钥，但是重复一台一台服务器更改又太浪费时间，这种重复又浪费时间的工作就交给计算机吧。在网上查了查，&lt;code&gt;paramiko&lt;/code&gt;正适合，看了看文档，不错，说干就干。&lt;/p&gt;</summary>
    
    
    
    <category term="Python" scheme="https://blog.yjll.site/categories/Python/"/>
    
    
  </entry>
  
  <entry>
    <title>Pandas计算最大连续间隔时间</title>
    <link href="https://blog.yjll.site/post/15886092.html"/>
    <id>https://blog.yjll.site/post/15886092.html</id>
    <published>2021-09-27T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.583Z</updated>
    
    <content type="html"><![CDATA[<p>日常统计中总会遇到需要统计和周期有关的数据，如用户连续登录天数，事件发生的连续性等。<br>样本数据是用户的ID和抽奖日期，准备计算出每个用户的连续抽奖最大间隔时间，我先说一下我的思路。先将根据用户ID将用户分组并按照日期排序，对相邻的日期进行比较，差异为1天的即为连续数据，对连续数据进行计数即可计算出最大的时间间隔。</p><span id="more"></span><p>数据样本</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> pandas <span class="token keyword">as</span> pddf <span class="token operator">=</span> pd<span class="token punctuation">.</span>read_csv<span class="token punctuation">(</span><span class="token string">r'tmp\draw.csv'</span><span class="token punctuation">)</span>df<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></div></figure><table><thead><tr><th></th><th>draw_date</th><th>weixin_id</th></tr></thead><tbody><tr><td>0</td><td>2021-05-25</td><td>751478029716439040</td></tr><tr><td>1</td><td>2021-05-28</td><td>751478029716439040</td></tr><tr><td>2</td><td>2021-06-08</td><td>751478029716439040</td></tr><tr><td>3</td><td>2021-06-09</td><td>751478029716439040</td></tr><tr><td>4</td><td>2021-06-15</td><td>639062248681259008</td></tr><tr><td>5</td><td>2021-06-16</td><td>639062248681259008</td></tr><tr><td>6</td><td>2021-06-17</td><td>639062248681259008</td></tr><tr><td>7</td><td>2021-06-18</td><td>639062248681259008</td></tr><tr><td>8</td><td>2021-06-19</td><td>639062248681259008</td></tr><tr><td>9</td><td>2021-06-21</td><td>639062248681259008</td></tr></tbody></table><p>样本中数据是字符串，我先将日期转成对应的格式</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python">df<span class="token punctuation">[</span><span class="token string">'draw_date'</span><span class="token punctuation">]</span> <span class="token operator">=</span> pd<span class="token punctuation">.</span>to_datetime<span class="token punctuation">(</span>df<span class="token punctuation">[</span><span class="token string">'draw_date'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>我这里使用<code>shift</code>将日期下移一位，再加上一天和相邻数据进行比较，目的是判断前后两条时间间隔是否相差1天，不相等记为True反之为False</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> datetime<span class="token keyword">for</span> name<span class="token punctuation">,</span>group <span class="token keyword">in</span> df<span class="token punctuation">.</span>groupby<span class="token punctuation">(</span><span class="token string">'weixin_id'</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    check <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>group<span class="token punctuation">[</span><span class="token string">'draw_date'</span><span class="token punctuation">]</span><span class="token punctuation">.</span>shift<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> group<span class="token punctuation">[</span><span class="token string">'draw_date'</span><span class="token punctuation">]</span><span class="token punctuation">)</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>check<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></div></figure><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">4     True5    False6    False7    False8    False9     TrueName: draw_date, dtype: bool0     True1     True2     True3    FalseName: draw_date, dtype: bool<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>用<code>cumsum</code>窗口函数，对上文提到的标记按照行累加，标记为False的情况不会计算</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">for</span> name<span class="token punctuation">,</span>group <span class="token keyword">in</span> df<span class="token punctuation">.</span>groupby<span class="token punctuation">(</span><span class="token string">'weixin_id'</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    check <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>group<span class="token punctuation">[</span><span class="token string">'draw_date'</span><span class="token punctuation">]</span><span class="token punctuation">.</span>shift<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> group<span class="token punctuation">[</span><span class="token string">'draw_date'</span><span class="token punctuation">]</span><span class="token punctuation">)</span>    cumsum <span class="token operator">=</span> check<span class="token punctuation">.</span>cumsum<span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>cumsum<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></div></figure><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">4    15    16    17    18    19    2Name: draw_date, dtype: int320    11    22    33    3Name: draw_date, dtype: int32<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>用<code>value_counts</code>对上文累计的数据进行分组计数，可以算出每段时间的间隔天数，按照我们的需求取最大的即可</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">for</span> name<span class="token punctuation">,</span>group <span class="token keyword">in</span> df<span class="token punctuation">.</span>groupby<span class="token punctuation">(</span><span class="token string">'weixin_id'</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    check <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>group<span class="token punctuation">[</span><span class="token string">'draw_date'</span><span class="token punctuation">]</span><span class="token punctuation">.</span>shift<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> group<span class="token punctuation">[</span><span class="token string">'draw_date'</span><span class="token punctuation">]</span><span class="token punctuation">)</span>    cumsum <span class="token operator">=</span> check<span class="token punctuation">.</span>cumsum<span class="token punctuation">(</span><span class="token punctuation">)</span>    value_counts <span class="token operator">=</span> cumsum<span class="token punctuation">.</span>value_counts<span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>value_counts<span class="token punctuation">)</span>    <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">1    52    1Name: draw_date, dtype: int643    22    11    1Name: draw_date, dtype: int64<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>上边的循环代码可以用<code>agg</code>替代</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> datetimedf<span class="token punctuation">.</span>groupby<span class="token punctuation">(</span><span class="token string">'weixin_id'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>agg<span class="token punctuation">(</span>    <span class="token keyword">lambda</span> group<span class="token punctuation">:</span>        <span class="token punctuation">(</span><span class="token punctuation">(</span>group<span class="token punctuation">.</span>shift<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> group<span class="token punctuation">)</span><span class="token punctuation">.</span>cumsum<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>value_counts<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token builtin">max</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><table><thead><tr><th></th><th>draw_date</th></tr></thead><tbody><tr><td>weixin_id</td><td></td></tr><tr><td>639062248681259008</td><td>5</td></tr><tr><td>751478029716439040</td><td>2</td></tr></tbody></table><p>前后数据比较也可直接使用<code>diff</code>函数，该函数可算出前后两条数据相减的结果</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python">df<span class="token punctuation">.</span>groupby<span class="token punctuation">(</span><span class="token string">'weixin_id'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>agg<span class="token punctuation">(</span>    <span class="token keyword">lambda</span> group <span class="token punctuation">:</span>        group<span class="token punctuation">.</span>diff<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token builtin">map</span><span class="token punctuation">(</span>            <span class="token keyword">lambda</span> d<span class="token punctuation">:</span>d<span class="token punctuation">.</span>days <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fillna<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>cumsum<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>value_counts<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token builtin">max</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><table><thead><tr><th></th><th>draw_date</th></tr></thead><tbody><tr><td>weixin_id</td><td></td></tr><tr><td>639062248681259008</td><td>5</td></tr><tr><td>751478029716439040</td><td>2</td></tr></tbody></table><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://blog.csdn.net/wwqnmdhmp/article/details/108927935">【pandas】计算最大连续间隔时间</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;日常统计中总会遇到需要统计和周期有关的数据，如用户连续登录天数，事件发生的连续性等。&lt;br&gt;样本数据是用户的ID和抽奖日期，准备计算出每个用户的连续抽奖最大间隔时间，我先说一下我的思路。先将根据用户ID将用户分组并按照日期排序，对相邻的日期进行比较，差异为1天的即为连续数据，对连续数据进行计数即可计算出最大的时间间隔。&lt;/p&gt;</summary>
    
    
    
    <category term="Python" scheme="https://blog.yjll.site/categories/Python/"/>
    
    
    <category term="Python" scheme="https://blog.yjll.site/tags/Python/"/>
    
    <category term="Pandas" scheme="https://blog.yjll.site/tags/Pandas/"/>
    
  </entry>
  
  <entry>
    <title>使用Logstash同步Mysql数据到Elasticsearch</title>
    <link href="https://blog.yjll.site/post/863f4886.html"/>
    <id>https://blog.yjll.site/post/863f4886.html</id>
    <published>2021-07-07T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.585Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://img.yjll.site/img/Logstash.png" alt="Logstash"><br>Logstash是一个开源数据收集引擎，具有近实时管道功能。Logstash可以动态地将来自不同数据源的数据统一起来，并将数据标准化到你所选择的目的地。</p><p>项目用到ES做统计，需要同步Mysql数据到ES中，我们使用Logstash进行同步，Logstash安装需要依赖的包比较多，我为了方便直接使用Docker进行操作。</p><span id="more"></span><h3 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h3><p>首先要编写同步数据的配置文件，从Mysql同步数据到ElasticSearch中，使用jdbc里连接数据库作为input，ouput为ES。</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">input &#123; stdin &#123; &#125;    jdbc &#123;        #注意mysql连接地址一定要用ip，不能使用localhost等        jdbc_connection_string &#x3D;&gt; &quot;jdbc:mysql:&#x2F;&#x2F;192.168.1.200:3308&#x2F;test&quot;        jdbc_user &#x3D;&gt; &quot;test&quot;        jdbc_password &#x3D;&gt; &quot;test&quot;        #这个jar包的地址是容器内的地址        jdbc_driver_library &#x3D;&gt; &quot;&#x2F;usr&#x2F;share&#x2F;logstash&#x2F;pipeline&#x2F;mysql-connector-java-8.0.11.jar&quot;        jdbc_driver_class &#x3D;&gt; &quot;com.mysql.jdbc.Driver&quot;        jdbc_paging_enabled &#x3D;&gt; &quot;true&quot;        jdbc_page_size &#x3D;&gt; &quot;5000&quot;        statement &#x3D;&gt; &quot;SELECT * FROM base_areas where id &gt; :sql_last_value&quot;        tracking_column &#x3D;&gt; id        use_column_value &#x3D;&gt; true        last_run_metadata_path &#x3D;&gt; &quot;&#x2F;usr&#x2F;share&#x2F;logstash&#x2F;pipeline&#x2F;area&quot;        schedule &#x3D;&gt; &quot;* * * * *&quot;    &#125; &#125;   output &#123;     stdout &#123;        codec &#x3D;&gt; json_lines    &#125;    elasticsearch &#123;        hosts &#x3D;&gt; &quot;192.168.1.200:9200&quot;        index &#x3D;&gt; &quot;area&quot;        document_type &#x3D;&gt; &quot;_doc&quot;        document_id &#x3D;&gt; &quot;%&#123;id&#125;&quot;    &#125;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>重点在于 <code>use_column_value</code> 和 <code>tracking_column</code> 这两个参数，当use_column_value为true时，可以用 :sql_last_value 这个变量来获取tracking_column对应的字段的最新值，默认即第一次启动时为 0 。我的示例中tracking_column对应id，即Logstash都会记录每次查询结果id的最大值，供下一次查询使用。</p><p>Logstash将tracking_column的最新值记录到<code>last_run_metadata_path</code>配置的文件，启动时读取该文件</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">docker run -it -v  $PWD&#x2F;pipeline&#x2F;:&#x2F;usr&#x2F;share&#x2F;logstash&#x2F;pipeline -v $PWD&#x2F;logstash.yml:&#x2F;usr&#x2F;share&#x2F;logstash&#x2F;config&#x2F;logstash.yml -v $PWD&#x2F;pipelines.yml:&#x2F;usr&#x2F;share&#x2F;logstash&#x2F;config&#x2F;pipelines.yml   logstash:6.4.3  bash<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>先安装jdbc和es的插件</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">bash-4.2$ ~&#x2F;bin&#x2F;logstash-plugin install logstash-input-jdbcValidating logstash-input-jdbcInstalling logstash-input-jdbcInstallation successfulbash-4.2$ ~&#x2F;bin&#x2F;logstash-plugin install logstash-output-elasticsearchValidating logstash-output-elasticsearchInstalling logstash-output-elasticsearchInstallation successful<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>由于是在容器内部连接服务，所以ES的连接不要使用localhost和127.0.0.1，更改为实际IP地址</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">bash-4.2$ cat ~&#x2F;config&#x2F;logstash.yml http.host: &quot;0.0.0.0&quot;xpack.monitoring.elasticsearch.url: http:&#x2F;&#x2F;192.168.1.200:9200<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="执行脚本"><a href="#执行脚本" class="headerlink" title="执行脚本"></a>执行脚本</h3><p>运行同步脚本可手动执行也可以配置调度执行</p><h4 id="手动执行"><a href="#手动执行" class="headerlink" title="手动执行"></a>手动执行</h4><p><code>~/bin/logstash -f ***.config</code></p><h4 id="调度执行"><a href="#调度执行" class="headerlink" title="调度执行"></a>调度执行</h4><p>调度任务可读取配置文件<code>config/pipelines.yml</code>，默认有一个<code>main</code>，<code>/usr/share/logstash/pipeline</code>为pipeline的存放路径，该文件夹内的pipeline都会被执行。<br>可手动更改该文件，添加<code>id</code>和<code>config</code>即可。</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">bash-4.2$ cat ~&#x2F;config&#x2F;pipelines.yml - pipeline.id: main  path.config: &quot;&#x2F;usr&#x2F;share&#x2F;logstash&#x2F;pipeline&quot;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>好，配置已完成，运行<code>/usr/local/bin/docker-entrypoint</code>启动调度任务</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p><code>logstash</code>是ES官方提供的工具，支持扩展多，配置灵活，但是只能同步增量数据，对于源数据删除和修改的情况，logstash无能为力，还有基于定时任务同步无法保证实时，对实时性要求高的场景要考虑其他方案。</p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://www.elastic.co/guide/en/logstash/current/docker-config.html#_pipeline_configuration">Configuring Logstash for Docker</a></p><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html">Jdbc input plugin</a></p><p><a href="https://doc.yonyoucloud.com/doc/logstash-best-practice-cn/index.html">Logstash 最佳实践</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;https://img.yjll.site/img/Logstash.png&quot; alt=&quot;Logstash&quot;&gt;&lt;br&gt;Logstash是一个开源数据收集引擎，具有近实时管道功能。Logstash可以动态地将来自不同数据源的数据统一起来，并将数据标准化到你所选择的目的地。&lt;/p&gt;
&lt;p&gt;项目用到ES做统计，需要同步Mysql数据到ES中，我们使用Logstash进行同步，Logstash安装需要依赖的包比较多，我为了方便直接使用Docker进行操作。&lt;/p&gt;</summary>
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="Elasticsearch" scheme="https://blog.yjll.site/tags/Elasticsearch/"/>
    
    <category term="Mysql" scheme="https://blog.yjll.site/tags/Mysql/"/>
    
    <category term="Logstash" scheme="https://blog.yjll.site/tags/Logstash/"/>
    
  </entry>
  
  <entry>
    <title>Canal实现Mysql至Elasticsearch实时同步</title>
    <link href="https://blog.yjll.site/post/15c08367.html"/>
    <id>https://blog.yjll.site/post/15c08367.html</id>
    <published>2021-06-29T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>canal 1.1.1版本之后, 内置增加客户端数据同步功能。我这里使用<a href="https://github.com/alibaba/canal/releases/download/canal-1.1.5/canal.adapter-1.1.5.tar.gz">canal.adapter-1.1.5.tar.gz</a>，该版本支持RabbitMQ，可从队列中取数据。</p><span id="more"></span><p>关于RabbitMQ配置和对接可以看我之前写的一篇<a href="/post/ab6a3b54.html" title="Canal推送Mysql增量数据至RabbitMQ">Canal推送Mysql增量数据至RabbitMQ</a>。</p><h3 id="对接RabbitMQ"><a href="#对接RabbitMQ" class="headerlink" title="对接RabbitMQ"></a>对接RabbitMQ</h3><p>编辑<code>adapter/conf/application.yml</code>,配置rabbitMQ信息</p><figure><div class="code-wrapper"><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">canal.conf</span><span class="token punctuation">:</span>  <span class="token key atrule">mode</span><span class="token punctuation">:</span> rabbitMQ <span class="token comment">#tcp kafka rocketMQ rabbitMQ</span>  <span class="token key atrule">consumerProperties</span><span class="token punctuation">:</span>    <span class="token comment"># rabbitMQ consumer</span>    <span class="token key atrule">rabbitmq.host</span><span class="token punctuation">:</span> 127.0.0.1    <span class="token key atrule">rabbitmq.virtual.host</span><span class="token punctuation">:</span> /    <span class="token key atrule">rabbitmq.username</span><span class="token punctuation">:</span> root    <span class="token key atrule">rabbitmq.password</span><span class="token punctuation">:</span> root    <span class="token key atrule">rabbitmq.resource.ownerId</span><span class="token punctuation">:</span>  <span class="token key atrule">srcDataSources</span><span class="token punctuation">:</span>    <span class="token key atrule">defaultDS</span><span class="token punctuation">:</span>      <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//remote<span class="token punctuation">:</span>3306/test<span class="token punctuation">?</span>useUnicode=true      <span class="token key atrule">username</span><span class="token punctuation">:</span> root      <span class="token key atrule">password</span><span class="token punctuation">:</span> root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="Elasticsearch配置"><a href="#Elasticsearch配置" class="headerlink" title="Elasticsearch配置"></a>Elasticsearch配置</h3><p>编辑<code>adapter/conf/application.yml</code>,配置canalAdapters同步es6，队列使用<code>canal.es</code></p><figure><div class="code-wrapper"><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">canalAdapters</span><span class="token punctuation">:</span>  <span class="token punctuation">-</span> <span class="token key atrule">instance</span><span class="token punctuation">:</span> canal.es <span class="token comment"># canal instance Name or mq topic name</span>    <span class="token key atrule">groups</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token key atrule">groupId</span><span class="token punctuation">:</span> g1        <span class="token key atrule">outerAdapters</span><span class="token punctuation">:</span>          <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> es6            <span class="token key atrule">hosts</span><span class="token punctuation">:</span> 127.0.0.1<span class="token punctuation">:</span><span class="token number">9200</span> <span class="token comment"># 127.0.0.1:9200 for rest mode</span>            <span class="token key atrule">properties</span><span class="token punctuation">:</span>              <span class="token key atrule">mode</span><span class="token punctuation">:</span> rest <span class="token comment"># or rest</span>              <span class="token comment"># security.auth: test:123456 #  only used for rest mode</span>              <span class="token key atrule">cluster.name</span><span class="token punctuation">:</span> elasticsearch<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>canal会扫描<code>adapter/conf/es6/</code>下的所有配置文件，其中信息要和<code>adapter/conf/application.yml</code>匹配</p><p>cat ./adapter/conf/es6/scan_log.yml</p><figure><div class="code-wrapper"><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">dataSourceKey</span><span class="token punctuation">:</span> defaultDS<span class="token key atrule">destination</span><span class="token punctuation">:</span> canal.es<span class="token key atrule">groupId</span><span class="token punctuation">:</span> g1<span class="token key atrule">esMapping</span><span class="token punctuation">:</span>  <span class="token key atrule">_index</span><span class="token punctuation">:</span> scan_log  <span class="token key atrule">_type</span><span class="token punctuation">:</span> _doc  <span class="token key atrule">_id</span><span class="token punctuation">:</span> id  <span class="token key atrule">sql</span><span class="token punctuation">:</span> <span class="token string">"select id, code, casecode from scan_log"</span>  <span class="token key atrule">commitBatch</span><span class="token punctuation">:</span> <span class="token number">3000</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="建立ElasticSearchmapping"><a href="#建立ElasticSearchmapping" class="headerlink" title="建立ElasticSearchmapping"></a>建立ElasticSearchmapping</h3><p>新版ES提倡使用<code>_doc</code>作为<code>_type</code>，我们这里使用Mysql表名作为ES的index，建立mapping</p><figure><div class="code-wrapper"><pre class="line-numbers language-json" data-language="json"><code class="language-json">PUT /scan_log <span class="token punctuation">&#123;</span>    <span class="token property">"mappings"</span><span class="token operator">:</span><span class="token punctuation">&#123;</span>        <span class="token property">"_doc"</span><span class="token operator">:</span><span class="token punctuation">&#123;</span>            <span class="token property">"properties"</span><span class="token operator">:</span><span class="token punctuation">&#123;</span>                <span class="token property">"code"</span><span class="token operator">:</span><span class="token punctuation">&#123;</span>                    <span class="token property">"type"</span><span class="token operator">:</span><span class="token string">"long"</span>                <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>                <span class="token property">"casecode"</span> <span class="token operator">:</span> <span class="token punctuation">&#123;</span>                    <span class="token property">"type"</span> <span class="token operator">:</span> <span class="token string">"text"</span><span class="token punctuation">,</span>                    <span class="token property">"fields"</span> <span class="token operator">:</span> <span class="token punctuation">&#123;</span>                    <span class="token property">"keyword"</span> <span class="token operator">:</span> <span class="token punctuation">&#123;</span>                        <span class="token property">"type"</span> <span class="token operator">:</span> <span class="token string">"keyword"</span><span class="token punctuation">,</span>                        <span class="token property">"ignore_above"</span> <span class="token operator">:</span> <span class="token number">256</span>                      <span class="token punctuation">&#125;</span>                    <span class="token punctuation">&#125;</span>                <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>我们也可以通过Logstash帮我们自动创建mapping,Logstash可以根据json格式创建mapping，关于Logstash可以看我之前写的文章<a href="/post/863f4886.html" title="使用Logstash同步Mysql数据到Elasticsearch">使用Logstash同步Mysql数据到Elasticsearch</a></p><p>使用generator方式生成json</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">cat logstash-sample.conf input &#123;   generator &#123;    count &#x3D;&gt; 1    message &#x3D;&gt; &#39;    &#123;        &quot;id&quot;: 628247969266810880,        &quot;code&quot;: 2199023788471,        &quot;casecode&quot;: &quot;999STZPMDDV&quot;    &#125;&#39;    codec &#x3D;&gt; json  &#125;&#125;output &#123;  elasticsearch &#123;    hosts &#x3D;&gt; [&quot;http:&#x2F;&#x2F;localhost:9200&quot;]    index &#x3D;&gt; &quot;scan_log&quot;    document_type &#x3D;&gt; &quot;_doc&quot;    document_id &#x3D;&gt; &quot;%&#123;id&#125;&quot;  &#125;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>mapping创建完成后，我们启动项目，日志中发现这个错误</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">Load canal adapter: es6 failed<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>出现问题的不只我一个，<a href="https://github.com/alibaba/canal/issues/3466">#3466</a>中有人提出解决方案，使用<a href="https://github.com/alibaba/canal/releases/download/canal-1.1.5-alpha-2/canal.adapter-1.1.5-SNAPSHOT.tar.gz">canal.adapter-1.1.5-SNAPSHOT.tar.gz</a>版本中的<code>client-adapter.es6x-1.1.5-SNAPSHOT-jar-with-dependencies.jar</code>替换现版本中的<code>client-adapter.es6x-1.1.5-jar-with-dependencies.jar</code>，完美解决。</p><h3 id="同步全量数据"><a href="#同步全量数据" class="headerlink" title="同步全量数据"></a>同步全量数据</h3><p>adapter支持通过REST接口的方式导入全量数据</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none"># 先暂停同步curl http:&#x2F;&#x2F;127.0.0.1:8081&#x2F;syncSwitch&#x2F;canal.escurl http:&#x2F;&#x2F;127.0.0.1:8081&#x2F;syncSwitch&#x2F;canal.es&#x2F;off -X PUT# 全量导入curl http:&#x2F;&#x2F;127.0.0.1:8081&#x2F;etl&#x2F;es6&#x2F;scan_log.yml -X POST# 恢复同步curl http:&#x2F;&#x2F;127.0.0.1:8081&#x2F;syncSwitch&#x2F;canal.es&#x2F;on -X PUT<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>路径中的<code>scan_log.yml</code>为<code>adapter/conf/es6/</code>下的配置</p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://github.com/alibaba/canal/wiki/Sync-ES">ElasticSearch适配器</a></p><p><a href="https://github.com/alibaba/canal/wiki/ClientAdapter">ClientAdapter</a></p><p><a href="https://jasonkayzk.github.io/2019/10/03/ElasticSearch%E4%B8%BA%E4%BB%80%E4%B9%88%E5%9C%A8%E9%AB%98%E7%89%88%E6%9C%AC%E7%A7%BB%E9%99%A4%E6%98%A0%E5%B0%84%E7%B1%BB%E5%9E%8B/">ElasticSearch为什么在高版本移除映射类型</a></p><p><a href="https://www.alibabacloud.com/help/zh/doc-detail/135297.htm">通过Canal将MySQL数据同步到阿里云Elasticsearch</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;canal 1.1.1版本之后, 内置增加客户端数据同步功能。我这里使用&lt;a href=&quot;https://github.com/alibaba/canal/releases/download/canal-1.1.5/canal.adapter-1.1.5.tar.gz&quot;&gt;canal.adapter-1.1.5.tar.gz&lt;/a&gt;，该版本支持RabbitMQ，可从队列中取数据。&lt;/p&gt;</summary>
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="Elasticsearch" scheme="https://blog.yjll.site/tags/Elasticsearch/"/>
    
    <category term="Mysql" scheme="https://blog.yjll.site/tags/Mysql/"/>
    
    <category term="Canal" scheme="https://blog.yjll.site/tags/Canal/"/>
    
  </entry>
  
  <entry>
    <title>Canal实现Mysql至ClickHouse实时同步</title>
    <link href="https://blog.yjll.site/post/abdd8675.html"/>
    <id>https://blog.yjll.site/post/abdd8675.html</id>
    <published>2021-06-29T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>前面两篇文章(<a href="/post/ab6a3b54.html" title="Canal推送Mysql增量数据至RabbitMQ">Canal推送Mysql增量数据至RabbitMQ</a> <a href="/post/15c08367.html" title="Canal实现Mysql至Elasticsearch实时同步">Canal实现Mysql至Elasticsearch实时同步</a>)介绍了如何搭建Canal并且使用Canal将Mysql的解析后的数据投放到MQ中，和从MQ中同步数据到Elasticsearch中，这篇文章主要介绍如何同步数据到关系型数据库中。</p><span id="more"></span><h3 id="ClickHouse"><a href="#ClickHouse" class="headerlink" title="ClickHouse"></a>ClickHouse</h3><p>Canal提供了对关系型数据库的支持，虽然ClickHouse不是关系型数据库，但是可以兼容部分SQL语句(select,insert等)，我很可以对增量数据进行同步。由于提供的包内不包含ClickHouse的JDBC驱动，我们去下载必要的jar包(<code>lz4-1.3.0.jar</code>,<code>clickhouse-jdbc-0.2.4.jar</code>,<code>httpclient-4.3.3.jar</code>,<code>httpcore-4.4.13.jar</code>)并放入<code>canal/adapter/lib</code>内。</p><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>我将数据分别同步到Mysql和ClickHouse中，防止相互干扰我这里使用了两个队列，两个instance分别从不同的队列中消费消息。</p><p>编辑 <code>adapter/conf/application.yml</code></p><figure><div class="code-wrapper"><pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">canalAdapters</span><span class="token punctuation">:</span>  <span class="token punctuation">-</span> <span class="token key atrule">instance</span><span class="token punctuation">:</span> canal.mysql <span class="token comment"># canal instance Name or mq topic name</span>    <span class="token key atrule">groups</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token key atrule">groupId</span><span class="token punctuation">:</span> g1        <span class="token key atrule">outerAdapters</span><span class="token punctuation">:</span>          <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> rdb            <span class="token key atrule">key</span><span class="token punctuation">:</span> mysql1            <span class="token key atrule">properties</span><span class="token punctuation">:</span>              <span class="token key atrule">jdbc.driverClassName</span><span class="token punctuation">:</span> com.mysql.jdbc.Driver              <span class="token key atrule">jdbc.url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/test<span class="token punctuation">?</span>useUnicode=true<span class="token important">&amp;serverTimezone=Asia/Shanghai</span>              <span class="token key atrule">jdbc.username</span><span class="token punctuation">:</span> root              <span class="token key atrule">jdbc.password</span><span class="token punctuation">:</span> root  <span class="token punctuation">-</span> <span class="token key atrule">instance</span><span class="token punctuation">:</span> canal.clickhouse <span class="token comment"># canal instance Name or mq topic name</span>    <span class="token key atrule">groups</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token key atrule">groupId</span><span class="token punctuation">:</span> g1        <span class="token key atrule">outerAdapters</span><span class="token punctuation">:</span>          <span class="token comment">#- name: logger</span>          <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> rdb            <span class="token key atrule">key</span><span class="token punctuation">:</span> clickhouse            <span class="token key atrule">properties</span><span class="token punctuation">:</span>              <span class="token key atrule">jdbc.driverClassName</span><span class="token punctuation">:</span> ru.yandex.clickhouse.ClickHouseDriver              <span class="token key atrule">jdbc.url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>clickhouse<span class="token punctuation">:</span>//127.0.0.1<span class="token punctuation">:</span>8123/pro              <span class="token key atrule">jdbc.username</span><span class="token punctuation">:</span>              <span class="token key atrule">jdbc.password</span><span class="token punctuation">:</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>编辑MySQL的配置文件 <code>adapter/conf/rdb/scan_log.yml</code></p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">dataSourceKey: defaultDSdestination: canal.mysqlgroupId: g1outerAdapterKey: mysql1concurrent: truedbMapping:  database: pro  table: scan_log  targetTable: scan_log  targetPk:    id: id  mapAll: true<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>编辑ClickHouse的配置文件 <code>adapter/conf/rdb/ch_scan_log.yml</code></p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">dataSourceKey: defaultDSdestination: canal.clickhousegroupId: g1outerAdapterKey: clickhouseconcurrent: truedbMapping:  database: pro  table: scan_log   targetTable: scan_log  targetPk:    id: id    mapAll: true<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="同步报错"><a href="#同步报错" class="headerlink" title="同步报错"></a>同步报错</h3><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">datax Unknown system variable &#39;tx_read_only&#39;The connection property &#39;zeroDateTimeBehavior&#39; acceptable values are: &#39;CONVERT_TO_NULL&#39;, &#39;EXCEPTION&#39; or &#39;ROUND&#39;. The value &#39;convertToNull&#39; is not acceptable.<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></div></figure><p>由于我使用的是Mysql8，需要更新mysql驱动<code>8.0.13</code>以上版本。</p><p>下载<a href="https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.22/mysql-connector-java-8.0.22.jar">mysql-connector-java-8.0.22.jar</a>放入lib中即可</p><p>还有同步clickhouse时，update和delete语句会报错，因为clickhouse的语法并不是完全兼容关系型数据库，我这里只对增量表进行了同步。</p><h3 id="全量同步"><a href="#全量同步" class="headerlink" title="全量同步"></a>全量同步</h3><p>Mysql通过REST接口的方式导入全量数据</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">curl http:&#x2F;&#x2F;127.0.0.1:8081&#x2F;etl&#x2F;rdb&#x2F;mysql1&#x2F;scan_log.yml<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>由于语法兼容性问题ClickHouse不能使用这种方式，同步全量数据我使用的是<a href="https://github.com/alibaba/DataX">DataX</a>，细节方面可以参考<a href="/post/6a3f5dff.html" title="DataX同步全量Mqsql数据到Elasticsearch">DataX同步全量Mqsql数据到Elasticsearch</a>这篇。</p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://segmentfault.com/a/1190000021512811">clickhouse修改用户密码</a></p><p><a href="https://github.com/alibaba/canal/wiki/Sync-RDB">RDB适配器</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前面两篇文章(&lt;a href=&quot;/post/ab6a3b54.html&quot; title=&quot;Canal推送Mysql增量数据至RabbitMQ&quot;&gt;Canal推送Mysql增量数据至RabbitMQ&lt;/a&gt; &lt;a href=&quot;/post/15c08367.html&quot; title=&quot;Canal实现Mysql至Elasticsearch实时同步&quot;&gt;Canal实现Mysql至Elasticsearch实时同步&lt;/a&gt;)介绍了如何搭建Canal并且使用Canal将Mysql的解析后的数据投放到MQ中，和从MQ中同步数据到Elasticsearch中，这篇文章主要介绍如何同步数据到关系型数据库中。&lt;/p&gt;</summary>
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="Mysql" scheme="https://blog.yjll.site/tags/Mysql/"/>
    
    <category term="Canal" scheme="https://blog.yjll.site/tags/Canal/"/>
    
    <category term="ClickHouse" scheme="https://blog.yjll.site/tags/ClickHouse/"/>
    
  </entry>
  
  <entry>
    <title>Canal推送Mysql增量数据至RabbitMQ</title>
    <link href="https://blog.yjll.site/post/ab6a3b54.html"/>
    <id>https://blog.yjll.site/post/ab6a3b54.html</id>
    <published>2021-06-29T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>canal [kə’næl]，译意为水道/管道/沟渠，主要用途是基于 MySQL 数据库增量日志解析，提供增量数据订阅和消费</p><p><img src="https://img.yjll.site/img/Canal.png" alt="Canal"></p><span id="more"></span><p>工作原理</p><ul><li>canal 模拟 MySQL slave 的交互协议，伪装自己为 MySQL slave ，向 MySQL master 发送 dump 协议</li><li>MySQL master 收到 dump 请求，开始推送 binary log 给 slave (即 canal )</li><li>canal 解析 binary log 对象(原始为 byte 流)</li></ul><p>从<a href="https://github.com/alibaba/canal/releases/tag/canal-1.1.5">v1.1.5</a>版本开始，Canal新增对RabbitMQ的支持。</p><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><!-- log-bin=mysql-bin 　　# 开启 binlogbinlog-format=ROW 　　# 选择 ROW 模式server_id=1 　　　　   # 配置 MySQL replaction 需要定义，不要和 canal 的 slaveId 重复 --><p><code>canal</code>需要Mysql端开启二进制日志同步，我使用的阿里云的Mysql，默认已开启<code>binlog dump</code>。</p><p>RabbitMQ我使用Docker部署，注意镜像要使用带<code>management</code>版本的，方便在界面操作。</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">docker run --name rabbitmq -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER&#x3D;root -e RABBITMQ_DEFAULT_PASS&#x3D;root   rabbitmq:3.8.2-management<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>RabbitMQ的<code>Exchange</code>,<code>Queue</code>,<code>Binding</code>也要优先配置好，我的需求是canal解析完Mysql的binlog后，将信息分别推送到3个队列中，我这里为了隔离环境建3个队列，分别是同步Elasticsearch的<code>canal.es</code>，同步ClickHouse的<code>canal.clickhouse</code>和同步Mysql的<code>canal.mysql</code>。因为是无差别投放，我只创建了一个交换机<code>canal</code>。将3个队列<code>canal.es</code>、<code>canal.clickhouse</code>、<code>canal.mysql</code>绑定到交换机<code>canal</code>上。</p><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>编辑<code>deployer/conf/example/instance.properties</code></p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none"># 我这里使用了gtid日志进行同步canal.instance.gtidon&#x3D;truecanal.instance.master.address&#x3D;localhost:3306canal.instance.dbUsername&#x3D;rootcanal.instance.dbPassword&#x3D;root# 这里配置发送消息的主题，由于3队列接收全部数据，所以此处主题我没有用到，使用的默认值canal.mq.topic&#x3D;example<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>编辑<code>deployer/conf/canal.properties</code></p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none"># 此处更改为rabbitmq模式canal.serverMode &#x3D; rabbitmqrabbitmq.host &#x3D; 127.0.0.1rabbitmq.virtual.host &#x3D; &#x2F;rabbitmq.exchange &#x3D; canalrabbitmq.username &#x3D; rootrabbitmq.password &#x3D; root# 消息持久化rabbitmq.deliveryMode &#x3D; 2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>运行 <code>bin/startup.sh</code> ，服务便开始运行了</p><h3 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h3><p>由于三个队列需要同步的数据库不同，无差别投放会造成不必要的性能浪费，可开启<code>dynamicTopic</code>功能，根据topic将数据投放至不同的队列。</p><p>编辑<code>deployer/conf/example/instance.properties</code></p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">canal.mq.dynamicTopic&#x3D;pro\\..*<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>投递的topic为<code>库名_表名</code></p><p>Exchanges和Queues的绑定关系在RebbitMQ的后台配置即可，模式选择Topic模式。</p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://github.com/alibaba/canal/wiki/QuickStart">alibaba/canal</a></p><p><a href="https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart">Canal Kafka RocketMQ QuickStart</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;canal [kə’næl]，译意为水道/管道/沟渠，主要用途是基于 MySQL 数据库增量日志解析，提供增量数据订阅和消费&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.yjll.site/img/Canal.png&quot; alt=&quot;Canal&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="Mysql" scheme="https://blog.yjll.site/tags/Mysql/"/>
    
    <category term="Canal" scheme="https://blog.yjll.site/tags/Canal/"/>
    
    <category term="RabbitMQ" scheme="https://blog.yjll.site/tags/RabbitMQ/"/>
    
  </entry>
  
  <entry>
    <title>Xpath语法</title>
    <link href="https://blog.yjll.site/post/d16b66c8.html"/>
    <id>https://blog.yjll.site/post/d16b66c8.html</id>
    <published>2021-04-27T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p><strong>XPath</strong>即为<a href="https://zh.wikipedia.org/wiki/XML">XML</a>路径语言（XML Path Language），它是一种用来确定XML文档中某部分位置的计算机语言。</p><p>XPath基于XML的树状结构，提供在数据结构树中找寻节点的能力。起初XPath的提出的初衷是将其作为一个通用的、介于XPointer与XSL间的语法模型。但是XPath很快的被开发者采用来当作小型查询语言。</p><h3 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h3><table><thead><tr><th>表达式</th><th>描述</th></tr></thead><tbody><tr><td>nodename</td><td>选取此节点的所有子节点</td></tr><tr><td>/</td><td>从根节点选取</td></tr><tr><td>//</td><td>从匹配选择的当前节点选择文档中的节点，而不考虑它们的位置</td></tr><tr><td>.</td><td>选取当前节点</td></tr><tr><td>..</td><td>选取当前节点的父节点</td></tr><tr><td>@</td><td>选取属性</td></tr></tbody></table><h3 id="Python"><a href="#Python" class="headerlink" title="Python"></a>Python</h3><p>python使用<code>lxml</code>进行解析</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> lxml <span class="token keyword">import</span> etreehtml<span class="token operator">=</span>etree<span class="token punctuation">.</span>parse<span class="token punctuation">(</span><span class="token string">'d:/github.html'</span><span class="token punctuation">,</span>etree<span class="token punctuation">.</span>HTMLParser<span class="token punctuation">(</span>encoding<span class="token operator">=</span><span class="token string">"utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>result <span class="token operator">=</span> html<span class="token punctuation">.</span>xpath<span class="token punctuation">(</span><span class="token triple-quoted-string string">'''//*[@id="org-repositories"]/div[1]/div/ul/li[1]/div[1]/div[1]/h3/a/@href'''</span><span class="token punctuation">)</span><span class="token keyword">for</span> row <span class="token keyword">in</span> result<span class="token punctuation">:</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>row<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p><code>scrapy</code>框架也支持xpath解析</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python">proxy <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token string">'http'</span><span class="token punctuation">:</span> <span class="token string">'http://dev:7890'</span><span class="token punctuation">,</span><span class="token string">'https'</span><span class="token punctuation">:</span> <span class="token string">'http://dev:7890'</span><span class="token punctuation">&#125;</span>resp <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>base_url<span class="token punctuation">,</span>verify<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">,</span>proxies<span class="token operator">=</span>proxy<span class="token punctuation">)</span><span class="token comment"># %%</span><span class="token keyword">print</span><span class="token punctuation">(</span>resp<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span class="token comment"># %%</span><span class="token keyword">from</span> scrapy<span class="token punctuation">.</span>selector <span class="token keyword">import</span> Selector<span class="token keyword">from</span> scrapy<span class="token punctuation">.</span>http <span class="token keyword">import</span> HtmlResponseresponse <span class="token operator">=</span> Selector<span class="token punctuation">(</span>text<span class="token operator">=</span>resp<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span class="token keyword">for</span> elem <span class="token keyword">in</span> response<span class="token punctuation">.</span>xpath<span class="token punctuation">(</span><span class="token string">'//*[@id="main"]/article'</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    title <span class="token operator">=</span> elem<span class="token punctuation">.</span>xpath<span class="token punctuation">(</span><span class="token string">'string(./header/h1/a)'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>extract_first<span class="token punctuation">(</span><span class="token punctuation">)</span>    url <span class="token operator">=</span> elem<span class="token punctuation">.</span>xpath<span class="token punctuation">(</span><span class="token string">'./header/h1/a/@href'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>extract_first<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token builtin">next</span> <span class="token operator">=</span> response<span class="token punctuation">.</span>xpath<span class="token punctuation">(</span><span class="token string">'//a[@rel="next"]/@href'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>extract_first<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h3><p>Java可以使用<code>htmlcleaner</code>解析HTML</p><figure><div class="code-wrapper"><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>net.sourceforge.htmlcleaner<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>htmlcleaner<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.6.1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>我这里代码使用Groovy写的，和Java写法有点差异</p><figure><div class="code-wrapper"><pre class="line-numbers language-groovy" data-language="groovy"><code class="language-groovy">HtmlCleaner hc <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HtmlCleaner</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">def</span> node <span class="token operator">=</span> hc<span class="token punctuation">.</span><span class="token function">clean</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">File</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">"d:/github.html"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span>println node<span class="token punctuation">.</span><span class="token function">getElementsByName</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">"title"</span></span><span class="token punctuation">,</span><span class="token boolean">true</span><span class="token punctuation">)</span>println node<span class="token punctuation">.</span><span class="token function">evaluateXPath</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">"//*[@id=\"org-repositories\"]/div[1]/div/ul/li[*]/div[1]/div[1]/h3/a/@href"</span></span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="获取xpath"><a href="#获取xpath" class="headerlink" title="获取xpath"></a>获取xpath</h3><p>我们可以通过Chrome的开发这工具来获取xpath，但这种方式获取的xpath不是很友好，只是单纯的将所有的路径都显示出来。</p><p><img src="https://img.yjll.site/img/Chrome_xpath.png" alt="Chrome_xpath"></p><p>可以安装<br><a href="https://github.com/fy0/EasySelect">EasySelect</a>这个Chrome扩展，可以很方便取到我们想要的数据。</p><p><img src="https://raw.githubusercontent.com/fy0/EasySelect/master/misc/example2.png" alt="example2"></p><h3 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h3><p>再安利一个小工具，可根据curl命令生成代码。<br><a href="https://curl.trillworks.com/">Convert curl syntax to Python</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;strong&gt;XPath&lt;/strong&gt;即为&lt;a href=&quot;https://zh.wikipedia.org/wiki/XML&quot;&gt;XML&lt;/a&gt;路径语言（XML Path Language），它是一种用来确定XML文档中某部分位置的计算机语言。&lt;/p&gt;
&lt;p&gt;XPat</summary>
      
    
    
    
    <category term="Python" scheme="https://blog.yjll.site/categories/Python/"/>
    
    
    <category term="Python" scheme="https://blog.yjll.site/tags/Python/"/>
    
    <category term="Xpath" scheme="https://blog.yjll.site/tags/Xpath/"/>
    
    <category term="Java" scheme="https://blog.yjll.site/tags/Java/"/>
    
    <category term="爬虫" scheme="https://blog.yjll.site/tags/%E7%88%AC%E8%99%AB/"/>
    
  </entry>
  
  <entry>
    <title>ZooKeeper在实际开发中的应用-事件监听</title>
    <link href="https://blog.yjll.site/post/bf85b35c.html"/>
    <id>https://blog.yjll.site/post/bf85b35c.html</id>
    <published>2021-04-25T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<h3 id="事件监听"><a href="#事件监听" class="headerlink" title="事件监听"></a>事件监听</h3><p><a href="https://curator.apache.org/">Apache Curator</a>是Netflix公司开源的一个Zookeeper客户端，与Zookeeper提供的原生客户端相比，Curator的抽象层次更高，简化了Zookeeper客户端的开发量。Patrixck Hunt（Zookeeper committer）以一句 <strong>Guava is to Java what Curator is to Zookeeper</strong> 给予高度评价。</p><h4 id="Watcher-标准事件处理器"><a href="#Watcher-标准事件处理器" class="headerlink" title="Watcher 标准事件处理器"></a>Watcher 标准事件处理器</h4><figure><div class="code-wrapper"><pre class="line-numbers language-groovy" data-language="groovy"><code class="language-groovy">Watcher watcher <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Watcher</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">process</span><span class="token punctuation">(</span>WatchedEvent watchedEvent<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">"监听到的变化 watchedEvent = "</span></span> <span class="token operator">+</span> watchedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> content <span class="token operator">=</span> curatorFramework<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token punctuation">.</span><span class="token function">usingWatcher</span><span class="token punctuation">(</span>watcher<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">'/path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">"监听节点内容："</span></span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 变更节点数据</span>curatorFramework<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span>workerPath<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">"Change"</span></span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>Watcher监听器是一次性的，可以在事件触发之后重复订阅</p><figure><div class="code-wrapper"><pre class="line-numbers language-groovy" data-language="groovy"><code class="language-groovy">Watcher watcher <span class="token operator">=</span> null<span class="token punctuation">;</span>watcher <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Watcher</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">process</span><span class="token punctuation">(</span>WatchedEvent watchedEvent<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        curatorFramework<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">usingWatcher</span><span class="token punctuation">(</span>watcher<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">'/path'</span><span class="token punctuation">)</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">"监听到的变化 watchedEvent = "</span></span> <span class="token operator">+</span> watchedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h4 id="Curator引入Cache实现监听"><a href="#Curator引入Cache实现监听" class="headerlink" title="Curator引入Cache实现监听"></a>Curator引入Cache实现监听</h4><p>Curator引入了Cache来方便我们订阅事件。</p><ul><li>NodeCache  监控本节点的新增，删除，更新</li><li>PathChildrenCache 不会对二级子节点进行监听，只会对子节点进行监听</li><li>TreeCache 监听指定节点下所有节点的变化</li></ul><p>我这里用<code>NodeCache</code>举例子</p><figure><div class="code-wrapper"><pre class="line-numbers language-groovy" data-language="groovy"><code class="language-groovy">NodeCache nodeCache <span class="token operator">=</span>        <span class="token keyword">new</span> <span class="token class-name">NodeCache</span><span class="token punctuation">(</span>curatorFramework<span class="token punctuation">,</span> workerPath<span class="token punctuation">)</span><span class="token punctuation">;</span>nodeCache<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>NodeCacheListener listener <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NodeCacheListener</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">nodeChanged</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">&#123;</span>        ChildData childData <span class="token operator">=</span> nodeCache<span class="token punctuation">.</span><span class="token function">getCurrentData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">"ZNode Change, data=&#123;&#125;"</span></span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>childData<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">"Utf-8"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>nodeCache<span class="token punctuation">.</span><span class="token function">getListenable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addListener</span><span class="token punctuation">(</span>listener<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 变更节点数据</span>curatorFramework<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span>workerPath<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">"Change1"</span></span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000L</span><span class="token punctuation">)</span>curatorFramework<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span>workerPath<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">"Change2"</span></span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000L</span><span class="token punctuation">)</span>curatorFramework<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span>workerPath<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">"Change3"</span></span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">10_000L</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h3><p>我们项目使用微服务技术对原有服务进行拆分，将接口分散到不同服务中，并将网关独立出来。服务端一部分接口需要登录才可以访问，网关需要对这部分接口和TOKEN进行校验。但是我们对需登录的接口的标记是基于注解的，方便切面拦截。拆分服务后网关是不知道哪些接口是需要校验的，最后我们借助了ZK来实现，下游服务发布时扫描代码中的接口，将需要登录的接口set到ZK中，网关启动时从ZK节点中取数据保存到内存中并订阅该节点，每次下游服务发布时触发事件，更新网关中的数据。</p><h4 id="下游服务实现"><a href="#下游服务实现" class="headerlink" title="下游服务实现"></a>下游服务实现</h4><h4 id="网关实现"><a href="#网关实现" class="headerlink" title="网关实现"></a>网关实现</h4><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://www.cnblogs.com/crazymakercircle/p/10228385.html">Zookeeper Curator 事件监听 </a></p><p><a href="https://zhouj000.github.io/2019/02/06/zookeeper-02/">zookeeper(二) Java API</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;事件监听&quot;&gt;&lt;a href=&quot;#事件监听&quot; class=&quot;headerlink&quot; title=&quot;事件监听&quot;&gt;&lt;/a&gt;事件监听&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://curator.apache.org/&quot;&gt;Apache Curator&lt;/a&gt;是Netfl</summary>
      
    
    
    
    <category term="应用" scheme="https://blog.yjll.site/categories/%E5%BA%94%E7%94%A8/"/>
    
    
    <category term="Java" scheme="https://blog.yjll.site/tags/Java/"/>
    
    <category term="中间件" scheme="https://blog.yjll.site/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    <category term="ZooKeeper" scheme="https://blog.yjll.site/tags/ZooKeeper/"/>
    
  </entry>
  
  <entry>
    <title>使用Docker部署Python项目</title>
    <link href="https://blog.yjll.site/post/ee11952c.html"/>
    <id>https://blog.yjll.site/post/ee11952c.html</id>
    <published>2021-01-25T16:54:23.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>日常开发Python，环境是一个让人头疼的问题，首先<code>Python2</code>和<code>Python3</code>很多语法不兼容，开发环境难免安装多个版本的Python，即使使用同一个版本Python不同项目使用不同版本的第三方包也会导致各种问题，所以开发Python都会使用虚拟环境，不同项目使用不同的环境，其实我们也可以使用Docker将不同的环境进行区分。</p><span id="more"></span><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">.├── app.py├── Dockerfile|── .gitignore└── requirements.txt<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>这是一个简单的python项目目录，只有一个<code>app.py</code>，运行一个基于Flask编写的web服务，requirements.txt是运行该项目所需要的依赖。启动项目前我们先通过<code>pip install -r requirements.txt</code>安装必要依赖，<code>python3 app.py</code>启动项目。</p><p>app.py</p><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> flask <span class="token keyword">import</span> Flaskapp <span class="token operator">=</span> Flask<span class="token punctuation">(</span>__name__<span class="token punctuation">)</span><span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token keyword">def</span> <span class="token function">hello_world</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">return</span> <span class="token string">'Hello, World!'</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">"__main__"</span><span class="token punctuation">:</span>    app<span class="token punctuation">.</span>run<span class="token punctuation">(</span>debug<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>host<span class="token operator">=</span><span class="token string">'0.0.0.0'</span><span class="token punctuation">,</span>port<span class="token operator">=</span><span class="token number">80</span><span class="token punctuation">)</span>    <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>现在我们创建一个容器，在容器内完成以上步骤。</p><figure><div class="code-wrapper"><pre class="line-numbers language-Dockerfile" data-language="Dockerfile"><code class="language-Dockerfile">FROM python:3.6# 设置工作目录WORKDIR &#x2F;app# 将当前目录的所有文件复制到工作目录内ADD . &#x2F;app# 安装依赖RUN pip install -r requirements.txt -i https:&#x2F;&#x2F;mirrors.aliyun.com&#x2F;pypi&#x2F;simple&#x2F;# 启动CMD [&quot;python&quot;, &quot;app.py&quot;]<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>构建镜像</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">docker build -t py-test .<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>运行</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">docker run -p 9089:80  --name&#x3D;py-test py-test<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">* Serving Flask app &quot;app&quot; (lazy loading)* Environment: production  WARNING: This is a development server. Do not use it in a production deployment.  Use a production WSGI server instead.* Debug mode: on* Running on http:&#x2F;&#x2F;0.0.0.0:80&#x2F; (Press CTRL+C to quit)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>好了，容器启动成功，但是这种部署方式有一个不灵活的点就是我们的代码是在容器构建的时候COPY到容器内部的，导致我们更新代码时需要重新构建容器。其实我们还可以通过<code>volume</code>将我们的代码挂载在容器内部，容器执行的其实是宿主机上的代码。</p><figure><div class="code-wrapper"><pre class="line-numbers language-Dockerfile" data-language="Dockerfile"><code class="language-Dockerfile">FROM python:3.6# 设置工作目录WORKDIR &#x2F;app# 将requirements.txt复制到工作目录，方便操作ADD .&#x2F;requirements.txt &#x2F;app&#x2F;requirements.txt# 安装依赖RUN pip install -r requirements.txt -i https:&#x2F;&#x2F;mirrors.aliyun.com&#x2F;pypi&#x2F;simple&#x2F;# 启动CMD [&quot;python&quot;, &quot;app.py&quot;]<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>启动容器时指定挂载路径，将我们当前目录挂载到/app</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">docker run -p 9089:80 -v $PWD&#x2F;:&#x2F;app -d --name&#x3D;py-test  py-test;<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></div></figure><p>我是以DEBUG方式启动的web服务，如代码有变更，服务会自动加载，我们现在更改一下app.py的代码保存一下，可以看到服务器自动重启了</p><figure><div class="code-wrapper"><pre class="line-numbers language-none"><code class="language-none">* Detected change in &#39;&#x2F;app&#x2F;app.py&#39;, reloading* Restarting with stat* Debugger is active!* Debugger PIN: 286-043-277<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></div></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;日常开发Python，环境是一个让人头疼的问题，首先&lt;code&gt;Python2&lt;/code&gt;和&lt;code&gt;Python3&lt;/code&gt;很多语法不兼容，开发环境难免安装多个版本的Python，即使使用同一个版本Python不同项目使用不同版本的第三方包也会导致各种问题，所以开发Python都会使用虚拟环境，不同项目使用不同的环境，其实我们也可以使用Docker将不同的环境进行区分。&lt;/p&gt;</summary>
    
    
    
    <category term="Python" scheme="https://blog.yjll.site/categories/Python/"/>
    
    
    <category term="Python" scheme="https://blog.yjll.site/tags/Python/"/>
    
    <category term="Docker" scheme="https://blog.yjll.site/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>Python通过跳板机连接远程MySQL</title>
    <link href="https://blog.yjll.site/post/adfcf5ef.html"/>
    <id>https://blog.yjll.site/post/adfcf5ef.html</id>
    <published>2020-11-06T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.584Z</updated>
    
    <content type="html"><![CDATA[<p>一般公司的数据库在一个远程服务器里，而这个服务器需要链接跳板机才可以访问，常用的数据库客户端工具都支持配置SSH隧道，可以通过SSH隧道连接远程服务。但python脚本如何访问数据库呢，我们可以借助<code>sshtunnel</code>这个库来实现。</p><span id="more"></span><figure><div class="code-wrapper"><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> sshtunnel <span class="token keyword">import</span> SSHTunnelForwarder<span class="token keyword">import</span> records<span class="token keyword">with</span> SSHTunnelForwarder<span class="token punctuation">(</span>        <span class="token punctuation">(</span><span class="token string">'jump-server'</span><span class="token punctuation">,</span> <span class="token number">22</span><span class="token punctuation">)</span><span class="token punctuation">,</span>        ssh_username<span class="token operator">=</span><span class="token string">"root"</span><span class="token punctuation">,</span>        ssh_pkey<span class="token operator">=</span><span class="token string">r"C:\Users\LH\.ssh\id_rsa"</span><span class="token punctuation">,</span>        remote_bind_address<span class="token operator">=</span><span class="token punctuation">(</span><span class="token string">'target-server'</span><span class="token punctuation">,</span> <span class="token number">3306</span><span class="token punctuation">)</span><span class="token punctuation">,</span>        local_bind_address<span class="token operator">=</span><span class="token punctuation">(</span><span class="token string">'127.0.0.1'</span><span class="token punctuation">,</span> <span class="token number">3306</span><span class="token punctuation">)</span>        <span class="token punctuation">)</span> <span class="token keyword">as</span> server<span class="token punctuation">:</span>        db <span class="token operator">=</span> records<span class="token punctuation">.</span>Database<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'mysql+pymysql://root:pwd@</span><span class="token interpolation"><span class="token punctuation">&#123;</span>server<span class="token punctuation">.</span>local_bind_address<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">&#125;</span></span><span class="token string">:</span><span class="token interpolation"><span class="token punctuation">&#123;</span>server<span class="token punctuation">.</span>local_bind_address<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">&#125;</span></span><span class="token string">/code'</span></span><span class="token punctuation">)</span>    result <span class="token operator">=</span> db<span class="token punctuation">.</span>query<span class="token punctuation">(</span><span class="token string">'select name from base_areas limit 3'</span><span class="token punctuation">)</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>as_dict<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;一般公司的数据库在一个远程服务器里，而这个服务器需要链接跳板机才可以访问，常用的数据库客户端工具都支持配置SSH隧道，可以通过SSH隧道连接远程服务。但python脚本如何访问数据库呢，我们可以借助&lt;code&gt;sshtunnel&lt;/code&gt;这个库来实现。&lt;/p&gt;</summary>
    
    
    
    <category term="Python" scheme="https://blog.yjll.site/categories/Python/"/>
    
    
    <category term="Python" scheme="https://blog.yjll.site/tags/Python/"/>
    
    <category term="sshtunnel" scheme="https://blog.yjll.site/tags/sshtunnel/"/>
    
  </entry>
  
  <entry>
    <title>Spring编程式事务原理</title>
    <link href="https://blog.yjll.site/post/65703737.html"/>
    <id>https://blog.yjll.site/post/65703737.html</id>
    <published>2020-11-06T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.583Z</updated>
    
    <content type="html"><![CDATA[<table><thead><tr><th>类型</th><th>说明</th><th>举例</th></tr></thead><tbody><tr><td>PROPAGATION_REQUIRED</td><td>支持当前事务 如果不存，就建一个（默认的）</td><td>A,B，如果A有事务，则B使用A事务。如果A没有事务，则B开启一个新的事务。（A,B在同一个事务中）</td></tr><tr><td>PROPAGATION_SUPPORTS</td><td>支持当前事务 如果不存在，就不使用事务</td><td>A,B，如果A有事务，则B就使用A的事务。如果A没有事务，则B就不使用事务</td></tr><tr><td>PROPAGATION_MANDATORY</td><td>支持当前事务 如果不存在，抛出异常</td><td>A,B，如果A有事务，则B就使用A的事务。如果A没有事务，则B抛出异常</td></tr><tr><td>PROPAGATION_REQUIRES_NEW</td><td>如果有事务存在 挂起当前事务，创建一个新的事务</td><td>A,B，如果A有事务，B将A的事务挂起，并重新创建一个事务（A，B不在一个事务中，两个事务互不影响）</td></tr><tr><td>PROPAGATION_NOT_SUPPORTED</td><td>以非事务方式运行 如果有事务存在，挂起当前事务</td><td>A,B，非事务方式运行，A有事务就挂起事务</td></tr><tr><td>PROPAGATION_NEVER</td><td>以非事务方式运行 如果有事务存在，抛出异常</td><td>总是以非事务运行</td></tr><tr><td>PROPAGATION_NESTED</td><td>如果当前事务存在，则嵌套事务执行</td><td>基于SavePonit技术。（保存点） A,B，A有事务，A执行之后，将A事务执行之后的内容保存到SavePonit。B事务有异常的情况，用户需要自己设置事务是提交还是回滚到保存点</td></tr></tbody></table><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Autowired</span><span class="token keyword">private</span> <span class="token class-name">DataSourceTransactionManager</span> dstManager<span class="token punctuation">;</span><span class="token annotation punctuation">@Autowired</span><span class="token keyword">private</span> <span class="token class-name">UserDao</span> userDao<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">updateTest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token class-name">DefaultTransactionDefinition</span> def <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultTransactionDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  def<span class="token punctuation">.</span><span class="token function">setPropagationBehavior</span><span class="token punctuation">(</span><span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_REQUIRES_NEW</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token class-name">TransactionStatus</span> transaction<span class="token operator">=</span> dstManager<span class="token punctuation">.</span><span class="token function">getTransaction</span><span class="token punctuation">(</span>def<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">try</span><span class="token punctuation">&#123;</span>    userDao<span class="token punctuation">.</span><span class="token function">updateTest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>    dstManager<span class="token punctuation">.</span><span class="token function">rollback</span><span class="token punctuation">(</span>transaction<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  dstManager<span class="token punctuation">.</span><span class="token function">commit</span><span class="token punctuation">(</span>transaction<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p><code>DataSourceTransactionManager</code>是<code>PlatformTransactionManager</code>的一个实现。<code>PlatformTransactionManager</code>是Spring事务处理抽象层中的管理器。</p><p>统一一致的事务抽象是Spring框架的一大优势，无论是全局事务还是本地事务，JTA、JDBC、Hibernate还是JPA，Spring都使用统一的编程模型，使得应用程序可以很容易地在全局事务与本地事务，或者不同的事务框架之间进行切换。下图是Spring事务抽象的核心类图：</p><p><img src="https://img.yjll.site/img/PlatformTransactionManager.png" alt="PlatformTransactionManager"></p><p>PlatformTransactionManager：事务管理器<br>getTransaction方法：事务获取操作，根据事务属性定义，获取当前事务或者创建新事物；<br>commit方法：事务提交操作，注意这里所说的提交并非直接提交事务，而是根据当前事务状态执行提交或者回滚操作；<br>rollback方法：事务回滚操作，同样，也并非一定直接回滚事务，也有可能只是标记事务为只读，等待其他调用方执行回滚。</p><p>TransactionDefinition：事务属性定义<br>getPropagationBehavior方法：返回事务的传播属性，默认是PROPAGATION_REQUIRED；<br>getIsolationLevel方法：返回事务隔离级别，事务隔离级别只有在创建新事务时才有效，也就是说只对应传播属性PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW；<br>getTimeout方法：返回事务超时时间，以秒为单位，同样只有在创建新事务时才有效；<br>isReadOnly方法：是否优化为只读事务，支持这项属性的事务管理器会将事务标记为只读，只读事务不允许有写操作，不支持只读属性的事务管理器需要忽略这项设置，这一点跟其他事务属性定义不同，针对其他不支持的属性设置，事务管理器应该抛出异常。<br>getName方法：返回事务名称，声明式事务中默认值为“类的完全限定名.方法名”。</p><p>TransactionStatus：当前事务状态<br>isNewTransaction方法：当前方法是否创建了新事务（区别于使用现有事务以及没有事务）；<br>hasSavepoint方法：在嵌套事务场景中，判断当前事务是否包含保存点；<br>setRollbackOnly和isRollbackOnly方法：只读属性设置（主要用于标记事务，等待回滚）和查询；<br>flush方法：刷新底层会话中的修改到数据库，一般用于刷新如Hibernate/JPA的会话，是否生效由具体事务资源实现决定；<br>isCompleted方法：判断当前事务是否已完成（已提交或者已回滚）。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token class-name">TransactionStatus</span> <span class="token function">getTransaction</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Nullable</span> <span class="token class-name">TransactionDefinition</span> definition<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">TransactionException</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 创建DataSourceTransactionObject</span><span class="token class-name">Object</span> transaction <span class="token operator">=</span> <span class="token function">doGetTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Cache debug flag to avoid repeated checks.</span><span class="token keyword">boolean</span> debugEnabled <span class="token operator">=</span> logger<span class="token punctuation">.</span><span class="token function">isDebugEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">// Use defaults if no transaction definition given.</span>definition <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultTransactionDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>    <span class="token comment">// 已有事务逻辑，通过handleExistingTransaction方法处理</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isExistingTransaction</span><span class="token punctuation">(</span>transaction<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">// Existing transaction found -> check propagation behavior to find out how to behave.</span><span class="token keyword">return</span> <span class="token function">handleExistingTransaction</span><span class="token punctuation">(</span>definition<span class="token punctuation">,</span> transaction<span class="token punctuation">,</span> debugEnabled<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// Check definition settings for new transaction.</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getTimeout</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">TIMEOUT_DEFAULT</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidTimeoutException</span><span class="token punctuation">(</span><span class="token string">"Invalid transaction timeout"</span><span class="token punctuation">,</span> definition<span class="token punctuation">.</span><span class="token function">getTimeout</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>    <span class="token comment">// 以下是当前无事务的情况</span>    <span class="token comment">// - PROPAGATION_MANDATORY：当前事务不存在，抛出异常</span><span class="token comment">// No existing transaction found -> check propagation behavior to find out how to proceed.</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getPropagationBehavior</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_MANDATORY</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalTransactionStateException</span><span class="token punctuation">(</span><span class="token string">"No existing transaction found for transaction marked with propagation 'mandatory'"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>    <span class="token comment">// 无事务便创建事务</span><span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getPropagationBehavior</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_REQUIRED</span> <span class="token operator">||</span>definition<span class="token punctuation">.</span><span class="token function">getPropagationBehavior</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_REQUIRES_NEW</span> <span class="token operator">||</span>definition<span class="token punctuation">.</span><span class="token function">getPropagationBehavior</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_NESTED</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token class-name">SuspendedResourcesHolder</span> suspendedResources <span class="token operator">=</span> <span class="token function">suspend</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>debugEnabled<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Creating new transaction with name ["</span> <span class="token operator">+</span> definition<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"]: "</span> <span class="token operator">+</span> definition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">try</span> <span class="token punctuation">&#123;</span><span class="token keyword">boolean</span> newSynchronization <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token function">getTransactionSynchronization</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token constant">SYNCHRONIZATION_NEVER</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">DefaultTransactionStatus</span> status <span class="token operator">=</span> <span class="token function">newTransactionStatus</span><span class="token punctuation">(</span>definition<span class="token punctuation">,</span> transaction<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> newSynchronization<span class="token punctuation">,</span> debugEnabled<span class="token punctuation">,</span> suspendedResources<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">doBegin</span><span class="token punctuation">(</span>transaction<span class="token punctuation">,</span> definition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">prepareSynchronization</span><span class="token punctuation">(</span>status<span class="token punctuation">,</span> definition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> status<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">RuntimeException</span> <span class="token operator">|</span> <span class="token class-name">Error</span> ex<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token function">resume</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> suspendedResources<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">throw</span> ex<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>    <span class="token comment">// 无事务不需创建</span><span class="token keyword">else</span> <span class="token punctuation">&#123;</span><span class="token comment">// Create "empty" transaction: no actual transaction, but potentially synchronization.</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getIsolationLevel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">ISOLATION_DEFAULT</span> <span class="token operator">&amp;&amp;</span> logger<span class="token punctuation">.</span><span class="token function">isWarnEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>logger<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">"Custom isolation level specified but no actual transaction initiated; "</span> <span class="token operator">+</span><span class="token string">"isolation level will effectively be ignored: "</span> <span class="token operator">+</span> definition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">boolean</span> newSynchronization <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token function">getTransactionSynchronization</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token constant">SYNCHRONIZATION_ALWAYS</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token function">prepareTransactionStatus</span><span class="token punctuation">(</span>definition<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> newSynchronization<span class="token punctuation">,</span> debugEnabled<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name"><span class="token namespace">org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>jdbc<span class="token punctuation">.</span>datasource<span class="token punctuation">.</span></span>DataSourceTransactionManager</span>#doGetTransaction<span class="token keyword">protected</span> <span class="token class-name">Object</span> <span class="token function">doGetTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token class-name">DataSourceTransactionObject</span> txObject <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DataSourceTransactionObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>txObject<span class="token punctuation">.</span><span class="token function">setSavepointAllowed</span><span class="token punctuation">(</span><span class="token function">isNestedTransactionAllowed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">ConnectionHolder</span> conHolder <span class="token operator">=</span><span class="token punctuation">(</span><span class="token class-name">ConnectionHolder</span><span class="token punctuation">)</span> <span class="token class-name">TransactionSynchronizationManager</span><span class="token punctuation">.</span><span class="token function">getResource</span><span class="token punctuation">(</span><span class="token function">obtainDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>txObject<span class="token punctuation">.</span><span class="token function">setConnectionHolder</span><span class="token punctuation">(</span>conHolder<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> txObject<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token class-name"><span class="token namespace">org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>jdbc<span class="token punctuation">.</span>datasource<span class="token punctuation">.</span></span>DataSourceTransactionManager</span>#isExistingTransaction<span class="token keyword">protected</span> <span class="token keyword">boolean</span> <span class="token function">isExistingTransaction</span><span class="token punctuation">(</span><span class="token class-name">Object</span> transaction<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token class-name">DataSourceTransactionObject</span> txObject <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">DataSourceTransactionObject</span><span class="token punctuation">)</span> transaction<span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token punctuation">(</span>txObject<span class="token punctuation">.</span><span class="token function">hasConnectionHolder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> txObject<span class="token punctuation">.</span><span class="token function">getConnectionHolder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isTransactionActive</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p><code>TransactionSynchronizationManager</code> 是事务同步管理器，TransactionSynchronizationManager通过ThreadLocal对象在当前线程记录了resources和synchronizations属性。resources是一个HashMap，用于记录当前参与事务的事务资源，方便进行事务同步，在DataSourceTransactionManager的例子中就是以dataSource作为key，保存了数据库连接，这样在同一个线程中，不同的方法调用就可以通过dataSource获取相同的数据库连接，从而保证所有操作在一个事务中进行。</p><p>org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token class-name">TransactionStatus</span> <span class="token function">handleExistingTransaction</span><span class="token punctuation">(</span><span class="token class-name">TransactionDefinition</span> definition<span class="token punctuation">,</span> <span class="token class-name">Object</span> transaction<span class="token punctuation">,</span> <span class="token keyword">boolean</span> debugEnabled<span class="token punctuation">)</span><span class="token keyword">throws</span> <span class="token class-name">TransactionException</span> <span class="token punctuation">&#123;</span>   <span class="token comment">// PROPAGATION_NEVER 直接抛出异常</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getPropagationBehavior</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_NEVER</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalTransactionStateException</span><span class="token punctuation">(</span><span class="token string">"Existing transaction found for transaction marked with propagation 'never'"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>  <span class="token comment">// PROPAGATION_NOT_SUPPORTED suspend挂起当前事务，当前方法不使用事务</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getPropagationBehavior</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_NOT_SUPPORTED</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>debugEnabled<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Suspending current transaction"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token class-name">Object</span> suspendedResources <span class="token operator">=</span> <span class="token function">suspend</span><span class="token punctuation">(</span>transaction<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">boolean</span> newSynchronization <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token function">getTransactionSynchronization</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token constant">SYNCHRONIZATION_ALWAYS</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token function">prepareTransactionStatus</span><span class="token punctuation">(</span>definition<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> newSynchronization<span class="token punctuation">,</span> debugEnabled<span class="token punctuation">,</span> suspendedResources<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>  <span class="token comment">// PROPAGATION_REQUIRES_NEW suspend挂起当前事务，doBegin创建一个新事务</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getPropagationBehavior</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_REQUIRES_NEW</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>debugEnabled<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Suspending current transaction, creating new transaction with name ["</span> <span class="token operator">+</span>definition<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"]"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token class-name">SuspendedResourcesHolder</span> suspendedResources <span class="token operator">=</span> <span class="token function">suspend</span><span class="token punctuation">(</span>transaction<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">try</span> <span class="token punctuation">&#123;</span><span class="token keyword">boolean</span> newSynchronization <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token function">getTransactionSynchronization</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token constant">SYNCHRONIZATION_NEVER</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">DefaultTransactionStatus</span> status <span class="token operator">=</span> <span class="token function">newTransactionStatus</span><span class="token punctuation">(</span>definition<span class="token punctuation">,</span> transaction<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> newSynchronization<span class="token punctuation">,</span> debugEnabled<span class="token punctuation">,</span> suspendedResources<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">doBegin</span><span class="token punctuation">(</span>transaction<span class="token punctuation">,</span> definition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">prepareSynchronization</span><span class="token punctuation">(</span>status<span class="token punctuation">,</span> definition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> status<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">RuntimeException</span> <span class="token operator">|</span> <span class="token class-name">Error</span> beginEx<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token function">resumeAfterBeginException</span><span class="token punctuation">(</span>transaction<span class="token punctuation">,</span> suspendedResources<span class="token punctuation">,</span> beginEx<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">throw</span> beginEx<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>  <span class="token comment">// PROPAGATION_NESTED 嵌套事务</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getPropagationBehavior</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PROPAGATION_NESTED</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isNestedTransactionAllowed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NestedTransactionNotSupportedException</span><span class="token punctuation">(</span><span class="token string">"Transaction manager does not allow nested transactions by default - "</span> <span class="token operator">+</span><span class="token string">"specify 'nestedTransactionAllowed' property with value 'true'"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>debugEnabled<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Creating nested transaction with name ["</span> <span class="token operator">+</span> definition<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"]"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">useSavepointForNestedTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">// Create savepoint within existing Spring-managed transaction,</span><span class="token comment">// through the SavepointManager API implemented by TransactionStatus.</span><span class="token comment">// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.</span><span class="token class-name">DefaultTransactionStatus</span> status <span class="token operator">=</span><span class="token function">prepareTransactionStatus</span><span class="token punctuation">(</span>definition<span class="token punctuation">,</span> transaction<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> debugEnabled<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>status<span class="token punctuation">.</span><span class="token function">createAndHoldSavepoint</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> status<span class="token punctuation">;</span><span class="token punctuation">&#125;</span>     <span class="token comment">// PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY 使用当前事务</span><span class="token keyword">else</span> <span class="token punctuation">&#123;</span><span class="token comment">// Nested transaction through nested begin and commit/rollback calls.</span><span class="token comment">// Usually only for JTA: Spring synchronization might get activated here</span><span class="token comment">// in case of a pre-existing JTA transaction.</span><span class="token keyword">boolean</span> newSynchronization <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token function">getTransactionSynchronization</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token constant">SYNCHRONIZATION_NEVER</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">DefaultTransactionStatus</span> status <span class="token operator">=</span> <span class="token function">newTransactionStatus</span><span class="token punctuation">(</span>definition<span class="token punctuation">,</span> transaction<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> newSynchronization<span class="token punctuation">,</span> debugEnabled<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">doBegin</span><span class="token punctuation">(</span>transaction<span class="token punctuation">,</span> definition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">prepareSynchronization</span><span class="token punctuation">(</span>status<span class="token punctuation">,</span> definition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> status<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.</span><span class="token keyword">if</span> <span class="token punctuation">(</span>debugEnabled<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Participating in existing transaction"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isValidateExistingTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getIsolationLevel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token class-name">TransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">ISOLATION_DEFAULT</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token class-name">Integer</span> currentIsolationLevel <span class="token operator">=</span> <span class="token class-name">TransactionSynchronizationManager</span><span class="token punctuation">.</span><span class="token function">getCurrentTransactionIsolationLevel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>currentIsolationLevel <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> currentIsolationLevel <span class="token operator">!=</span> definition<span class="token punctuation">.</span><span class="token function">getIsolationLevel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token class-name">Constants</span> isoConstants <span class="token operator">=</span> <span class="token class-name">DefaultTransactionDefinition</span><span class="token punctuation">.</span>constants<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalTransactionStateException</span><span class="token punctuation">(</span><span class="token string">"Participating transaction with definition ["</span> <span class="token operator">+</span>definition <span class="token operator">+</span> <span class="token string">"] specifies isolation level which is incompatible with existing transaction: "</span> <span class="token operator">+</span><span class="token punctuation">(</span>currentIsolationLevel <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">?</span>isoConstants<span class="token punctuation">.</span><span class="token function">toCode</span><span class="token punctuation">(</span>currentIsolationLevel<span class="token punctuation">,</span> <span class="token class-name">DefaultTransactionDefinition</span><span class="token punctuation">.</span><span class="token constant">PREFIX_ISOLATION</span><span class="token punctuation">)</span> <span class="token operator">:</span><span class="token string">"(unknown)"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>definition<span class="token punctuation">.</span><span class="token function">isReadOnly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">TransactionSynchronizationManager</span><span class="token punctuation">.</span><span class="token function">isCurrentTransactionReadOnly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalTransactionStateException</span><span class="token punctuation">(</span><span class="token string">"Participating transaction with definition ["</span> <span class="token operator">+</span>definition <span class="token operator">+</span> <span class="token string">"] is not marked as read-only but existing transaction is"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">boolean</span> newSynchronization <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token function">getTransactionSynchronization</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token constant">SYNCHRONIZATION_NEVER</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token function">prepareTransactionStatus</span><span class="token punctuation">(</span>definition<span class="token punctuation">,</span> transaction<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> newSynchronization<span class="token punctuation">,</span> debugEnabled<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p><a href="https://zhuanlan.zhihu.com/p/54067384">【技术干货】Spring事务原理一探</a></p><p><a href="https://blog.csdn.net/oDeviloo/article/details/101757711">spring事务传播性源码解析</a></p><p><a href="https://my.oschina.net/marvelcode/blog/3195379">Spring事务源码解析</a></p>]]></content>
    
    
      
      
    <summary type="html">&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;PROPAGATION_REQUIRED&lt;/td&gt;
&lt;td&gt;支持当前事务 如果不存，就建一个（默认的）&lt;</summary>
      
    
    
    
    <category term="Java" scheme="https://blog.yjll.site/categories/Java/"/>
    
    
    <category term="Spring" scheme="https://blog.yjll.site/tags/Spring/"/>
    
    <category term="Transaction" scheme="https://blog.yjll.site/tags/Transaction/"/>
    
  </entry>
  
  <entry>
    <title>聊一聊Mybatis-Spring都帮我们做了什么</title>
    <link href="https://blog.yjll.site/post/112d5fa0.html"/>
    <id>https://blog.yjll.site/post/112d5fa0.html</id>
    <published>2020-11-03T00:00:00.000Z</published>
    <updated>2026-10-14T06:56:34.583Z</updated>
    
    <content type="html"><![CDATA[<p>MyBatis-Spring 会帮助我们将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中，创建映射器 mapper 和 SqlSession 并注入到 bean 中，本文将跟随Mybatis-Spring 的源码，一步步探究是它是如何实现的。</p><span id="more"></span><p>我们先进行一个必要的bean配置。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token annotation punctuation">@MapperScan</span><span class="token punctuation">(</span>basePackages <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token string">"com.xxx.goods.dao"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> sqlSessionFactoryRef <span class="token operator">=</span> <span class="token string">"goodsSqlSessionFactory"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GoodsDataSourceConfig</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> <span class="token class-name">Environment</span> env<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Bean</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"goodsDataSource"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"spring.datasource.goods"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token class-name">DataSource</span> <span class="token function">initDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token class-name">DataSourceBuilder</span><span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>        <span class="token annotation punctuation">@Bean</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"goodsTransactionManager"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Primary</span>    <span class="token keyword">public</span> <span class="token class-name">DataSourceTransactionManager</span> <span class="token function">transactionManager</span><span class="token punctuation">(</span><span class="token class-name">DataSource</span> dataSource<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">DataSourceTransactionManager</span><span class="token punctuation">(</span>dataSource<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token annotation punctuation">@Bean</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"goodsSqlSessionFactory"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Primary</span>    <span class="token keyword">public</span> <span class="token class-name">SqlSessionFactory</span> <span class="token function">initSqlSessionFactory</span><span class="token punctuation">(</span><span class="token class-name">DataSource</span> dataSource<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">final</span> <span class="token class-name">SqlSessionFactoryBean</span> sessionFactory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SqlSessionFactoryBean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        sessionFactory<span class="token punctuation">.</span><span class="token function">setDataSource</span><span class="token punctuation">(</span>dataSource<span class="token punctuation">)</span><span class="token punctuation">;</span>        sessionFactory<span class="token punctuation">.</span><span class="token function">setMapperLocations</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">PathMatchingResourcePatternResolver</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">getResources</span><span class="token punctuation">(</span>env<span class="token punctuation">.</span><span class="token function">getProperty</span><span class="token punctuation">(</span><span class="token string">"mybatis.goods.mapper-locations"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> sessionFactory<span class="token punctuation">.</span><span class="token function">getObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="Mybatis原生写法"><a href="#Mybatis原生写法" class="headerlink" title="Mybatis原生写法"></a>Mybatis原生写法</h3><p>我这里引用<a href="https://mybatis.org/mybatis-3/zh/getting-started.html">Mybatis官方文档</a>中的说明</p><ul><li><code>SqlSessionFactory</code> SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在，没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次，多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到，最简单的就是使用单例模式或者静态单例模式。</li></ul><ul><li><code>SqlSession</code> 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的，因此是不能被共享的，所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域，甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中，比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架，考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说，每次收到 HTTP 请求，就可以打开一个 SqlSession，返回一个响应后，就关闭它。 这个关闭操作很重要，为了确保每次都能执行关闭操作，你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式：</li></ul><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">SqlSession</span> session <span class="token operator">=</span> sqlSessionFactory<span class="token punctuation">.</span><span class="token function">openSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 你的应用逻辑代码</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></div></figure><ul><li><code>Mapper</code> 映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲，任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说，映射器实例应该在调用它们的方法中被获取，使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题，但是你很快会发现，在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此，最好将映射器放在方法作用域内。就像下面的例子一样：</li></ul><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">SqlSession</span> session <span class="token operator">=</span> sqlSessionFactory<span class="token punctuation">.</span><span class="token function">openSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token class-name">BlogMapper</span> mapper <span class="token operator">=</span> session<span class="token punctuation">.</span><span class="token function">getMapper</span><span class="token punctuation">(</span><span class="token class-name">BlogMapper</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 你的应用逻辑代码</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></div></figure><h3 id="依赖注入框架"><a href="#依赖注入框架" class="headerlink" title="依赖注入框架"></a>依赖注入框架</h3><p>依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器，并将它们直接注入到你的 bean 中，因此可以直接忽略它们的生命周期。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserService</span> <span class="token punctuation">&#123;</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> <span class="token class-name">UserMapper</span> userMapper<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>我们分析一下<code>Mybatis-Spring</code>如何做到的</p><p>Mybatis配置时我们使用了<code>@MapperScan</code>注解，底层会使用<code>MapperScannerConfigurer</code>进行扫描并将Mapper注册到Spring容器中，核心代码要看<code>org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions</code>这个方法。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">processBeanDefinitions</span><span class="token punctuation">(</span><span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">BeanDefinitionHolder</span><span class="token punctuation">></span></span> beanDefinitions<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token class-name">GenericBeanDefinition</span> definition<span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">BeanDefinitionHolder</span> holder <span class="token operator">:</span> beanDefinitions<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    definition <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">GenericBeanDefinition</span><span class="token punctuation">)</span> holder<span class="token punctuation">.</span><span class="token function">getBeanDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 这两句代码时关键，这里将 beanClass 进行了重新设置，设置为了 MapperFactoryBean，并且将原来接口 class 设置为构造方法参数</span>    definition<span class="token punctuation">.</span><span class="token function">getConstructorArgumentValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addGenericArgumentValue</span><span class="token punctuation">(</span>definition<span class="token punctuation">.</span><span class="token function">getBeanClassName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// issue #59</span>    definition<span class="token punctuation">.</span><span class="token function">setBeanClass</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>mapperFactoryBean<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    definition<span class="token punctuation">.</span><span class="token function">getPropertyValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"addToConfig"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addToConfig<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 设置 sqlSessionFactory，如果有配置</span>    <span class="token keyword">boolean</span> explicitFactoryUsed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">StringUtils</span><span class="token punctuation">.</span><span class="token function">hasText</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactoryBeanName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      definition<span class="token punctuation">.</span><span class="token function">getPropertyValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"sqlSessionFactory"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeBeanReference</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactoryBeanName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      explicitFactoryUsed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactory <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      definition<span class="token punctuation">.</span><span class="token function">getPropertyValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"sqlSessionFactory"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactory<span class="token punctuation">)</span><span class="token punctuation">;</span>      explicitFactoryUsed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 设置 sqlSessionTemplate，如果有配置</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">StringUtils</span><span class="token punctuation">.</span><span class="token function">hasText</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionTemplateBeanName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      definition<span class="token punctuation">.</span><span class="token function">getPropertyValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"sqlSessionTemplate"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeBeanReference</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionTemplateBeanName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      explicitFactoryUsed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionTemplate <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      definition<span class="token punctuation">.</span><span class="token function">getPropertyValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"sqlSessionTemplate"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionTemplate<span class="token punctuation">)</span><span class="token punctuation">;</span>      explicitFactoryUsed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 如果 sqlSessionFactory &amp; sqlSessionTemplate 都没有指定，那么启用类型自动注入</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>explicitFactoryUsed<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      definition<span class="token punctuation">.</span><span class="token function">setAutowireMode</span><span class="token punctuation">(</span><span class="token class-name">AbstractBeanDefinition</span><span class="token punctuation">.</span><span class="token constant">AUTOWIRE_BY_TYPE</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>方法中<code>this.mapperFactoryBean.getClass()</code>会固定返回<code>org.mybatis.spring.mapper.MapperFactoryBean</code>，<code>definition.getBeanClassName()</code>返回的Mapper的类名作为<code>MapperFactoryBean</code>的属性。<br>Spring初始化进行装配时会调用<code>MapperFactoryBean</code>的<code>getObject()</code>。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token class-name">SqlSession</span> <span class="token function">getSqlSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sqlSession<span class="token punctuation">;</span><span class="token punctuation">&#125;</span>  <span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token class-name">T</span> <span class="token function">getObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token function">getSqlSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getMapper</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>mapperInterface<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>看到这，就知道了怎么将Mapper声明成bean，但是getSqlSession()返回的是属性中的sqlSession，Mybatis原生的写法中sqlSession不是线程安全的，那Mybatis-Spring如何做到用一个sqlSession处理多个线程的请求？我们继续看这个sqlSession有什么特别的。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setSqlSessionFactory</span><span class="token punctuation">(</span><span class="token class-name">SqlSessionFactory</span> sqlSessionFactory<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>externalSqlSession<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>sqlSession <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">(</span>sqlSessionFactory<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p>此处使用的<code>sqlSession</code>并非是Mybatis的<code>DefaultSqlSession</code>，而是<code>Mybatis-Spring</code>提供的<code>SqlSessionTemplate</code>。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">(</span><span class="token class-name">SqlSessionFactory</span> sqlSessionFactory<span class="token punctuation">,</span> <span class="token class-name">ExecutorType</span> executorType<span class="token punctuation">,</span>    <span class="token class-name">PersistenceExceptionTranslator</span> exceptionTranslator<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">notNull</span><span class="token punctuation">(</span>sqlSessionFactory<span class="token punctuation">,</span> <span class="token string">"Property 'sqlSessionFactory' is required"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">notNull</span><span class="token punctuation">(</span>executorType<span class="token punctuation">,</span> <span class="token string">"Property 'executorType' is required"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactory <span class="token operator">=</span> sqlSessionFactory<span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>executorType <span class="token operator">=</span> executorType<span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>exceptionTranslator <span class="token operator">=</span> exceptionTranslator<span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionProxy <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">SqlSession</span><span class="token punctuation">)</span> <span class="token function">newProxyInstance</span><span class="token punctuation">(</span>      <span class="token class-name">SqlSessionFactory</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      <span class="token keyword">new</span> <span class="token class-name">Class</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">&#123;</span> <span class="token class-name">SqlSession</span><span class="token punctuation">.</span><span class="token keyword">class</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>      <span class="token keyword">new</span> <span class="token class-name">SqlSessionInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">T</span><span class="token punctuation">></span></span> <span class="token class-name">T</span> <span class="token function">selectOne</span><span class="token punctuation">(</span><span class="token class-name">String</span> statement<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionProxy<span class="token punctuation">.</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">T</span><span class="token punctuation">></span></span> <span class="token function">selectOne</span><span class="token punctuation">(</span>statement<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p><code>SqlSessionTemplate</code>虽然是<code>SqlSession</code>的实现，但内部真正调用并非是他自己，而是生成了一个代理类，我们再看一下代理。</p><figure><div class="code-wrapper"><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">class</span> <span class="token class-name">SqlSessionInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">InvocationHandler</span> <span class="token punctuation">&#123;</span>  <span class="token annotation punctuation">@Override</span>  <span class="token keyword">public</span> <span class="token class-name">Object</span> <span class="token function">invoke</span><span class="token punctuation">(</span><span class="token class-name">Object</span> proxy<span class="token punctuation">,</span> <span class="token class-name">Method</span> method<span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Throwable</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">SqlSession</span> sqlSession <span class="token operator">=</span> <span class="token function">getSqlSession</span><span class="token punctuation">(</span>        <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">.</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactory<span class="token punctuation">,</span>        <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">.</span><span class="token keyword">this</span><span class="token punctuation">.</span>executorType<span class="token punctuation">,</span>        <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">.</span><span class="token keyword">this</span><span class="token punctuation">.</span>exceptionTranslator<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>      <span class="token class-name">Object</span> result <span class="token operator">=</span> method<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>sqlSession<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isSqlSessionTransactional</span><span class="token punctuation">(</span>sqlSession<span class="token punctuation">,</span> <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">.</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactory<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// force commit even on non-dirty sessions because some databases require</span>        <span class="token comment">// a commit/rollback before calling close()</span>        sqlSession<span class="token punctuation">.</span><span class="token function">commit</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> t<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token class-name">Throwable</span> unwrapped <span class="token operator">=</span> <span class="token function">unwrapThrowable</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">.</span><span class="token keyword">this</span><span class="token punctuation">.</span>exceptionTranslator <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> unwrapped <span class="token keyword">instanceof</span> <span class="token class-name">PersistenceException</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// release the connection to avoid a deadlock if the translator is no loaded. See issue #22</span>        <span class="token function">closeSqlSession</span><span class="token punctuation">(</span>sqlSession<span class="token punctuation">,</span> <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">.</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactory<span class="token punctuation">)</span><span class="token punctuation">;</span>        sqlSession <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>        <span class="token class-name">Throwable</span> translated <span class="token operator">=</span> <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">.</span><span class="token keyword">this</span><span class="token punctuation">.</span>exceptionTranslator<span class="token punctuation">.</span><span class="token function">translateExceptionIfPossible</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">PersistenceException</span><span class="token punctuation">)</span> unwrapped<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>translated <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          unwrapped <span class="token operator">=</span> translated<span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">throw</span> unwrapped<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>sqlSession <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">closeSqlSession</span><span class="token punctuation">(</span>sqlSession<span class="token punctuation">,</span> <span class="token class-name">SqlSessionTemplate</span><span class="token punctuation">.</span><span class="token keyword">this</span><span class="token punctuation">.</span>sqlSessionFactory<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></div></figure><p><code>sqlSessionProxy</code>运行时，每次都会判断当前事务中是否有SqlSession，没有就重新创建，这样可以保证多线程访问不会出现问题，也可以保证事务的传递。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Mybatis-Spring代码量不大，但实现比较巧妙，如果对Mapper注入和Spring事务感兴趣可以深入了解一下。</p><h3 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h3><p><a href="https://mybatis.org/mybatis-3/">MyBatis.org</a></p><p><a href="http://mybatis.org/spring/zh/getting-started.html">mybatis-spring</a></p><p><a href="https://my.oschina.net/u/174366/blog/1788966">mybatis MapperScannerConfigurer 源码剖析</a></p><p><a href="https://juejin.im/post/6844904033610629128">Mybatis源码系列3-三种SqlSession的区别</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;MyBatis-Spring 会帮助我们将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中，创建映射器 mapper 和 SqlSession 并注入到 bean 中，本文将跟随Mybatis-Spring 的源码，一步步探究是它是如何实现的。&lt;/p&gt;</summary>
    
    
    
    <category term="Java" scheme="https://blog.yjll.site/categories/Java/"/>
    
    
    <category term="Java" scheme="https://blog.yjll.site/tags/Java/"/>
    
    <category term="Spring" scheme="https://blog.yjll.site/tags/Spring/"/>
    
    <category term="Mybatis" scheme="https://blog.yjll.site/tags/Mybatis/"/>
    
  </entry>
  
</feed>
