resultMap继承到底有多强?看完这6个真实案例你一定会重构旧代码

第一章:resultMap继承的底层原理与设计思想

在 MyBatis 框架中,`resultMap` 是实现复杂结果映射的核心机制。其继承特性并非传统面向对象意义上的继承,而是通过 `extends` 属性在 XML 配置层面实现结构复用。这一设计源于减少重复配置、提升可维护性的工程需求,体现了“配置即代码”的架构哲学。

继承机制的实现方式

当一个 `resultMap` 声明了 `extends` 属性时,MyBatis 在解析阶段会将父级 `resultMap` 的映射关系合并到当前实例中。若存在同名属性,子 `resultMap` 的定义将覆盖父级。
  • 父级 resultMap 定义通用字段,如 id 和状态
  • 子级通过 extends 引用父级并扩展特有字段
  • 解析器递归合并映射节点,构建完整结果结构
<resultMap id="baseResultMap" type="User">
  <id property="id" column="user_id"/>
  <result property="status" column="status"/>
</resultMap>

<resultMap id="detailedUser" type="DetailedUser" extends="baseResultMap">
  <result property="email" column="email"/> 
</resultMap>

设计思想解析

该机制背后的设计思想包含:
  1. 关注点分离:基础映射与业务映射解耦
  2. 配置复用:避免跨多个 resultMap 重复声明相同字段
  3. 运行时优化:在 SqlSessionFactory 构建阶段完成合并,不影响执行性能
特性说明
继承方向单向继承,仅支持一级延伸
覆盖规则子类同名 property 完全覆盖父类定义
解析时机XML 加载期完成合并,非运行时动态处理
graph TD A[Parse Parent resultMap] --> B[Parse Child with extends] B --> C{Resolve Reference} C --> D[Merge Mapping Entries] D --> E[Store Final ResultStructure]

第二章:基础继承机制的应用实践

2.1 理解resultMap的extends关键字工作原理

在MyBatis中,`resultMap` 的 `extends` 关键字用于实现结果映射的继承机制。通过继承已有 `resultMap`,可以复用公共字段映射配置,减少重复定义。
继承的基本语法
<resultMap id="baseResultMap" type="User">
  <id property="id" column="user_id"/>
  <result property="name" column="user_name"/>
</resultMap>

<resultMap id="extendedResultMap" type="Student" extends="baseResultMap">
  <result property="grade" column="student_grade"/>
</resultMap>
上述代码中,`extendedResultMap` 继承了 `baseResultMap` 的所有映射规则,并新增 `grade` 字段。执行时,MyBatis 会合并父映射与子映射的字段配置。
继承机制的优势
  • 提升代码复用性,避免重复定义相同字段
  • 便于维护,基础映射修改后,所有子映射自动生效
  • 支持多层继承,形成映射层级结构

2.2 单层继承下的字段复用与映射优化

在单层继承结构中,父类字段的合理复用能显著减少冗余代码。通过统一字段映射策略,子类可直接继承并扩展父类属性,提升数据一致性。
字段继承机制
子类自动获取父类非私有字段,无需重复声明。例如:

type User struct {
    ID   uint
    Name string
}

type AdminUser struct {
    User  // 匿名嵌入实现继承
    Role string
}
上述代码中,AdminUser 复用 UserIDName 字段。匿名嵌入使 User 成为 AdminUser 的“父类”,实现字段共享。
映射优化策略
使用标签(tag)统一数据库字段映射,避免手动转换:
结构体字段数据库列说明
IDuser_id主键映射
Nameuser_name命名规范化
该方式结合 ORM 框架可自动完成结构体与表字段的高效映射,降低维护成本。

2.3 继承中属性冲突的解析优先级规则

在面向对象编程中,当子类继承父类并重定义同名属性时,解析优先级决定了运行时实际访问的属性版本。默认情况下,实例自身的属性优先于从父类继承的属性。
属性查找链机制
JavaScript 和 Python 等语言遵循“就近原则”:首先在实例自身查找属性,若不存在则沿原型链或继承链向上搜索。
代码示例:Python 中的属性覆盖

class Parent:
    name = "Parent"

class Child(Parent):
    name = "Child"  # 覆盖父类属性

print(Child().name)  # 输出: Child
上述代码中,`Child` 类定义了与 `Parent` 同名的 `name` 属性。实例调用 `name` 时返回子类值,体现子类属性的高优先级。
优先级规则总结
  • 实例自身定义的属性优先级最高
  • 子类定义的属性覆盖父类同名属性
  • 多层继承中,最近祖先类的属性优先生效

2.4 使用继承简化复杂实体映射配置

在处理多个具有共性字段的实体时,重复的映射配置会显著增加维护成本。通过引入继承机制,可以将公共属性提取到基类中,由子类复用。
共享字段抽象
将如ID、创建时间、更新时间等通用字段封装在基类中,所有业务实体继承该类,自动获得一致的映射定义。
代码示例

@MappedSuperclass
public abstract class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    // getters and setters
}
上述代码中,@MappedSuperclass 保证基类不被单独映射为表,但其字段将被子类实体继承并映射到对应数据库列。
  • 减少重复注解,提升配置可读性
  • 统一生命周期字段管理策略
  • 支持多层继承结构下的映射传递

2.5 基于继承的DAO层代码结构重构示例

在传统DAO模式中,每个实体类常需重复编写增删改查方法。通过引入基类抽象通用操作,可显著减少冗余代码。
通用DAO基类设计
public abstract class BaseDao<T> {
    protected EntityManager entityManager;

    public void save(T entity) {
        entityManager.persist(entity);
    }

    public T findById(Serializable id) {
        return entityManager.find(getEntityClass(), id);
    }

    protected abstract Class<T> getEntityClass();
}
上述代码定义了泛型基类,封装了JPA核心操作。子类只需实现 getEntityClass() 方法即可获得基础CRUD能力。
用户DAO继承实现
  • UserDao extends BaseDao<User> 自动继承通用方法
  • 特有查询如 findByEmail 可在子类中单独定义
  • 事务管理仍由Spring容器统一控制
该结构提升了代码复用性,同时保持扩展灵活性。

第三章:多层级继承与高级映射策略

3.1 构建多级resultMap继承体系的场景分析

在复杂业务系统中,数据库表之间常存在继承与关联关系,例如用户体系中的基础用户、管理员与会员。为避免重复定义映射字段,MyBatis 提供了多级 `resultMap` 继承机制。
基础 resultMap 定义
<resultMap id="BaseResultMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
</resultMap>
该映射定义了所有用户类型的公共字段,作为继承体系的根节点。
扩展子类映射
<resultMap id="AdminResultMap" type="Admin" extends="BaseResultMap">
    <result property="dept" column="department"/>
</resultMap>
通过 `extends` 属性复用父级字段,仅需声明特有属性,提升可维护性。
  • 减少冗余:避免在多个 resultMap 中重复定义相同字段
  • 易于维护:基础结构变更时只需修改父级映射
  • 支持多层继承:可构建 Base → User → Admin/VIPUser 的层级结构

3.2 抽象父级resultMap的设计原则与最佳实践

在MyBatis中,抽象父级`resultMap`用于提取多个映射结果的共性字段,提升配置复用性与维护效率。通过继承机制,子`resultMap`可复用父级定义的属性映射,避免重复配置。
设计原则
  • 单一职责:每个抽象`resultMap`应聚焦于特定领域,如基础信息、审计字段等;
  • 可扩展性:预留扩展点,便于子类通过<association><collection>补充关联映射;
  • 避免过度抽象:仅提取真正通用的字段,防止耦合过重。
代码示例
<resultMap id="BaseResultMap" type="BaseEntity">
  <id property="id" column="id"/>
  <result property="createTime" column="create_time"/>
</resultMap>

<resultMap id="UserResultMap" type="User" extends="BaseResultMap">
  <result property="username" column="username"/>
</resultMap>
上述配置中,BaseResultMap定义了所有实体共有的idcreateTime字段,UserResultMap通过extends继承并扩展专属属性,实现清晰的层次化映射结构。

3.3 联合继承与鉴别器(discriminator)的协同使用

在复杂对象建模中,联合继承与鉴别器的结合可显著提升类型解析的准确性。通过定义共享结构的变体类型,系统可根据鉴别器字段动态判断具体子类型。
模式定义示例
{
  "type": "object",
  "discriminator": {
    "propertyName": "kind"
  },
  "oneOf": [
    {
      "properties": {
        "kind": { "const": "cat" },
        "meowVolume": { "type": "number" }
      }
    },
    {
      "properties": {
        "kind": { "const": "dog" },
        "barkVolume": { "type": "number" }
      }
    }
  ]
}
上述模式中,discriminatorpropertyName 指定为 kind,解析器依据该字段值选择对应的子模式进行校验,避免运行时类型混淆。
优势分析
  • 提升序列化效率:减少冗余字段判断
  • 增强类型安全:编译期即可检测不匹配分支
  • 支持扩展性:新增类型仅需追加 oneOf 项并更新鉴别值

第四章:真实业务场景中的继承应用案例

4.1 案例一:用户权限体系中角色映射的继承实现

在构建复杂的用户权限系统时,角色之间的继承关系能有效减少重复配置。通过定义基础角色并允许高级角色继承其权限,可实现灵活且可维护的访问控制策略。
角色继承模型设计
采用面向对象的继承思想,将“管理员”角色继承“普通用户”角色的权限,在数据库层面通过外键关联实现:

-- 角色表结构
CREATE TABLE roles (
  id INT PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  parent_role_id INT, -- 指向父角色,形成继承链
  FOREIGN KEY (parent_role_id) REFERENCES roles(id)
);
上述设计中,`parent_role_id` 允许为空,表示该角色为顶层角色。查询用户权限时,需递归向上遍历所有父角色,聚合全部权限集合。
权限聚合流程
  • 用户登录后加载其直接分配的角色
  • 根据角色的 `parent_role_id` 构建继承路径
  • 沿路径逐层合并权限,避免重复授权
  • 最终生成统一的权限令牌供后续鉴权使用

4.2 案例二:电商平台商品类目结构的动态映射

在电商平台中,商品类目结构常因业务扩展而频繁调整。为实现不同系统间类目的动态映射,采用基于配置表的元数据驱动方案成为关键。
映射配置表设计
通过数据库表统一管理类目映射关系,支持实时更新:
源类目ID目标类目ID映射状态更新时间
cat_1001g_2001生效2025-04-01
cat_1002g_2005待审核2025-04-03
动态解析逻辑
// 根据源类目ID查找目标类目
func GetTargetCategory(srcID string) (string, error) {
    row := db.QueryRow("SELECT target_id FROM category_mapping WHERE src_id = ? AND status = '生效'", srcID)
    var targetID string
    if err := row.Scan(&targetID); err != nil {
        return "", err
    }
    return targetID, nil // 返回映射后的类目
}
该函数通过查询数据库获取最新映射结果,避免硬编码,提升系统灵活性与可维护性。

4.3 案例三:金融系统中交易记录的分层数据封装

在金融系统中,交易记录需满足高一致性与可追溯性。通过分层数据封装,可将原始交易数据、审计元数据与业务上下文分离管理,提升系统可维护性。
数据结构设计
采用嵌套结构封装交易信息,确保各层职责清晰:

type Transaction struct {
    Header   Metadata       `json:"header"`   // 审计信息
    Payload  TradeDetail    `json:"payload"`  // 交易主体
    AuditLog []AuditEntry   `json:"audit_log"`// 操作日志
}

type Metadata struct {
    TraceID     string `json:"trace_id"`
    Timestamp   int64  `json:"timestamp"`
    UserID      string `json:"user_id"`
}
该结构中,Header承载链路追踪与安全审计字段,Payload封装核心交易内容,AuditLog记录变更轨迹,实现关注点分离。
优势分析
  • 增强数据一致性:写入时统一提交三层结构
  • 支持灵活查询:可按层提取,降低IO开销
  • 便于合规审计:独立审计层满足监管要求

4.4 案例四:内容管理系统(CMS)的多态内容提取

在现代内容管理系统中,文章、图片、视频等不同类型的内容需要统一存储与提取。通过多态设计模式,可为各类内容定义统一接口,实现灵活扩展。
多态内容接口设计
type Content interface {
    Render() string
    GetType() string
}

type Article struct{ Title string }
func (a Article) Render() string { return "<article>" + a.Title + "</article>" }
func (a Article) GetType() string { return "article" }
上述代码定义了通用内容渲染接口,不同内容类型实现各自的 Render 和 GetType 方法,便于系统统一调用。
内容类型映射表
类型处理器输出格式
ArticleHTMLRendererHTML片段
VideoIFrameRenderer嵌入式播放器
该映射确保系统可根据内容类型动态选择渲染策略,提升可维护性。

第五章:从重构到架构升级——重新定义MyBatis映射规范

在大型企业级应用中,随着业务模块的不断扩展,MyBatis 映射文件逐渐变得臃肿,SQL 语句分散、命名不统一、重复代码多等问题日益突出。为提升可维护性与团队协作效率,必须对现有映射规范进行系统性重构与架构升级。
统一命名与结构规范
所有 Mapper 接口命名应以业务领域为核心,后缀统一为 `Mapper`,如 `UserAuthMapper`;对应的 XML 文件需保持同名,并置于 `resources/mappers/auth/` 路径下。SQL ID 命名采用 `_` 格式,例如 `select_user_by_id`。
引入 ResultMap 共享机制
通过定义公共 `ResultMap` 减少字段映射冗余:
<resultMap id="BaseUserResult" type="com.example.domain.User">
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    <result property="email" column="email"/>
</resultMap>

<select id="select_user_by_id" resultMap="BaseUserResult">
    SELECT user_id, username, email 
    FROM t_user 
    WHERE user_id = #{userId}
</select>
动态 SQL 模块化复用
使用 `` 片段提取常用条件,提升可读性与复用率:
<sql id="user_query_condition">
    <where>
        <if test="username != null">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
    </where>
</sql>
集成 MyBatis-Plus 提升开发效率
在新模块中逐步引入 MyBatis-Plus,利用其 `@TableName`、`LambdaQueryWrapper` 等特性减少 XML 编写量,同时保留传统 MyBatis 对复杂查询的支持。
策略适用场景优势
传统 XML 映射报表、多表关联查询SQL 控制精细
MyBatis-PlusCRUD 高频操作减少模板代码
在 MyBatis 中,**通用查询映射结果(`<resultMap>`)** 是用于将 SQL 查询结果集中的列映射到 Java 对象属性上的核心机制。以下是其生成方式及在系统中如何区分不同表的映射结果的说明: --- ### 一、通用查询映射结果(`<resultMap>`)是如何生成的? 1. **手动编写 XML 配置**: - 开发者根据数据库表结构和对应的 Java 实体类,手动在 MyBatis 的 Mapper XML 文件中定义 `<resultMap>`。 - 每个 `<result>` 标签将数据库字段(column)与 Java 对象属性(property)一一对应。 2. **通过 MyBatis Generator 自动生成**: - 使用 [MyBatis Generator](http://mybatis.org/generator/) 工具,根据数据库表结构自动生成实体类、Mapper 接口和 XML 映射文件,包括 `<resultMap>`。 - 可以配置生成 `BaseResultMap`、`ResultMapWithBLOBs` 等不同类型的映射。 3. **使用注解方式(适用于简单映射)**: - 在接口中使用 `@Results`、`@Result` 注解定义映射关系。 - 示例: ```java @Results(id = "BaseResultMap", value = { @Result(property = "sTime", column = "S_TIME"), @Result(property = "sLevel", column = "S_LEVEL") }) @Select("SELECT * FROM sj_wo_sys_log") List<SjWoSysLog> selectAll(); ``` --- ### 二、系统中如何区分不同表的 `<resultMap>`? 在一个系统中通常会有多个数据库表,对应多个实体类,因此会存在多个 `<resultMap>`。为了有效区分,可以采用以下方式: #### 1. **命名规范** - 使用统一命名规则区分不同表的映射结果,例如: - `UserResultMap` - `OrderResultMap` - `BaseResultMap`(常用于基础字段) - `RoleResultMap` #### 2. **namespace + id 唯一标识** - 每个 `<resultMap>` 的 `id` 在其所属的 `namespace` 下必须唯一。 - 示例: ```xml <mapper namespace="com.sjsemi.app.wo.dao.SjWoSysLogMapper"> <resultMap id="BaseResultMap" type="com.sjsemi.app.wo.domain.SjWoSysLog"> ... </resultMap> </mapper> ``` - 另一个表的 Mapper: ```xml <mapper namespace="com.sjsemi.app.user.dao.UserMapper"> <resultMap id="BaseResultMap" type="com.sjsemi.app.user.domain.User"> ... </resultMap> </mapper> ``` - 虽然两个 `id` 相同,但由于 `namespace` 不同,它们是不同的映射结果。 #### 3. **使用继承复用基础字段映射** - 如果多个表有相同字段(如 `create_time`, `update_time`),可以通过 `<resultMap>` 的 `extends` 属性复用基础映射。 - 示例: ```xml <resultMap id="BaseResultMap" type="BaseEntity"> <result property="createTime" column="CREATE_TIME"/> <result property="updateTime" column="UPDATE_TIME"/> </resultMap> <resultMap id="UserResultMap" type="User" extends="BaseResultMap"> <result property="username" column="USERNAME"/> </resultMap> ``` #### 4. **使用不同 Mapper 文件管理不同表的映射** - 每张表对应一个 Mapper XML 文件,每个文件中定义自己的 `<resultMap>`,便于管理和维护。 --- ### 三、总结 | 问题 | 解决方式 | |------|----------| | 如何生成 `<resultMap>`? | 手动编写、MyBatis Generator 自动生成、使用注解 | | 如何区分不同表的 `<resultMap>`? | 命名规范、namespace + id 唯一、继承复用、分文件管理 | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值