MyBatis-Plus 与 MyBatis 原生 XML 混合使用指南

适用版本:MyBatis-Plus 3.0+(主流版本兼容)
核心目标:在享受 MyBatis-Plus 简化 CRUD 的同时,保留 MyBatis 原生 XML 的强大灵活性。
适用场景:企业级项目中,简单操作用 MP,复杂 SQL 用原生 XML。


一、核心概念

1.1 MyBatis-Plus 是什么?

MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,完全兼容 MyBatis,提供:

  • BaseMapper<T> 接口:无需写 XML 即可实现 CRUD。
  • 自动分页、逻辑删除、自动填充、代码生成器等高级功能。

1.2 为什么需要混合使用?

需求 MyBatis-Plus MyBatis 原生 XML
简单增删改查 ✅ 极简(继承 BaseMapper ⛔ 繁琐
复杂 SQL(多表关联、动态条件) ⚠️ 可用 QueryWrapper,但可读性差 ✅ 强大灵活
存储过程调用 ⚠️ 支持有限 ✅ 完全支持
高级动态 SQL(<choose>, <foreach> 嵌套) ⚠️ 可用但复杂 ✅ 天然支持

结论MyBatis-Plus 是“增强”,不是“替代”,它与 MyBatis 原生 XML 完美共存。


二、详细操作步骤(手把手教学)

步骤 1:添加依赖(Maven)

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis-Plus(已包含 MyBatis 核心) -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>

    <!-- 数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Lombok(可选) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

🔔 关键点:只需引入 mybatis-plus-boot-starter,它内部已依赖 mybatis,无需额外引入。


步骤 2:配置 application.yml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  # 扫描 XML 映射文件
  mapper-locations: classpath:mapper/*.xml
  # 实体类别名包路径
  type-aliases-package: com.example.demo.entity
  # 配置项
  configuration:
    # 开启驼峰命名自动映射
    map-underscore-to-camel-case: true
    # 打印 SQL(开发环境)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 全局配置(可选)
  global-config:
    db-config:
      # 主键类型(AUTO 自增)
      id-type: auto

步骤 3:创建实体类(Entity)

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

@Data
@TableName("user") // 指定数据库表名
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

    private String email;

    @TableField("create_time")
    private LocalDateTime createTime;

    @TableField("status")
    private Integer status;
}

步骤 4:创建 Mapper 接口(混合使用核心)

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 继承 BaseMapper<User>:获得 MP 内置 CRUD 方法
 * 同时可定义自己的方法,由 XML 实现
 */
@Repository
public interface UserMapper extends BaseMapper<User> {

    // 方法1:使用 MP 内置方法(无需实现)
    // 例如:selectById, insert, updateById 等

    // 方法2:自定义复杂查询,由 XML 实现
    IPage<User> selectUserWithRole(Page<User> page, @Param("name") String name);

    // 方法3:多表关联查询
    List<User> selectUserWithDept(@Param("deptId") Long deptId);

    // 方法4:调用存储过程(示例)
    void callUserProcedure(@Param("userId") Long userId);
}

步骤 5:创建 MyBatis 原生 XML 映射文件

resources/mapper/UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    <!-- 复杂分页查询:用户 + 角色信息 -->
    <select id="selectUserWithRole" resultType="com.example.demo.entity.User">
        SELECT u.id, u.name, u.email, u.create_time, r.role_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
        <where>
            <if test="name != null and name != ''">
                AND u.name LIKE CONCAT('%', #{name}, '%')
            </if>
        </where>
        ORDER BY u.id DESC
    </select>

    <!-- 多表关联:查询某部门下的所有用户 -->
    <select id="selectUserWithDept" resultType="com.example.demo.entity.User">
        SELECT u.*
        FROM user u
        INNER JOIN dept d ON u.dept_id = d.id
        WHERE d.id = #{deptId}
    </select>

    <!-- 调用存储过程 -->
    <select id="callUserProcedure" statementType="CALLABLE">
        {call proc_get_user_info(#{userId, mode=IN, jdbcType=BIGINT})}
    </select>

</mapper>

关键点

  • namespace 必须与 Mapper 接口全限定名一致。
  • 方法名(如 selectUserWithRole)必须与接口方法名完全匹配。
  • 参数使用 @Param("xxx") 注解,XML 中通过 #{xxx} 引用。

步骤 6:配置分页插件(MP 特性)

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {

    /**
     * 配置分页插件(3.4.0+ 推荐方式)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

步骤 7:Service 层调用示例

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    // 使用 MP 内置方法
    public User getById(Long id) {
        return userMapper.selectById(id);
    }

    // 使用原生 XML 分页查询
    public IPage<User> getUserWithRole(int pageNum, int pageSize, String name) {
        Page<User> page = new Page<>(pageNum, pageSize);
        return userMapper.selectUserWithRole(page, name);
    }

    // 使用原生 XML 多表查询
    public List<User> getUsersByDept(Long deptId) {
        return userMapper.selectUserWithDept(deptId);
    }
}

步骤 8:Controller 测试

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getById(id);
    }

    @GetMapping("/with-role")
    public IPage<User> getUserWithRole(
            @RequestParam(defaultValue = "1") int pageNum,
            @RequestParam(defaultValue = "10") int pageSize,
            @RequestParam(required = false) String name) {
        return userService.getUserWithRole(pageNum, pageSize, name);
    }
}

三、常见错误与解决方案

错误现象 原因 解决方案
Invalid bound statement (not found) XML 文件未被加载 检查 mapper-locations 路径、namespace、方法名
参数无法映射(null) 多参数未加 @Param 所有命名参数必须加 @Param("xxx")
分页无效 未配置分页插件 添加 MybatisPlusInterceptor 并注册 PaginationInnerInterceptor
实体字段映射错误 未开启驼峰转换 map-underscore-to-camel-case: true
BaseMapper 方法失效 Mapper 接口未继承 BaseMapper<T> 确保 UserMapper extends BaseMapper<User>

四、注意事项

  1. XML 优先级高于注解:如果 XML 中定义了方法,接口上的 @Select 等注解将被忽略。
  2. 方法签名必须一致:返回类型、参数类型、参数名(通过 @Param)必须匹配。
  3. @Param 不可省略:即使单参数,如果 XML 中使用了命名引用(如 #{name}),也建议加上 @Param
  4. 事务管理:混合使用不影响 Spring 事务,仍可用 @Transactional
  5. SQL 注入风险:避免使用 ${},优先使用 #{}

五、使用技巧

5.1 利用 MP 内置方法减少 XML

// 直接使用 MP 方法,无需写 XML
List<User> users = userMapper.selectList(
    new QueryWrapper<User>()
        .like("name", "张")
        .eq("status", 1)
        .orderByDesc("create_time")
);

5.2 在 XML 中调用 MP 自动填充字段

确保 @TableField(fill = FieldFill.INSERT) 字段在插入时自动填充。

5.3 动态 SQL 优先用 XML

避免在 QueryWrapper 中拼接复杂逻辑,用 XML 更清晰。


六、最佳实践

实践 说明
✅ CRUD 用 MP 内置方法 快速开发
✅ 复杂查询用原生 XML 可维护性强
✅ 统一 XML 路径 mapper/*.xml
✅ 开启驼峰映射 减少字段配置
✅ 分页使用 MP 插件 避免手写分页 SQL
✅ 参数加 @Param 避免绑定错误

七、性能优化建议

  1. SQL 优化:为查询字段加索引,避免 SELECT *
  2. 分页优化:大数据量使用“游标分页”或“keySet 分页”。
  3. 批量操作:使用 saveBatch()updateBatchById()
  4. 缓存:开启 MyBatis 二级缓存(@CacheNamespace)。
  5. 连接池:使用 HikariCP 等高性能连接池。

八、总结

使用方式 适用场景 推荐指数
MP 内置方法 简单 CRUD ⭐⭐⭐⭐⭐
原生 XML 复杂查询、多表、存储过程 ⭐⭐⭐⭐⭐
混合使用 企业级项目推荐方案 强烈推荐

核心结论MyBatis-Plus 与 MyBatis 原生 XML 不是“二选一”,而是“黄金搭档”
MP 负责“提效”,XML 负责“灵活”,两者结合,既能快速开发,又能应对复杂业务。