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

MyBatis 進階教程 (二):一對一、一對多關聯映射與分頁查詢實戰

     編輯:Java 相關知識

前言

MyBatis 的強大在於靈活的結果映射機制,支援複雜的對象關聯查詢。本文通過「丈夫-妻子」(一對一)、「鎖-鑰匙」(一對多) 經典案例,詳細講解 <association>、<collection>、嵌套查詢與嵌套結果映射,並實戰分頁查詢與批量操作。

一、一對一關聯映射

場景:一個丈夫對應一個妻子

1. 數據庫設計

CREATE TABLE t_husband (
    id INT PRIMARY KEY AUTO_INCREMENT,
    husband_name VARCHAR(50)
);

CREATE TABLE t_wife (
    id INT PRIMARY KEY AUTO_INCREMENT,
    wife_name VARCHAR(50),
    fk_husband_id INT,
    FOREIGN KEY (fk_husband_id) REFERENCES t_husband(id)
);

-- 測試數據
INSERT INTO t_husband VALUES (NULL, 'Tom');
INSERT INTO t_wife VALUES (NULL, 'Kitty', 1);

2. 實體類設計 (雙向關聯)

public class Husband {
    private Integer id;
    private String name;
    private Wife wife; // 關聯對象
    // getter/setter/toString
}

public class Wife {
    private Integer id;
    private String name;
    private Husband husband; // 反向關聯
    // getter/setter/toString
}

3. Mapper 介面

public interface HusbandMapper {
    Husband selectById(int id);
    Husband selectHusbandWithWife(int id); // 關聯查詢
}

4. 映射文件 - 嵌套查詢方式 (推薦)

<mapper namespace="com.example.mapper.HusbandMapper">
    <!-- 結果映射:丈夫 + 妻子 -->
    <resultMap id="husbandWifeMap" type="Husband">
        <id property="id" column="id"/>
        <result property="name" column="husband_name"/>
        
        <!-- association: 一對一關聯 -->
        <association property="wife" 
                     column="id" 
                     javaType="Wife"
                     select="com.example.mapper.WifeMapper.selectWifeByHusbandId">
        </association>
    </resultMap>
    
    <select id="selectById" resultType="Husband">
        SELECT * FROM t_husband WHERE id = #{id}
    </select>
    
    <select id="selectHusbandWithWife" resultMap="husbandWifeMap">
        SELECT * FROM t_husband WHERE id = #{id}
    </select>
</mapper>

5. 妻子 Mapper (被關聯查詢)

<mapper namespace="com.example.mapper.WifeMapper">
    <select id="selectWifeByHusbandId" resultType="Wife">
        SELECT * FROM t_wife WHERE fk_husband_id = #{id}
    </select>
</mapper>

6. 關鍵屬性解析

屬性說明
property實體類中關聯屬性名
column主查詢結果中作為參數的列名
javaType關聯對象的完整類名或別名
select關聯查詢的映射語句 ID (命名空間.方法名)
fetchTypelazy (預設) / eager - 延遲/立即加載

二、一對多關聯映射

場景:一把鎖對應多把鑰匙

1. 數據庫設計

CREATE TABLE t_lock (
    id INT PRIMARY KEY AUTO_INCREMENT,
    lock_name VARCHAR(50)
);

CREATE TABLE t_key (
    id INT PRIMARY KEY AUTO_INCREMENT,
    key_name VARCHAR(50),
    fk_lock_id INT,
    FOREIGN KEY (fk_lock_id) REFERENCES t_lock(id)
);

2. 實體類

public class Lock {
    private Integer id;
    private String name;
    private List<Key> keys; // 集合關聯
    // getter/setter/toString
}

public class Key {
    private Integer id;
    private String name;
    private Lock lock; // 反向引用
    // getter/setter/toString
}

3. Mapper 介面

public interface LockMapper {
    int insert(Lock lock);
    Lock selectById(int id);
    Lock selectLockWithKeys(int id); // 關聯查詢
}

public interface KeyMapper {
    int batchInsert(@Param("keys") List<Key> keys);
    List<Key> selectKeysByLockId(int lockId);
}

4. 映射文件 - LockMapper.xml

<mapper namespace="com.example.mapper.LockMapper">
    <resultMap id="lockMap" type="Lock">
        <id property="id" column="id"/>
        <result property="name" column="lock_name"/>
    </resultMap>
    
    <resultMap id="lockKeysMap" type="Lock">
        <id property="id" column="id"/>
        <result property="name" column="lock_name"/>
        
        <!-- collection: 一對多關聯 -->
        <collection property="keys" 
                    column="id" 
                    ofType="Key"
                    select="com.example.mapper.KeyMapper.selectKeysByLockId">
        </collection>
    </resultMap>
    
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO t_lock (lock_name) VALUES (#{name})
    </insert>
    
    <select id="selectById" resultMap="lockMap">
        SELECT * FROM t_lock WHERE id = #{id}
    </select>
    
    <select id="selectLockWithKeys" resultMap="lockKeysMap">
        SELECT * FROM t_lock WHERE id = #{id}
    </select>
</mapper>

5. 映射文件 - KeyMapper.xml (含批量插入)

<mapper namespace="com.example.mapper.KeyMapper">
    <resultMap id="keyMap" type="Key">
        <id property="id" column="id"/>
        <result property="name" column="key_name"/>
    </resultMap>
    
    <!-- 批量插入:foreach 遍歷集合 -->
    <insert id="batchInsert">
        INSERT INTO t_key (key_name, fk_lock_id) VALUES
        <foreach collection="keys" item="key" separator=",">
            (#{key.name}, #{key.lock.id})
        </foreach>
    </insert>
    
    <select id="selectKeysByLockId" resultMap="keyMap">
        SELECT * FROM t_key WHERE fk_lock_id = #{id}
    </select>
</mapper>

6. collection 關鍵屬性

  • ofType:集合泛型類型 (而非 javaType)
  • collection (foreach):遍歷的集合參數名
  • item (foreach):迭代變量名
  • separator (foreach):分隔符

三、分頁查詢與批量操作

1. 分頁工具類

public class PageHelper {
    private static final ThreadLocal<PageParam> LOCAL_PARAM = new ThreadLocal<>();
    
    public static void startPage(int pageNum, int pageSize) {
        LOCAL_PARAM.set(new PageParam(pageNum, pageSize));
    }
    
    public static void clearPage() {
        LOCAL_PARAM.remove();
    }
    
    public static PageParam getPageParam() {
        return LOCAL_PARAM.get();
    }
    
    public static class PageParam {
        private final int pageNum;
        private final int pageSize;
        public PageParam(int pageNum, int pageSize) {
            this.pageNum = pageNum;
            this.pageSize = pageSize;
        }
        public int getOffset() { return (pageNum - 1) * pageSize; }
        public int getLimit() { return pageSize; }
    }
}

2. 通用分頁查詢 Mapper

public interface UserMapper {
    List<User> selectPage(@Param("params") Map<String, Object> params);
    int selectCount(@Param("params") Map<String, Object> params);
}

3. 動態 SQL 分頁映射

<mapper namespace="com.example.mapper.UserMapper">
    <resultMap id="userMap" type="User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="email" column="email"/>
    </resultMap>
    
    <sql id="commonWhere">
        WHERE 1=1
        <if test="params.username != null and params.username != ''">
            AND username LIKE CONCAT('%', #{params.username}, '%')
        </if>
        <if test="params.email != null and params.email != ''">
            AND email LIKE CONCAT('%', #{params.email}, '%')
        </if>
    </sql>
    
    <select id="selectPage" resultMap="userMap">
        SELECT * FROM t_user
        <include refid="commonWhere"/>
        ORDER BY id DESC
        LIMIT #{params.offset}, #{params.limit}
    </select>
    
    <select id="selectCount" resultType="int">
        SELECT COUNT(*) FROM t_user
        <include refid="commonWhere"/>
    </select>
</mapper>

4. Service 層分頁封裝

public PageResult<User> queryPage(int pageNum, int pageSize, String username, String email) {
    PageHelper.startPage(pageNum, pageSize);
    try {
        Map<String, Object> params = new HashMap<>();
        params.put("offset", PageHelper.getPageParam().getOffset());
        params.put("limit", PageHelper.getPageParam().getLimit());
        params.put("username", username);
        params.put("email", email);
        
        List<User> list = userMapper.selectPage(params);
        int total = userMapper.selectCount(params);
        
        return new PageResult<>(pageNum, pageSize, total, list);
    } finally {
        PageHelper.clearPage();
    }
}

5. 批量操作最佳實踐

// 批量插入
@Insert("")
int batchInsert(@Param("list") List<User> list);

// 批量刪除
@Delete("")
int batchDelete(@Param("ids") List<Integer> ids);

四、關聯查詢優化建議

策略適用場景優點缺點
嵌套查詢 (association/collection + select)關聯數據較大、按需加載支援延遲加載、SQL 簡單N+1 問題、多次查詢
嵌套結果映射 (resultMap + join)關聯數據較小、經常一起使用單次查詢、無 N+1結果集冗余、映射複雜
嵌套結果 + 去重 (resultMap + join + distinct)一對多、需避免主表重複單次查詢、數據準確SQL 複雜、需注意去重

嵌套結果映射範例 (一對多 Join)

<resultMap id="lockKeysJoinMap" type="Lock">
    <id property="id" column="lock_id"/>
    <result property="name" column="lock_name"/>
    <collection property="keys" ofType="Key">
        <id property="id" column="key_id"/>
        <result property="name" column="key_name"/>
    </collection>
</resultMap>

<select id="selectLockWithKeysJoin" resultMap="lockKeysJoinMap">
    SELECT 
        l.id AS lock_id, l.lock_name,
        k.id AS key_id, k.key_name
    FROM t_lock l
    LEFT JOIN t_key k ON l.id = k.fk_lock_id
    WHERE l.id = #{id}
</select>

總結

  • 一對一:使用 <association>,配合 select 實現延遲加載或 join 實現立即加載
  • 一對多:使用 <collection>,注意 ofType 指定泛型類型
  • 批量操作:善用 <foreach>,參數用 @Param 明確命名
  • 分頁查詢:ThreadLocal 傳遞分頁參數,動態 SQL 組裝 WHERE/LIMIT
  • 優化選擇:小數據量用 Join,大數據量用嵌套查詢+延遲加載,必要時引入緩存
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved