MyBatis 配置复用详解:从入门到精通

MyBatis 配置复用详解:从入门到精通

引言

在实际开发中,MyBatis 的 XML 配置文件往往会随着业务复杂度提升而急剧膨胀。大量的重复 SQL 和 resultMap 不仅难以维护,还容易引入错误。本文将深入探讨 MyBatis 的各种配置复用技巧,帮助您写出更优雅、更易维护的持久层代码。

一、SQL 片段复用

SQL 片段复用是最基础也是最常用的复用方式,适用于重复的列名、查询条件、排序分页等场景。

1.1 基础 SQL 片段定义与引用

<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 定义常用列名片段 -->
    <sql id="Base_Column_List">
        id, username, password, email, phone, 
        status, create_time, update_time
    </sql>
    
    <!-- 定义查询条件片段 -->
    <sql id="Where_Clause">
        <where>
            <if test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="email != null and email != ''">
                AND email = #{email}
            </if>
            <if test="status != null">
                AND status = #{status}
            </if>
            <if test="startTime != null">
                AND create_time >= #{startTime}
            </if>
            <if test="endTime != null">
                AND create_time &lt;= #{endTime}
            </if>
        </where>
    </sql>
    
    <!-- 引用片段 -->
    <select id="selectByCondition" resultType="User">
        SELECT 
        <include refid="Base_Column_List"/>
        FROM user
        <include refid="Where_Clause"/>
    </select>
</mapper>

1.2 带参数的 SQL 片段

SQL 片段支持参数传递,使其更加灵活:

<!-- 定义带表别名的列片段 -->
<sql id="Alias_Columns">
    ${alias}.id, ${alias}.username, ${alias}.email, ${alias}.create_time
</sql>

<!-- 定义动态表名片段 -->
<sql id="Dynamic_Table">
    ${tableName} ${alias}
</sql>

<!-- 定义连接条件片段 -->
<sql id="Join_Condition">
    ${mainAlias}.${mainField} = ${joinAlias}.${joinField}
</sql>

<!-- 使用参数化片段 -->
<select id="selectUserWithOrders" resultMap="UserOrderMap">
    SELECT
    <include refid="Alias_Columns">
        <property name="alias" value="u"/>
    </include>,
    o.id as order_id, o.order_no, o.amount
    FROM 
    <include refid="Dynamic_Table">
        <property name="tableName" value="user"/>
        <property name="alias" value="u"/>
    </include>
    LEFT JOIN order o ON 
    <include refid="Join_Condition">
        <property name="mainAlias" value="u"/>
        <property name="mainField" value="id"/>
        <property name="joinAlias" value="o"/>
        <property name="joinField" value="user_id"/>
    </include>
    WHERE u.id = #{id}
</select>

1.3 条件片段的高级用法

<!-- 定义多种条件片段 -->
<sql id="Basic_Conditions">
    <if test="deleted != null">
        AND deleted = #{deleted}
    </if>
    <if test="enabled != null">
        AND enabled = #{enabled}
    </if>
</sql>

<sql id="Time_Range_Condition">
    <if test="timeField != null and startTime != null">
        AND ${timeField} >= #{startTime}
    </if>
    <if test="timeField != null and endTime != null">
        AND ${timeField} &lt;= #{endTime}
    </if>
</sql>

<sql id="Pagination">
    <if test="offset != null and limit != null">
        LIMIT #{offset}, #{limit}
    </if>
    <if test="pageNum != null and pageSize != null">
        LIMIT #{pageNum}, #{pageSize}
    </if>
</sql>

<!-- 组合使用 -->
<select id="searchUsers" resultType="User">
    SELECT * FROM user
    <where>
        <include refid="Basic_Conditions"/>
        <include refid="Time_Range_Condition">
            <property name="timeField" value="create_time"/>
        </include>
        <if test="keyword != null">
            AND (username LIKE CONCAT('%', #{keyword}, '%')
                 OR email LIKE CONCAT('%', #{keyword}, '%'))
        </if>
    </where>
    ORDER BY create_time DESC
    <include refid="Pagination"/>
</select>

1.4 SQL 片段的嵌套使用

<!-- 定义基础片段 -->
<sql id="User_Fields">
    id, username, email
</sql>

<sql id="User_Base_Query">
    SELECT <include refid="User_Fields"/> FROM user
</sql>

<!-- 嵌套构建复杂查询 -->
<sql id="Active_User_Query">
    <include refid="User_Base_Query"/>
    WHERE status = 1 AND last_login_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
</sql>

<sql id="Vip_User_Query">
    <include refid="User_Base_Query"/>
    WHERE vip_level > 0
</sql>

<!-- 使用 -->
<select id="countActiveVipUsers" resultType="int">
    SELECT COUNT(*) FROM (
        <include refid="Active_User_Query"/>
        UNION
        <include refid="Vip_User_Query"/>
    ) AS combined
</select>

二、resultMap 复用

resultMap 复用是处理复杂对象映射的关键技术,可以避免重复定义字段映射关系。

2.1 基础继承复用

<!-- 基础映射定义 -->
<resultMap id="BaseResultMap" type="com.example.entity.User">
    <id property="id" column="id" jdbcType="BIGINT"/>
    <result property="username" column="username" jdbcType="VARCHAR"/>
    <result property="password" column="password" jdbcType="VARCHAR"/>
    <result property="email" column="email" jdbcType="VARCHAR"/>
    <result property="phone" column="phone" jdbcType="VARCHAR"/>
    <result property="status" column="status" jdbcType="INTEGER"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>

<!-- 继承并扩展关联对象 -->
<resultMap id="UserWithRoleMap" extends="BaseResultMap" type="com.example.entity.User">
    <!-- 一对一关联 -->
    <association property="role" javaType="com.example.entity.Role">
        <id property="id" column="role_id"/>
        <result property="name" column="role_name"/>
        <result property="code" column="role_code"/>
        <result property="description" column="role_desc"/>
    </association>
</resultMap>

<!-- 继承并扩展集合对象 -->
<resultMap id="UserWithOrdersMap" extends="BaseResultMap" type="com.example.entity.User">
    <!-- 一对多集合 -->
    <collection property="orders" ofType="com.example.entity.Order" 
                notNullColumn="order_id">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="amount" column="amount"/>
        <result property="status" column="order_status"/>
        <result property="createTime" column="order_create_time"/>
    </collection>
</resultMap>

<!-- 多层继承 -->
<resultMap id="UserFullInfoMap" extends="UserWithRoleMap" type="com.example.entity.User">
    <collection property="permissions" ofType="com.example.entity.Permission">
        <id property="id" column="permission_id"/>
        <result property="name" column="permission_name"/>
        <result property="code" column="permission_code"/>
    </collection>
    <!-- 添加额外属性 -->
    <result property="lastLoginIp" column="last_login_ip"/>
    <result property="lastLoginTime" column="last_login_time"/>
</resultMap>

2.2 引用外部 resultMap

<!-- 定义可复用的关联映射(可在其他文件中) -->
<mapper namespace="com.example.mapper.RoleMapper">
    <resultMap id="RoleMap" type="com.example.entity.Role">
        <id property="id" column="role_id"/>
        <result property="name" column="role_name"/>
        <result property="code" column="role_code"/>
        <result property="permissions" column="id" 
                select="com.example.mapper.PermissionMapper.selectByRoleId"/>
    </resultMap>
</mapper>

<!-- 在当前文件中引用 -->
<mapper namespace="com.example.mapper.UserMapper">
    <resultMap id="UserWithRoleRefMap" extends="BaseResultMap" 
               type="com.example.entity.User">
        <!-- 引用外部 resultMap,注意列别名要匹配 -->
        <association property="role" 
                     resultMap="com.example.mapper.RoleMapper.RoleMap"
                     columnPrefix="role_"/>
        
        <!-- 也可以使用 select 方式引用 -->
        <collection property="roles" 
                    column="id" 
                    select="com.example.mapper.RoleMapper.selectByUserId"/>
    </resultMap>
    
    <!-- 对应的查询 -->
    <select id="selectUserWithRole" resultMap="UserWithRoleRefMap">
        SELECT 
            u.*,
            r.id as role_id,
            r.name as role_name,
            r.code as role_code
        FROM user u
        LEFT JOIN user_role ur ON u.id = ur.user_id
        LEFT JOIN role r ON ur.role_id = r.id
        WHERE u.id = #{id}
    </select>
</mapper>

2.3 使用 columnPrefix 实现同一结构的复用

<!-- 定义地址映射 -->
<resultMap id="AddressMap" type="com.example.entity.Address">
    <result property="province" column="province"/>
    <result property="city" column="city"/>
    <result property="district" column="district"/>
    <result property="street" column="street"/>
    <result property="detail" column="detail"/>
    <result property="zipCode" column="zip_code"/>
</resultMap>

<!-- 在多个地方复用,通过 columnPrefix 区分不同地址 -->
<resultMap id="UserWithAddressesMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    
    <!-- 注册地址,使用 reg_ 前缀 -->
    <association property="registerAddress" 
                 resultMap="AddressMap" 
                 columnPrefix="reg_"/>
    
    <!-- 居住地址,使用 live_ 前缀 -->
    <association property="livingAddress" 
                 resultMap="AddressMap" 
                 columnPrefix="live_"/>
    
    <!-- 工作地址,使用 work_ 前缀 -->
    <association property="workAddress" 
                 resultMap="AddressMap" 
                 columnPrefix="work_"/>
    
    <!-- 收货地址列表,每个地址使用不同的前缀 -->
    <collection property="shippingAddresses" 
                ofType="com.example.entity.Address"
                resultMap="AddressMap"
                columnPrefix="ship_"/>
</resultMap>

<!-- 对应的查询 -->
<select id="selectUserWithAllAddresses" resultMap="UserWithAddressesMap">
    SELECT 
        u.id, u.username,
        ra.province as reg_province,
        ra.city as reg_city,
        ra.district as reg_district,
        ra.street as reg_street,
        ra.detail as reg_detail,
        ra.zip_code as reg_zip_code,
        
        la.province as live_province,
        la.city as live_city,
        la.district as live_district,
        la.street as live_street,
        la.detail as live_detail,
        la.zip_code as live_zip_code,
        
        wa.province as work_province,
        wa.city as work_city,
        wa.district as work_district,
        wa.street as work_street,
        wa.detail as work_detail,
        wa.zip_code as work_zip_code,
        
        sa.province as ship_province,
        sa.city as ship_city,
        sa.district as ship_district,
        sa.street as ship_street,
        sa.detail as ship_detail,
        sa.zip_code as ship_zip_code
        
    FROM user u
    LEFT JOIN address ra ON u.register_address_id = ra.id
    LEFT JOIN address la ON u.living_address_id = la.id
    LEFT JOIN address wa ON u.work_address_id = wa.id
    LEFT JOIN user_shipping_address usa ON u.id = usa.user_id
    LEFT JOIN address sa ON usa.address_id = sa.id
    WHERE u.id = #{id}
</select>

2.4 构造方法映射复用

<!-- 定义带构造方法的映射 -->
<resultMap id="ConstructorMap" type="com.example.entity.User">
    <constructor>
        <idArg column="id" javaType="Long"/>
        <arg column="username" javaType="String"/>
        <arg column="email" javaType="String"/>
        <arg column="create_time" javaType="java.time.LocalDateTime"/>
    </constructor>
</resultMap>

<!-- 继承构造方法映射 -->
<resultMap id="UserWithRoleConstructorMap" extends="ConstructorMap" 
           type="com.example.entity.User">
    <association property="role" javaType="com.example.entity.Role">
        <id property="id" column="role_id"/>
        <result property="name" column="role_name"/>
    </association>
</resultMap>

<!-- 使用不同的构造参数顺序 -->
<resultMap id="AlternativeConstructorMap" type="com.example.entity.User">
    <constructor>
        <arg column="username" javaType="String"/>
        <arg column="email" javaType="String"/>
        <idArg column="id" javaType="Long"/>
    </constructor>
</resultMap>

2.5 鉴别器映射复用

<!-- 定义基础映射 -->
<resultMap id="BaseMessageMap" type="com.example.entity.Message">
    <id property="id" column="id"/>
    <result property="content" column="content"/>
    <result property="createTime" column="create_time"/>
</resultMap>

<!-- 使用鉴别器实现多态查询 -->
<resultMap id="MessageMap" extends="BaseMessageMap" 
           type="com.example.entity.Message">
    <discriminator javaType="int" column="message_type">
        <!-- 文本消息 -->
        <case value="1" resultMap="TextMessageMap"/>
        <!-- 图片消息 -->
        <case value="2" resultMap="ImageMessageMap"/>
        <!-- 语音消息 -->
        <case value="3" resultMap="VoiceMessageMap"/>
        <!-- 视频消息,继承基础映射并扩展 -->
        <case value="4" resultType="com.example.entity.VideoMessage">
            <result property="videoUrl" column="video_url"/>
            <result property="duration" column="duration"/>
            <result property="thumbnail" column="thumbnail"/>
        </case>
    </discriminator>
</resultMap>

<!-- 各子类映射可以继续继承 -->
<resultMap id="TextMessageMap" extends="BaseMessageMap" 
           type="com.example.entity.TextMessage">
    <result property="textFormat" column="text_format"/>
</resultMap>

<resultMap id="ImageMessageMap" extends="BaseMessageMap" 
           type="com.example.entity.ImageMessage">
    <result property="imageUrl" column="image_url"/>
    <result property="width" column="width"/>
    <result property="height" column="height"/>
</resultMap>

三、高级复用技巧

3.1 通过继承复用完整查询

<!-- 基础查询 -->
<select id="selectBaseUsers" resultMap="BaseResultMap">
    SELECT id, username, email, status 
    FROM user 
    WHERE status = 1
</select>

<!-- 继承基础查询并扩展条件 -->
<select id="selectActiveUsers" resultMap="BaseResultMap" 
        extends="selectBaseUsers">
    AND last_login_time > #{lastLoginTime}
</select>

<!-- 继承并修改排序 -->
<select id="selectRecentActiveUsers" resultMap="BaseResultMap" 
        extends="selectActiveUsers">
    ORDER BY last_login_time DESC
    LIMIT #{limit}
</select>

<!-- 多层继承 -->
<select id="selectVipActiveUsers" resultMap="UserWithRoleMap" 
        extends="selectActiveUsers">
    AND vip_level > 0
    AND EXISTS (SELECT 1 FROM user_role WHERE user_id = user.id)
</select>

3.2 泛型基础映射抽象

<!-- 定义抽象的基础映射(不指定具体类型) -->
<resultMap id="BaseEntityMap" type="com.example.entity.BaseEntity">
    <id property="id" column="id"/>
    <result property="createTime" column="create_time"/>
    <result property="updateTime" column="update_time"/>
    <result property="createBy" column="create_by"/>
    <result property="updateBy" column="update_by"/>
    <result property="deleted" column="deleted" javaType="boolean"/>
</resultMap>

<!-- 子类继承基础映射 -->
<resultMap id="ProductMap" extends="BaseEntityMap" 
           type="com.example.entity.Product">
    <result property="name" column="name"/>
    <result property="price" column="price"/>
    <result property="stock" column="stock"/>
</resultMap>

<resultMap id="OrderMap" extends="BaseEntityMap" 
           type="com.example.entity.Order">
    <result property="orderNo" column="order_no"/>
    <result property="amount" column="amount"/>
    <result property="userId" column="user_id"/>
</resultMap>

<resultMap id="CategoryMap" extends="BaseEntityMap" 
           type="com.example.entity.Category">
    <result property="name" column="name"/>
    <result property="parentId" column="parent_id"/>
    <result property="level" column="level"/>
</resultMap>

3.3 动态映射组合

<!-- 定义可选的映射片段 -->
<sql id="Optional_User_Fields">
    <if test="includeProfile">
        , profile, avatar
    </if>
    <if test="includeStats">
        , login_count, last_login_ip
    </if>
</sql>

<!-- 动态构建 resultMap 的引用 -->
<resultMap id="DynamicUserMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 基础字段始终包含 -->
</resultMap>

<resultMap id="UserWithProfileMap" extends="DynamicUserMap" 
           type="com.example.entity.User">
    <result property="profile" column="profile"/>
    <result property="avatar" column="avatar"/>
</resultMap>

<resultMap id="UserWithStatsMap" extends="DynamicUserMap" 
           type="com.example.entity.User">
    <result property="loginCount" column="login_count"/>
    <result property="lastLoginIp" column="last_login_ip"/>
</resultMap>

<!-- 根据条件动态选择 resultMap -->
<select id="selectUserDynamic" resultMap="DynamicUserMap">
    SELECT id, username
    <include refid="Optional_User_Fields"/>
    FROM user
    WHERE id = #{id}
</select>

3.4 类型处理器复用

<!-- 自定义类型处理器 -->
<typeHandler handler="com.example.handler.JsonTypeHandler" 
             javaType="com.example.entity.JsonObject"/>

<typeHandler handler="com.example.handler.EnumTypeHandler" 
             javaType="com.example.enums.StatusEnum"/>

<!-- 在 resultMap 中复用 -->
<resultMap id="ProductDetailMap" type="com.example.entity.Product">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 复用 JSON 处理器 -->
    <result property="attributes" column="attributes" 
            typeHandler="com.example.handler.JsonTypeHandler"/>
    <result property="specs" column="specs" 
            typeHandler="com.example.handler.JsonTypeHandler"/>
    
    <!-- 复用枚举处理器 -->
    <result property="status" column="status" 
            typeHandler="com.example.handler.EnumTypeHandler"/>
    <result property="category" column="category" 
            typeHandler="com.example.handler.EnumTypeHandler"/>
</resultMap>

<!-- 在多个映射中复用相同的处理器配置 -->
<resultMap id="OrderExtrasMap" type="com.example.entity.Order">
    <result property="extras" column="extras" 
            typeHandler="com.example.handler.JsonTypeHandler"/>
    <result property="status" column="status" 
            typeHandler="com.example.handler.EnumTypeHandler"/>
</resultMap>

四、最佳实践与注意事项

4.1 命名规范建议

<!-- 使用清晰、一致的命名规范 -->
<mapper namespace="com.example.mapper.UserMapper">
    <!-- SQL 片段命名:功能 + 类型 -->
    <sql id="Base_Column_List"/>           <!-- 基础列名 -->
    <sql id="Base_Where_Clause"/>           <!-- 基础条件 -->
    <sql id="Refined_Where_Clause"/>        <!-- 精确条件 -->
    <sql id="Fuzzy_Where_Clause"/>          <!-- 模糊条件 -->
    <sql id="Order_By_Clause"/>             <!-- 排序 -->
    <sql id="Pagination_Clause"/>           <!-- 分页 -->
    
    <!-- resultMap 命名:实体 + 功能 + Map -->
    <resultMap id="BaseResultMap"/>         <!-- 基础映射 -->
    <resultMap id="UserWithRoleMap"/>       <!-- 带角色 -->
    <resultMap id="UserDetailMap"/>         <!-- 详细信息 -->
    <resultMap id="UserLazyMap"/>           <!-- 懒加载 -->
    
    <!-- 查询命名:操作 + 条件 + 结果类型 -->
    <select id="selectById"/>
    <select id="selectByCondition"/>
    <select id="selectWithRoleById"/>
    <select id="selectPage"/>
</mapper>

4.2 性能考虑

<!-- 1. 懒加载配置 -->
<resultMap id="UserLazyMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    
    <!-- 设置懒加载 -->
    <association property="role" 
                 select="com.example.mapper.RoleMapper.selectByUserId"
                 column="id"
                 fetchType="lazy"/>
    
    <collection property="orders"
                select="com.example.mapper.OrderMapper.selectByUserId"
                column="id"
                fetchType="lazy"/>
</resultMap>

<!-- 2. 使用延迟加载的全局配置 -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

<!-- 3. 批量查询优化 -->
<resultMap id="UserWithOrdersBatchMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    
    <!-- 使用批量查询 -->
    <collection property="orders" 
                ofType="com.example.entity.Order"
                select="com.example.mapper.OrderMapper.selectByUserIds"
                column="{userIds=id}"/>
</resultMap>

4.3 常见问题与解决方案

<!-- 问题1:循环引用导致栈溢出 -->
<!-- 解决方案:合理设置递归深度或使用延迟加载 -->
<resultMap id="CategoryTreeMap" type="com.example.entity.Category">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 设置递归查询,但要有终止条件 -->
    <collection property="children" 
                ofType="com.example.entity.Category"
                select="selectChildren"
                column="{parentId=id, level=level+1}"/>
</resultMap>

<!-- 问题2:字段冲突 -->
<!-- 解决方案:使用别名和 columnPrefix -->
<resultMap id="UserProductMap" type="com.example.entity.UserProduct">
    <id property="userId" column="user_id"/>
    <id property="productId" column="product_id"/>
    
    <!-- 使用别名区分同名字段 -->
    <association property="user" resultMap="UserMap" columnPrefix="u_"/>
    <association property="product" resultMap="ProductMap" columnPrefix="p_"/>
</resultMap>

<!-- 问题3:N+1 查询问题 -->
<!-- 解决方案:使用联表查询代替嵌套查询 -->
<resultMap id="UserWithOrdersJoinMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    
    <!-- 使用联表查询 -->
    <collection property="orders" ofType="com.example.entity.Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="amount" column="amount"/>
    </collection>
</resultMap>

<!-- 对应的优化查询 -->
<select id="selectUserWithOrders" resultMap="UserWithOrdersJoinMap">
    SELECT 
        u.*,
        o.id as order_id,
        o.order_no,
        o.amount
    FROM user u
    LEFT JOIN order o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

五、实际项目应用示例

5.1 通用 CRUD 复用框架

<!-- BaseMapper.xml - 基础映射文件 -->
<mapper namespace="com.example.mapper.BaseMapper">
    
    <!-- 通用字段 -->
    <sql id="Base_Column">
        id, create_time, update_time, create_by, update_by, deleted
    </sql>
    
    <!-- 通用条件:未删除 -->
    <sql id="Not_Deleted">
        AND deleted = 0
    </sql>
    
    <!-- 通用 resultMap -->
    <resultMap id="BaseResultMap" type="com.example.entity.BaseEntity">
        <id property="id" column="id"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
        <result property="createBy" column="create_by"/>
        <result property="updateBy" column="update_by"/>
        <result property="deleted" column="deleted"/>
    </resultMap>
    
    <!-- 通用插入 -->
    <sql id="Base_Insert">
        INSERT INTO ${tableName} (
            <include refid="Base_Column"/>
            <if test="extraFields != null">
                , ${extraFields}
            </if>
        ) VALUES (
            #{id}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleted}
            <if test="extraValues != null">
                , ${extraValues}
            </if>
        )
    </sql>
    
    <!-- 通用更新 -->
    <sql id="Base_Update">
        UPDATE ${tableName}
        <set>
            update_time = NOW(),
            <if test="updateBy != null">update_by = #{updateBy},</if>
            ${updateFields}
        </set>
        WHERE id = #{id}
    </sql>
</mapper>

<!-- UserMapper.xml - 具体实现 -->
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 继承通用 resultMap -->
    <resultMap id="UserMap" extends="com.example.mapper.BaseMapper.BaseResultMap" 
               type="com.example.entity.User">
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="email" column="email"/>
        <result property="phone" column="phone"/>
        <result property="status" column="status"/>
    </resultMap>
    
    <!-- 使用通用 SQL 片段 -->
    <select id="selectById" resultMap="UserMap">
        SELECT 
            <include refid="com.example.mapper.BaseMapper.Base_Column"/>,
            username, password, email, phone, status
        FROM user
        WHERE id = #{id}
        <include refid="com.example.mapper.BaseMapper.Not_Deleted"/>
    </select>
    
    <!-- 复杂查询 -->
    <select id="selectUserDetail" resultMap="UserDetailMap">
        SELECT 
            u.*,
            r.id as role_id,
            r.name as role_name,
            r.code as role_code,
            d.id as dept_id,
            d.name as dept_name
        FROM user u
        LEFT JOIN user_role ur ON u.id = ur.user_id
        LEFT JOIN role r ON ur.role_id = r.id
        LEFT JOIN department d ON u.dept_id = d.id
        WHERE u.id = #{id}
        <include refid="com.example.mapper.BaseMapper.Not_Deleted"/>
    </select>
</mapper>

5.2 多租户系统复用

<!-- 租户隔离的基础配置 -->
<mapper namespace="com.example.mapper.TenantBaseMapper">
    
    <!-- 租户字段 -->
    <sql id="Tenant_Column">
        tenant_id
    </sql>
    
    <!-- 租户条件 -->
    <sql id="Tenant_Condition">
        AND tenant_id = #{tenantId}
    </sql>
    
    <!-- 自动注入租户ID -->
    <sql id="Auto_Set_Tenant">
        <if test="tenantId == null">
            #{_parameter.getTenantId()} as tenant_id,
        </if>
        <if test="tenantId != null">
            #{tenantId} as tenant_id,
        </if>
    </sql>
    
    <!-- 租户基础 resultMap -->
    <resultMap id="TenantBaseMap" extends="com.example.mapper.BaseMapper.BaseResultMap">
        <result property="tenantId" column="tenant_id"/>
    </resultMap>
</mapper>

<!-- 业务表继承租户配置 -->
<resultMap id="BusinessMap" extends="com.example.mapper.TenantBaseMapper.TenantBaseMap" 
           type="com.example.entity.Business">
    <!-- 业务特有字段 -->
    <result property="name" column="name"/>
    <result property="code" column="code"/>
</resultMap>

<!-- 查询时自动带上租户条件 -->
<select id="selectByBusiness" resultMap="BusinessMap">
    SELECT * FROM business
    <where>
        <include refid="com.example.mapper.TenantBaseMapper.Tenant_Condition"/>
        <if test="name != null">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
    </where>
</select>

六、总结

6.1 复用方式对比

复用方式 适用场景 优点 缺点
SQL 片段 重复的 SQL 代码块 简单直观,参数灵活 仅限于 SQL 层面
resultMap 继承 实体映射复用 面向对象,支持多态 继承层级过深难维护
columnPrefix 同一结构多字段 避免重复定义 查询语句较复杂
引用外部 resultMap 跨文件复用 模块化好,解耦 需注意命名空间
查询继承 相似查询复用 减少重复 SQL 调试相对困难

6.2 最佳实践建议

  1. 合理粒度:SQL 片段粒度不宜过细,避免过度拆分
  2. 命名规范:建立统一的命名规范,提高可读性
  3. 适度继承:resultMap 继承层级建议不超过 3 层
  4. 性能考虑:注意 N+1 问题,合理使用联表查询
  5. 注释完善:关键复用点添加注释,说明用途和使用方式
  6. 版本控制:复用配置变更时注意影响范围

6.3 注意事项

  • 作用域:同 namespace 内可直接引用,跨 namespace 需使用全限定名
  • 参数传递<include> 标签的 <property> 只在当前片段有效
  • 优先级:extends 的 SQL 会在当前 SQL 之前执行
  • 循环依赖:避免 resultMap 循环引用导致栈溢出
  • 调试难度:过度复用可能增加调试难度,需要权衡

通过合理运用这些复用技巧,可以显著提高 MyBatis 配置的可维护性,减少重复代码,提升开发效率。在实际项目中,建议根据具体场景选择合适的复用方式,并建立团队统一的规范。

posted on 2026-03-07 09:03  二月无雨  阅读(39)  评论(0)    收藏  举报