首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

MyBatis 多表關聯查詢與分頁實戰:resultMap 進階映射與 PageHelper 整合思路

     編輯:Java 相關知識

多表關聯查詢:resultMap 映射關聯對象

實體類之間存在關聯關係(一對一、一對多)時,MyBatis 通過 <resultMap> 的 <association>(一對一)和 <collection>(一對多)標籤實現關聯對象自動映射。

實體類設計

public class Website {
    private int id;
    private String name;
    private int visitorId;
    private int status;
    private Date createTime;
    private Visitor visitor;  // 關聯對象
    
    // getter/setter/toString...
}

關聯查詢映射配置

<mapper namespace="david.mybatis.demo.IWebsiteOperation">
  <!-- 關聯查詢:Website + Visitor -->
  <select id="query" parameterType="int" resultMap="websiteRs">
    select 
      w.id as siteId, w.name as siteName, 
      v.id as visitorId, v.name as visitorName,
      w.status as siteStatus, w.createtime as siteCreateTime
    from Website w
    inner join Visitor v on w.visitorid = v.id
    where w.status > 0 and w.id = #{id}
  </select>

  <resultMap type="Website" id="websiteRs">
    <id column="siteId" property="id" />
    <result column="siteName" property="name" />
    <result column="siteStatus" property="status" />
    <result column="siteCreateTime" property="createTime" />
    <!-- 關鍵:association 映射一對一關聯對象 -->
    <association property="visitor" javaType="Visitor" resultMap="visitorRs" />
  </resultMap>

  <resultMap type="Visitor" id="visitorRs">
    <id column="visitorId" property="id" />
    <result column="visitorName" property="name" />
  </resultMap>
</mapper>

關鍵點解析

  • 列別名消除衝突:Website 和 Visitor 都有 id/name 字段,必須起別名(如 siteId、visitorId)避免映射錯亂
  • <association>:property 對應實體屬性名(visitor),javaType 指定關聯類型,resultMap 引用關聯對象的映射定義
  • 嵌套 resultMap 復用:Visitor 的映射單獨定義,供多處復用

分頁查詢實現方案

方案一:手動 LIMIT 分頁(MySQL)

定義分頁參數對象:

public class PagenateArgs {
    private int pageIndex;    // 頁碼(從 0 開始)
    private int pageSize;     // 每頁條數
    private int pageStart;    // 起始行 = pageIndex * pageSize
    private String orderFieldStr;   // 排序字段
    private String orderDirectionStr; // ASC/DESC
    
    public PagenateArgs(int pageIndex, int pageSize, String orderField, String orderDirection) {
        this.pageIndex = pageIndex;
        this.pageSize = pageSize;
        this.orderFieldStr = orderField;
        this.orderDirectionStr = orderDirection;
        this.pageStart = pageIndex * pageSize;
    }
    // getter...
}

Mapper 接口與 XML:

// 接口
List<Visitor> getListByPagenate(PagenateArgs args);


<select id="getListByPagenate" parameterType="PagenateArgs" resultType="Visitor">
  select * from (
    <include refid="getListSql" />
    <include refid="orderBySql"/>
  ) t
  <if test="pageStart > -1 and pageSize > -1">
    limit #{pageStart}, #{pageSize}
  </if>
</select>

<sql id="orderBySql"> order by ${orderFieldStr} ${orderDirectionStr} </sql>

調用示例:

PagenateArgs args = new PagenateArgs(1, 5, "id", "DESC"); // 第2頁,每頁5條,ID倒序
List<Visitor> pageData = vOperation.getListByPagenate(args);

方案二:帶行號的分頁查詢

MySQL 8.0+ 支持窗口函數,舊版本可用變量模擬 ROW_NUMBER():

<resultMap type="VisitorWithRn" id="visitorWithRnRs">
  <id column="Id" property="id" />
  <result column="Name" property="name" />
  <result column="Email" property="email" />
  <result column="Status" property="status" />
  <result column="CreateTime" property="createTime" />
  <result column="Rownum" property="rownum" />  <!-- 行號映射 -->
</resultMap>

<select id="getListByPagenateWithRn" resultMap="visitorWithRnRs">
  select t.Rownum, t.Id, t.Name, t.Email, t.Status, t.CreateTime from (
    <include refid="getListSqlContainsRn" />
    <include refid="orderBySql"/>
  ) t
  <if test="pageStart > -1 and pageSize > -1">
    limit #{pageStart}, #{pageSize}
  </if>
</select>

<sql id="getListSqlContainsRn">
  select @rownum:=@rownum+1 as Rownum, result.id, result.name, result.email, result.status, result.createTime
  from (select @rownum:=0, Visitor.* from Visitor where status > 0) result
</sql>

方案三:PageHelper 插件(生產環境推薦)

引入依賴:

<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.3.3</version>
</dependency>

配置攔截器(mybatis-config.xml):

<plugins>
  <plugin interceptor="com.github.pagehelper.PageInterceptor">
    <property name="helperDialect" value="mysql"/>
    <property name="reasonable" value="true"/>
    <property name="supportMethodsArguments" value="true"/>
    <property name="params" value="count=countSql"/>
  </plugin>
</plugins>

業務代碼極簡:

PageHelper.startPage(pageNum, pageSize, "id DESC");
List<Visitor> list = visitorMapper.getList(); // 自動攔截添加 LIMIT
PageInfo<Visitor> pageInfo = new PageInfo<>(list); // 獲取總數、總頁數等

分頁方案對比與選型建議

方案優點缺點適用場景
手寫 LIMIT零依賴、完全可控、學習價值高需手動計數總條數、跨數據庫兼容性差簡單項目、教學演示、特殊分頁邏輯
PageHelper非侵入式、支持多種數據庫、功能豐富引入第三方依賴、原理較複雜企業級項目、標準分頁需求、團隊協作
MyBatis-Plus內置分頁、CRUD 自動生成、條件構造器框架侵入性強、學習成本較高新項目、追求開發效率、標準化開發

最佳實踐總結

  1. 關聯查詢必用列別名:避免同名列映射衝突
  2. 優先使用 <association>/<collection>:聲明式映射關聯對象,代碼清晰
  3. 分頁參數封裝對象化:PagenateArgs 統一管理分頁、排序參數
  4. 生產環境首選 PageHelper:非侵入式、跨數據庫、功能完善
  5. 大數據量避免 OFFSET 深分頁:改用游標分頁(WHERE id > lastId LIMIT size)
  6. 排序字段白名單校驗:防止 ${orderField} 注入風險

總結

MyBatis 通過強大的 resultMap 機制優雅解決多表關聯映射問題,<association> 處理一對一,<collection> 處理一對多。分頁查詢方面,手寫 LIMIT 適合學習與簡單場景,生產環境強烈建議接入 PageHelper 或 MyBatis-Plus 等成熟分頁插件,實現零侵入、跨數據庫的標準化分頁能力。掌握這兩大核心技能,是 MyBatis 進階開發者的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved