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> |
四、注意事项
- XML 优先级高于注解:如果 XML 中定义了方法,接口上的
@Select
等注解将被忽略。 - 方法签名必须一致:返回类型、参数类型、参数名(通过
@Param
)必须匹配。 @Param
不可省略:即使单参数,如果 XML 中使用了命名引用(如#{name}
),也建议加上@Param
。- 事务管理:混合使用不影响 Spring 事务,仍可用
@Transactional
。 - 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 |
避免绑定错误 |
七、性能优化建议
- SQL 优化:为查询字段加索引,避免
SELECT *
。 - 分页优化:大数据量使用“游标分页”或“keySet 分页”。
- 批量操作:使用
saveBatch()
、updateBatchById()
。 - 缓存:开启 MyBatis 二级缓存(
@CacheNamespace
)。 - 连接池:使用 HikariCP 等高性能连接池。
八、总结
使用方式 | 适用场景 | 推荐指数 |
---|---|---|
MP 内置方法 | 简单 CRUD | ⭐⭐⭐⭐⭐ |
原生 XML | 复杂查询、多表、存储过程 | ⭐⭐⭐⭐⭐ |
混合使用 | 企业级项目推荐方案 | ✅ 强烈推荐 |
✅ 核心结论: MyBatis-Plus 与 MyBatis 原生 XML 不是“二选一”,而是“黄金搭档”。
MP 负责“提效”,XML 负责“灵活”,两者结合,既能快速开发,又能应对复杂业务。