<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>MyBatisGX Blog</title>
        <link>http://www.mybatisgx.com/blog</link>
        <description>MyBatisGX Blog</description>
        <lastBuildDate>Fri, 20 Mar 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-Hans</language>
        <item>
            <title><![CDATA[为什么我不喜欢用 MyBatis Plus、JPA 框架，更愿意使用 MyBatis]]></title>
            <link>http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis</link>
            <guid>http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis</guid>
            <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从 MyBatis Plus、JPA 到 MyBatis 的选择困境，以及 MyBatisGX 的解决之道]]></description>
            <content:encoded><![CDATA[<p>快速开发 vs 架构清晰，这是持久层框架选择中最常见的两难。</p>
<p>大多数团队选择 MyBatis Plus：</p>
<ul>
<li class="">第 1 个月：开发速度快 ✅</li>
<li class="">第 6 个月：代码难维护 ❌</li>
</ul>
<p>少数团队选择 MyBatis：</p>
<ul>
<li class="">第 1 个月：写 XML 繁琐 ❌</li>
<li class="">第 6 个月：架构清晰 ✅</li>
</ul>
<p>这就是技术选型的困境：<strong>前期效率 vs 长期质量</strong></p>
<p>但真的必须在两者之间做选择吗？</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一mybatis-plus-的问题持久层逻辑泄露">一、MyBatis Plus 的问题：持久层逻辑泄露<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E4%B8%80mybatis-plus-%E7%9A%84%E9%97%AE%E9%A2%98%E6%8C%81%E4%B9%85%E5%B1%82%E9%80%BB%E8%BE%91%E6%B3%84%E9%9C%B2" class="hash-link" aria-label="一、MyBatis Plus 的问题：持久层逻辑泄露的直接链接" title="一、MyBatis Plus 的问题：持久层逻辑泄露的直接链接" translate="no">​</a></h2>
<p>MyBatis Plus 最大的问题，不是功能不够强大，而是<strong>侵入了不该侵入的地方</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="问题的本质service-层被污染">问题的本质：Service 层被污染<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E9%97%AE%E9%A2%98%E7%9A%84%E6%9C%AC%E8%B4%A8service-%E5%B1%82%E8%A2%AB%E6%B1%A1%E6%9F%93" class="hash-link" aria-label="问题的本质：Service 层被污染的直接链接" title="问题的本质：Service 层被污染的直接链接" translate="no">​</a></h3>
<p>看看这段代码：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">// ═══════════════════════════════════════════</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// MyBatis Plus 方式（Service 层 50 行）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// ═══════════════════════════════════════════</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">@Service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public class OrderService {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    public List&lt;Order&gt; searchOrders(OrderQueryDTO query) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        LambdaQueryWrapper&lt;Order&gt; wrapper = new LambdaQueryWrapper&lt;&gt;();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 状态筛选</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getStatus() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.eq(Order::getStatus, query.getStatus());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 时间范围</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getStartTime() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.ge(Order::getCreateTime, query.getStartTime());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getEndTime() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.le(Order::getCreateTime, query.getEndTime());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 金额范围</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getMinAmount() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.ge(Order::getTotalAmount, query.getMinAmount());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getMaxAmount() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.le(Order::getTotalAmount, query.getMaxAmount());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 支付方式</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getPayType() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.eq(Order::getPayType, query.getPayType());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 配送方式</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getDeliveryType() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.eq(Order::getDeliveryType, query.getDeliveryType());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 关键词搜索（多字段）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (StringUtils.isNotBlank(query.getKeyword())) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.and(w -&gt; w</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                .like(Order::getOrderNo, query.getKeyword())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                .or()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                .like(Order::getUserName, query.getKeyword())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                .or()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                .like(Order::getUserPhone, query.getKeyword())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            );</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // ...（省略类似的 20 行条件判断）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        wrapper.orderByDesc(Order::getCreateTime);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return orderMapper.selectList(wrapper);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>这段代码有什么问题？</p>
<p><strong>1. Service 层混杂大量数据库逻辑</strong></p>
<ul>
<li class="">数据库字段名硬编码：<code>Order::getStatus</code>、<code>Order::getCreateTime</code></li>
<li class="">查询条件构建：<code>wrapper.eq()</code>、<code>wrapper.ge()</code>、<code>wrapper.like()</code></li>
<li class="">这些都是持久层的关注点，不应该出现在业务层</li>
</ul>
<p><strong>2. 代码难以复用和测试</strong></p>
<ul>
<li class="">相似的 Wrapper 构建代码散落在各个 Service 方法中</li>
<li class="">单元测试要 mock Mapper，还要验证 Wrapper 的正确性</li>
<li class="">查询逻辑和业务逻辑耦合在一起</li>
</ul>
<p><strong>3. 维护成本快速上升</strong></p>
<ul>
<li class="">随着查询条件增加，Service 方法越来越长</li>
<li class="">业务逻辑被淹没在查询构建代码中</li>
<li class="">新人看代码要花很长时间才能找到核心逻辑</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="架构边界被破坏">架构边界被破坏<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E6%9E%B6%E6%9E%84%E8%BE%B9%E7%95%8C%E8%A2%AB%E7%A0%B4%E5%9D%8F" class="hash-link" aria-label="架构边界被破坏的直接链接" title="架构边界被破坏的直接链接" translate="no">​</a></h3>
<p>传统的三层架构应该是这样的：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">┌─────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  Controller 层                          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ├─ 接收 HTTP 请求                      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └─ 调用 Service                        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────────────────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">┌─────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  Service 层（业务逻辑层）                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ├─ 业务流程编排                        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ├─ 事务控制                            │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └─ 调用 DAO                            │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────────────────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">┌─────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  DAO 层（数据访问层）                    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ├─ 封装数据库访问                      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └─ SQL 执行                            │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────────────────────────────────┘</span><br></span></code></pre></div></div>
<p>但 MyBatis Plus 的实际情况是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">┌─────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  Service 层（混乱层）                    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ├─ 业务流程编排                        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ├─ Wrapper 构建 ◄── 数据库逻辑！       │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ├─ 字段名硬编码 ◄── 数据库逻辑！       │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ├─ 条件拼装     ◄── 数据库逻辑！       │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └─ null 判断    ◄── 数据库逻辑！       │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────────────────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">┌─────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  Mapper 接口（几乎是摆设）               │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └─ 只提供基础 CRUD 方法                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────────────────────────────────┘</span><br></span></code></pre></div></div>
<p><strong>持久层逻辑泄露到了 Service 层！</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="更致命的问题接管成本高">更致命的问题：接管成本高<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E6%9B%B4%E8%87%B4%E5%91%BD%E7%9A%84%E9%97%AE%E9%A2%98%E6%8E%A5%E7%AE%A1%E6%88%90%E6%9C%AC%E9%AB%98" class="hash-link" aria-label="更致命的问题：接管成本高的直接链接" title="更致命的问题：接管成本高的直接链接" translate="no">​</a></h3>
<p>当你需要优化 SQL（比如加 JOIN）时，会发现：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">MyBatis Plus 的接管路径：</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step 1: Service 层有 50 行 Wrapper 代码</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step 2: 决定写 XML 优化 SQL</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step 3: 改 Service 代码（！）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        从 selectList(wrapper)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        改为 selectByCustomQuery(...)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step 4: 调用方可能也要改</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">接管成本：高 ❌</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">风险：大 ❌</span><br></span></code></pre></div></div>
<p>这就是 MyBatis Plus 的核心问题：<strong>快是快了，但把代码写脏了。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二jpa-的问题灵活性不够">二、JPA 的问题：灵活性不够<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E4%BA%8Cjpa-%E7%9A%84%E9%97%AE%E9%A2%98%E7%81%B5%E6%B4%BB%E6%80%A7%E4%B8%8D%E5%A4%9F" class="hash-link" aria-label="二、JPA 的问题：灵活性不够的直接链接" title="二、JPA 的问题：灵活性不够的直接链接" translate="no">​</a></h2>
<p>你可能会说："那 JPA 呢？它不用写 Wrapper，应该更好吧？"</p>
<p>JPA 确实避免了 Wrapper 的问题，但它有自己的痛点。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="痛点-1动态查询很繁琐">痛点 1：动态查询很繁琐<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E7%97%9B%E7%82%B9-1%E5%8A%A8%E6%80%81%E6%9F%A5%E8%AF%A2%E5%BE%88%E7%B9%81%E7%90%90" class="hash-link" aria-label="痛点 1：动态查询很繁琐的直接链接" title="痛点 1：动态查询很繁琐的直接链接" translate="no">​</a></h3>
<p>JPA 处理动态查询，只能用 Specification：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">public Specification&lt;User&gt; buildSpec(UserQueryDTO query) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return (root, criteriaQuery, cb) -&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        List&lt;Predicate&gt; predicates = new ArrayList&lt;&gt;();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getName() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            predicates.add(cb.like(root.get("name"), "%" + query.getName() + "%"));</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getMinAge() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            predicates.add(cb.gt(root.get("age"), query.getMinAge()));</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getStatus() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            predicates.add(cb.equal(root.get("status"), query.getStatus()));</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 字段名是字符串，没有类型安全</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 写起来很啰嗦</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return cb.and(predicates.toArray(new Predicate[0]));</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    };</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 使用</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">List&lt;User&gt; users = userRepository.findAll(buildSpec(query));</span><br></span></code></pre></div></div>
<p>问题：</p>
<ul>
<li class="">字段名是字符串（<code>root.get("name")</code>），没有类型安全</li>
<li class="">代码啰嗦，远不如 SQL 直观</li>
<li class="">Specification 不易复用</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="痛点-2黑盒运行时">痛点 2：黑盒运行时<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E7%97%9B%E7%82%B9-2%E9%BB%91%E7%9B%92%E8%BF%90%E8%A1%8C%E6%97%B6" class="hash-link" aria-label="痛点 2：黑盒运行时的直接链接" title="痛点 2：黑盒运行时的直接链接" translate="no">​</a></h3>
<p>JPA 最被诟病的是它的"魔法"：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">@Transactional</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public void updateUser(Long userId, String newName) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    User user = userRepository.findById(userId).get();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    user.setName(newName);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 没有显式 save()，但数据库已经更新了？！</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 什么时候执行的 SQL？</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 执行了哪些 SQL？</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 你完全不知道...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>这段代码背后可能发生了：</p>
<ol>
<li class="">SELECT 查询</li>
<li class="">脏检查</li>
<li class="">事务提交时自动 UPDATE</li>
<li class="">可能还触发了懒加载...</li>
</ol>
<p><strong>你无法精确控制 SQL 的执行时机。</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="痛点-3n1-问题">痛点 3：N+1 问题<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E7%97%9B%E7%82%B9-3n1-%E9%97%AE%E9%A2%98" class="hash-link" aria-label="痛点 3：N+1 问题的直接链接" title="痛点 3：N+1 问题的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">List&lt;User&gt; users = userRepository.findAll();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">for (User user : users) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 看起来只是取个属性，实际上...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    List&lt;Order&gt; orders = user.getOrders(); // ← 触发 SQL！</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 如果有 100 个用户，这里会执行 100 次 SQL！</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="痛点-4接管成本更高">痛点 4：接管成本更高<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E7%97%9B%E7%82%B9-4%E6%8E%A5%E7%AE%A1%E6%88%90%E6%9C%AC%E6%9B%B4%E9%AB%98" class="hash-link" aria-label="痛点 4：接管成本更高的直接链接" title="痛点 4：接管成本更高的直接链接" translate="no">​</a></h3>
<p>当需要手写 SQL 优化时：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">// Step 1: 写 Native SQL</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">@Query(value = "SELECT u.*, o.* FROM user u " +</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">               "LEFT JOIN orders o ON u.id = o.user_id " +</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">               "WHERE u.status = ?1",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       nativeQuery = true)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">List&lt;Object[]&gt; findUsersWithOrders(Integer status);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// Step 2: 结果映射变复杂</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 返回的是 Object[]，需要手动映射...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 或者配置 @SqlResultSetMapping...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 总之，很麻烦！</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">接管成本：极高 ❌</span><br></span></code></pre></div></div>
<p>JPA 的问题总结：<strong>看起来很美好，但失去了对 SQL 的控制。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三mybatis两难的选择">三、MyBatis：两难的选择<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E4%B8%89mybatis%E4%B8%A4%E9%9A%BE%E7%9A%84%E9%80%89%E6%8B%A9" class="hash-link" aria-label="三、MyBatis：两难的选择的直接链接" title="三、MyBatis：两难的选择的直接链接" translate="no">​</a></h2>
<p>经过对比，你会发现 MyBatis 其实很不错：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mybatis-的优点">MyBatis 的优点<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#mybatis-%E7%9A%84%E4%BC%98%E7%82%B9" class="hash-link" aria-label="MyBatis 的优点的直接链接" title="MyBatis 的优点的直接链接" translate="no">​</a></h3>
<p><strong>1. 架构清晰</strong></p>
<ul>
<li class="">持久层逻辑在 DAO + XML</li>
<li class="">Service 层纯净，只有业务逻辑</li>
<li class="">边界清晰，职责明确</li>
</ul>
<p><strong>2. SQL 完全可控</strong></p>
<ul>
<li class="">SQL 写在 XML 里，一目了然</li>
<li class="">可以精确优化每一个查询</li>
<li class="">没有黑盒，没有魔法</li>
</ul>
<p><strong>3. 接管成本为零</strong></p>
<ul>
<li class="">因为一开始就是手写 SQL</li>
<li class="">不存在"接管"的问题</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="对比表格">对比表格<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E5%AF%B9%E6%AF%94%E8%A1%A8%E6%A0%BC" class="hash-link" aria-label="对比表格的直接链接" title="对比表格的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>维度</th><th>MyBatis Plus</th><th>JPA</th><th>MyBatis</th></tr></thead><tbody><tr><td><strong>Service 层代码</strong></td><td>混杂 Wrapper</td><td>理想情况纯净</td><td>纯业务逻辑 ✅</td></tr><tr><td><strong>持久层逻辑位置</strong></td><td>Service 层 ❌</td><td>Repository</td><td>DAO + XML ✅</td></tr><tr><td><strong>SQL 可见性</strong></td><td>运行时生成</td><td>黑盒 ❌</td><td>XML 可见 ✅</td></tr><tr><td><strong>SQL 可控性</strong></td><td>中等</td><td>差 ❌</td><td>完全可控 ✅</td></tr><tr><td><strong>动态查询</strong></td><td>Wrapper（易写）</td><td>Specification（繁琐）</td><td><code>&lt;if&gt;</code> 标签（清晰）✅</td></tr><tr><td><strong>接管成本</strong></td><td>高 ❌</td><td>极高 ❌</td><td>无需接管 ✅</td></tr><tr><td><strong>学习曲线</strong></td><td>低</td><td>高</td><td>中等</td></tr></tbody></table>
<p><strong>MyBatis 几乎完胜！</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="但是">但是...<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E4%BD%86%E6%98%AF" class="hash-link" aria-label="但是...的直接链接" title="但是...的直接链接" translate="no">​</a></h3>
<p>MyBatis 有一个明显的痛点：</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- 就查个用户列表，也要写 XML... --&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">select</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">findByNameLike</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">resultMap</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">BaseResultMap</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">select</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">select</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">findByAgeGt</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">resultMap</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">BaseResultMap</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  SELECT * FROM user WHERE age &gt; #{age}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">select</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">select</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">findByStatus</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">resultMap</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">BaseResultMap</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  SELECT * FROM user WHERE status = #{status}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">select</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- 类似的 SQL 要写几十个... --&gt;</span><br></span></code></pre></div></div>
<p><strong>简单 CRUD 也要写 XML，确实很繁琐。</strong></p>
<p>这也是为什么很多团队选择 MyBatis Plus 的原因——它解决了这个痛点。</p>
<p>但问题是：<strong>它在解决繁琐的同时，破坏了架构。</strong></p>
<p>能否有一种方式：</p>
<ul>
<li class="">✅ 像 MyBatis 一样架构清晰</li>
<li class="">✅ 像 MyBatis Plus 一样简单 CRUD 不用写 XML</li>
<li class="">✅ 保留 MyBatis 的 SQL 可控性</li>
</ul>
<p><strong>能否两全其美？</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四mybatisgx保留优点解决痛点">四、MyBatisGX：保留优点，解决痛点<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E5%9B%9Bmybatisgx%E4%BF%9D%E7%95%99%E4%BC%98%E7%82%B9%E8%A7%A3%E5%86%B3%E7%97%9B%E7%82%B9" class="hash-link" aria-label="四、MyBatisGX：保留优点，解决痛点的直接链接" title="四、MyBatisGX：保留优点，解决痛点的直接链接" translate="no">​</a></h2>
<p>这就是 MyBatisGX 的设计初衷。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心设计思想">核心设计思想<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3" class="hash-link" aria-label="核心设计思想的直接链接" title="核心设计思想的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">MyBatisGX = MyBatis 的架构清晰 + JPA 的开发便利 - JPA 的黑盒</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">目标：</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">1. 保留 MyBatis 的 SQL 可控性</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">2. 解决简单 CRUD 的繁琐性</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">3. 避免持久层逻辑泄露到 Service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">4. 接管成本保持为零</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="关键特性-1持久层逻辑不泄露">关键特性 1：持久层逻辑不泄露<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E5%85%B3%E9%94%AE%E7%89%B9%E6%80%A7-1%E6%8C%81%E4%B9%85%E5%B1%82%E9%80%BB%E8%BE%91%E4%B8%8D%E6%B3%84%E9%9C%B2" class="hash-link" aria-label="关键特性 1：持久层逻辑不泄露的直接链接" title="关键特性 1：持久层逻辑不泄露的直接链接" translate="no">​</a></h3>
<p>这是 MyBatisGX 最核心的差异化优势。</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">// ═══════════════════════════════════════════</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// MyBatis Plus 方式（Service 层 50 行）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// ═══════════════════════════════════════════</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">@Service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public class OrderService {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    public List&lt;Order&gt; searchOrders(OrderQueryDTO query) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 50 行 Wrapper 构建代码...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        LambdaQueryWrapper&lt;Order&gt; wrapper = new LambdaQueryWrapper&lt;&gt;();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (query.getStatus() != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            wrapper.eq(Order::getStatus, query.getStatus());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // ...（省略 40 行）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return orderMapper.selectList(wrapper);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// ═══════════════════════════════════════════</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// MyBatisGX 方式（Service 层 5 行）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// ═══════════════════════════════════════════</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">@Service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public class OrderService {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    public List&lt;Order&gt; searchOrders(OrderQuery query) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return orderDao.findList(query);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 查询逻辑在 DAO 层</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public interface OrderDao extends SimpleDao&lt;Order, OrderQuery, Long&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @Dynamic</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    List&lt;Order&gt; findList(OrderQuery query);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// QueryEntity 定义（类型安全）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">@QueryEntity</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public class OrderQuery extends Order {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Integer statusEq;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Date createTimeGe;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Date createTimeLe;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private BigDecimal totalAmountGe;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private BigDecimal totalAmountLe;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private String orderNoLike;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private String userNameLike;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // ...null 字段自动忽略</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p><strong>从 50 行缩减到 5 行，Service 层纯净！</strong></p>
<p>优势：</p>
<ul>
<li class="">✅ Service 层只有业务逻辑</li>
<li class="">✅ 持久层逻辑收敛到 DAO</li>
<li class="">✅ 类型安全，易于测试</li>
<li class="">✅ 查询逻辑可复用</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="关键特性-2简单-sql-自动生成">关键特性 2：简单 SQL 自动生成<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E5%85%B3%E9%94%AE%E7%89%B9%E6%80%A7-2%E7%AE%80%E5%8D%95-sql-%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90" class="hash-link" aria-label="关键特性 2：简单 SQL 自动生成的直接链接" title="关键特性 2：简单 SQL 自动生成的直接链接" translate="no">​</a></h3>
<p>解决 MyBatis 的繁琐问题。</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">public interface UserDao extends SimpleDao&lt;User, UserQuery, Long&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 简单查询（不需要写 XML）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    List&lt;User&gt; findByNameLike(String name);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 复杂条件组合</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    List&lt;User&gt; findByNameLikeAndAgeGtAndStatusIn(</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        String name,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Integer age,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        List&lt;Integer&gt; statusList</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    );</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 排序</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    List&lt;User&gt; findByStatusOrderByCreateTimeDesc(Integer status);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 分页</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Page&lt;User&gt; findByAgeGt(Integer age, Pageable pageable);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 框架自动生成 SQL，无需写 XML ✅</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 但如果需要优化，XML 随时可以接管 ✅</span><br></span></code></pre></div></div>
<p>支持的查询关键字：</p>
<ul>
<li class="">比较：<code>Lt</code>、<code>Lteq</code>、<code>Gt</code>、<code>Gteq</code>、<code>Eq</code></li>
<li class="">模糊：<code>Like</code>、<code>StartingWith</code>、<code>EndingWith</code></li>
<li class="">范围：<code>Between</code>、<code>In</code></li>
<li class="">空值：<code>IsNull</code>、<code>IsNotNull</code></li>
<li class="">逻辑：<code>And</code>、<code>Or</code></li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="关键特性-3接管成本为零">关键特性 3：接管成本为零<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E5%85%B3%E9%94%AE%E7%89%B9%E6%80%A7-3%E6%8E%A5%E7%AE%A1%E6%88%90%E6%9C%AC%E4%B8%BA%E9%9B%B6" class="hash-link" aria-label="关键特性 3：接管成本为零的直接链接" title="关键特性 3：接管成本为零的直接链接" translate="no">​</a></h3>
<p>保留 MyBatis 的核心优势。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">场景：性能需要优化，需要写 JOIN</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">┌──────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ MyBatis Plus 的接管路径              │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├──────────────────────────────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ Step 1: Service 层有 50 行 Wrapper   │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ Step 2: 写 XML                       │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ Step 3: 改 Service 代码（！）        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│         orderMapper.selectList()     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│         改为 selectByCustomQuery()   │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ Step 4: 调用方可能也要改             │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│                                      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ 接管成本：高 ❌                      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ 风险：大 ❌                          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└──────────────────────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">┌──────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ MyBatisGX 的接管路径                 │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├──────────────────────────────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ Step 1: DAO 方法                     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   findByShopIdAndStatus(...)         │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│                                      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ Step 2: 写 XML（Service 不动！）     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   &lt;select id="findByShopIdAndStatus"&gt;│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│     SELECT o.*, s.name, u.name       │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│     FROM order o                     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│     LEFT JOIN shop s ON ...          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│     LEFT JOIN user u ON ...          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│     WHERE o.shop_id = #{shopId}      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       AND o.status = #{status}       │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   &lt;/select&gt;                          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│                                      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ 接管成本：零 ✅                      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ 风险：低 ✅                          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ Service 代码：一行都不用改 ✅        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└──────────────────────────────────────┘</span><br></span></code></pre></div></div>
<p><strong>XML 优先级最高，随时可以覆盖自动生成的 SQL。</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="综合对比表格">综合对比表格<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E7%BB%BC%E5%90%88%E5%AF%B9%E6%AF%94%E8%A1%A8%E6%A0%BC" class="hash-link" aria-label="综合对比表格的直接链接" title="综合对比表格的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>维度</th><th>MP</th><th>JPA</th><th>MyBatis</th><th>MyBatisGX</th></tr></thead><tbody><tr><td><strong>架构清晰</strong></td><td>❌ 泄露</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td><strong>简单 CRUD</strong></td><td>✅ 不用写 XML</td><td>✅</td><td>❌ 要写 XML</td><td>✅ 不用写</td></tr><tr><td><strong>SQL 可控</strong></td><td>⚠️ 中等</td><td>❌ 黑盒</td><td>✅ 完全可控</td><td>✅ 完全可控</td></tr><tr><td><strong>动态查询</strong></td><td>✅ Wrapper 易写</td><td>⚠️ Specification 繁琐</td><td>✅ <code>&lt;if&gt;</code> 清晰</td><td>✅ Dynamic注解</td></tr><tr><td><strong>接管成本</strong></td><td>❌ 高</td><td>❌ 极高</td><td>✅ 无需接管</td><td>✅ 零成本</td></tr><tr><td><strong>类型安全</strong></td><td>✅ Lambda</td><td>✅</td><td>⚠️ 部分</td><td>✅</td></tr><tr><td><strong>学习曲线</strong></td><td>低</td><td>高</td><td>中</td><td>低</td></tr></tbody></table>
<p><strong>MyBatisGX 在各个维度都有竞争力！</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="其他特性">其他特性<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E5%85%B6%E4%BB%96%E7%89%B9%E6%80%A7" class="hash-link" aria-label="其他特性的直接链接" title="其他特性的直接链接" translate="no">​</a></h3>
<p><strong>1. 声明式关联查询</strong></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">@Entity</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">@Table(name = "user")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public class User {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @Id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Long id;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 声明式关联 + 批量抓取避免 N+1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @ManyToOne(fetch = FetchType.LAZY)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @JoinColumn(name = "org_id")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @Fetch(FetchMode.BATCH)  // 批量查询避免 N+1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Org org;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 四种抓取模式：SIMPLE / BATCH / JOIN / NONE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 精确控制性能，不会有 JPA 的 N+1 问题</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// NONE 模式：完全不查询，手动控制</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">@ManyToOne</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">@Fetch(FetchMode.NONE)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">private Avatar avatar;  // 从缓存/CDN 加载，不走数据库</span><br></span></code></pre></div></div>
<p><strong>2. 逻辑删除、乐观锁</strong></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">@Entity</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public class User {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @LogicDelete</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Integer deleted;  // 0: 正常，1: 已删除</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @Version</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Integer version;  // 乐观锁</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 删除时自动转换为 UPDATE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">userDao.deleteById(userId);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// UPDATE user SET deleted = 1 WHERE id = ? AND deleted = 0</span><br></span></code></pre></div></div>
<p><strong>3. 字段自动填充</strong></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">@Entity</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public class User {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @GeneratedValue(CreateTimeProcessor.class)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Date createTime;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @GeneratedValue(UpdateTimeProcessor.class)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Date updateTime;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 插入/更新时自动填充</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="适用场景">适用场景<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="适用场景的直接链接" title="适用场景的直接链接" translate="no">​</a></h3>
<p>MyBatisGX 特别适合：</p>
<ul>
<li class="">✅ 需要长期维护的项目（&gt; 6 个月）</li>
<li class="">✅ 关心代码可维护性和架构清晰度</li>
<li class="">✅ 不想 Service 层被数据库逻辑污染</li>
<li class="">✅ 需要保留 SQL 完全控制权</li>
<li class="">✅ 从 MyBatis 项目升级（100% 兼容）</li>
</ul>
<p><strong>如果你的项目：</strong></p>
<ul>
<li class="">只活 3 个月（能跑就行）→ 用 MyBatis Plus 没问题</li>
<li class="">需要跨 10 种数据库 → 用 JPA 更合适</li>
<li class="">都是复杂 SQL 和报表 → 用 MyBatis 最合适</li>
</ul>
<p><strong>但如果你的项目需要长期维护，关心架构质量，那 MyBatisGX 可能是最优解。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五总结如何选择">五、总结：如何选择<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E4%BA%94%E6%80%BB%E7%BB%93%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9" class="hash-link" aria-label="五、总结：如何选择的直接链接" title="五、总结：如何选择的直接链接" translate="no">​</a></h2>
<p>回到文章开头的问题：<strong>快速开发 vs 架构清晰，能否两全其美？</strong></p>
<p>答案是：<strong>可以。</strong></p>
<p>但前提是：<strong>不要把持久层逻辑写到业务层。</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="选择框架--选择技术债务">选择框架 = 选择技术债务<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E9%80%89%E6%8B%A9%E6%A1%86%E6%9E%B6--%E9%80%89%E6%8B%A9%E6%8A%80%E6%9C%AF%E5%80%BA%E5%8A%A1" class="hash-link" aria-label="选择框架 = 选择技术债务的直接链接" title="选择框架 = 选择技术债务的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">MyBatis Plus:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  前期：快速开发 ✅</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  后期：重构困难 ❌</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  原因：持久层逻辑泄露</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">JPA:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  前期：开发便利 ✅</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  后期：SQL 失控 ❌</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  原因：黑盒运行时</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">MyBatis:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  前期：写 XML 繁琐 ❌</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  后期：架构清晰 ✅</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  原因：强制分层</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">MyBatisGX:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  前期：快速开发 ✅</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  后期：架构清晰 ✅</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  原因：DAO 层收敛 + 自动生成</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心观点">核心观点<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E6%A0%B8%E5%BF%83%E8%A7%82%E7%82%B9" class="hash-link" aria-label="核心观点的直接链接" title="核心观点的直接链接" translate="no">​</a></h3>
<blockquote>
<p><strong>不要用短期效率换长期债务。</strong></p>
<p><strong>好的框架不应该破坏架构，而应该保护架构。</strong></p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="给技术选型者的建议">给技术选型者的建议<a href="http://www.mybatisgx.com/blog/2026/03/20/why-i-prefer-mybatis#%E7%BB%99%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B%E8%80%85%E7%9A%84%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="给技术选型者的建议的直接链接" title="给技术选型者的建议的直接链接" translate="no">​</a></h3>
<p>如果你正在选型，问自己三个问题：</p>
<ol>
<li class="">
<p><strong>这个项目会活多久？</strong></p>
<ul>
<li class="">如果只活 3 个月，怎么快怎么来</li>
<li class="">如果要活 1 年以上，认真考虑架构</li>
</ul>
</li>
<li class="">
<p><strong>Service 层会被污染吗？</strong></p>
<ul>
<li class="">如果会，6 个月后你会后悔</li>
<li class="">如果不会，那就放心用</li>
</ul>
</li>
<li class="">
<p><strong>遇到性能问题时，能轻松优化吗？</strong></p>
<ul>
<li class="">如果要改 Service 代码，风险很大</li>
<li class="">如果只改 XML，风险很小</li>
</ul>
</li>
</ol>
<p><strong>选择框架，就是选择项目的未来。</strong></p>
<hr>
<p><strong>MyBatisGX 项目地址</strong>：<a href="https://github.com/cris-xue/mybatisgx" target="_blank" rel="noopener noreferrer" class="">https://github.com/cris-xue/mybatisgx</a></p>
<p><strong>在线文档</strong>：<a href="http://www.mybatisgx.com/" target="_blank" rel="noopener noreferrer" class="">http://www.mybatisgx.com</a></p>
<p>你觉得MyBatisGX可以帮助到你，欢迎 Star 和试用 MyBatisGX。</p>
<p>如果你有不同的看法，欢迎在评论区讨论。</p>]]></content:encoded>
            <category>mybatis</category>
            <category>mybatis-plus</category>
            <category>jpa</category>
            <category>架构</category>
            <category>orm</category>
        </item>
    </channel>
</rss>