目录
🗃️ 37 MyBatis与MyBatis-Plus——ORM映射、动态SQL与代码生成
更新日期:2026年6月 | Java入门到精通系列 · 第五阶段·企业级开发
© 版权声明:本文为原创技术文章,转载请联系作者并注明出处。
一、ORM与MyBatis概述
1.1 什么是ORM
ORM(Object-Relational Mapping,对象关系映射) 是一种将数据库表与Java对象进行映射的技术,让我们可以用面向对象的方式操作数据库。
数据库表 ←→ Java类
表字段 ←→ 类属性
表行记录 ←→ 类实例
SQL操作 ←→ 方法调用
1.2 常见ORM框架对比
| 框架 | 类型 | SQL控制 | 学习成本 | 适用场景 |
|---|---|---|---|---|
| JDBC | 原生API | 完全手写 | 低 | 底层操作 |
| MyBatis | 半自动ORM | 手写SQL | 中 | 复杂SQL、性能要求高 |
| MyBatis-Plus | 增强ORM | 自动生成+手写 | 低 | 简单CRUD + 复杂查询 |
| JPA/Hibernate | 全自动ORM | 自动生成 | 高 | 简单业务、快速开发 |
| Spring JDBC | 模板封装 | 手写SQL | 低 | 轻量级操作 |
1.3 MyBatis架构
┌─────────────────────────────────────────────┐
│ 应用程序 │
├─────────────────────────────────────────────┤
│ MyBatis API │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ │
│ │SqlSession│ │Mapper接口 │ │ 动态SQL │ │
│ └─────────┘ └──────────┘ └───────────┘ │
├─────────────────────────────────────────────┤
│ 核心层 │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ │
│ │配置解析 │ │SQL执行 │ │ 结果映射 │ │
│ └─────────┘ └──────────┘ └───────────┘ │
├─────────────────────────────────────────────┤
│ 基础层 │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ │
│ │事务管理 │ │连接池 │ │ 缓存 │ │
│ └─────────┘ └──────────┘ └───────────┘ │
├─────────────────────────────────────────────┤
│ 数据库(MySQL/PostgreSQL/...) │
└─────────────────────────────────────────────┘
二、MyBatis快速入门
2.1 引入依赖
<!-- Spring Boot项目 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
2.2 配置文件
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
# XML映射文件位置
mapper-locations: classpath:mapper/**/*.xml
# 实体类别名包
type-aliases-package: com.example.entity
configuration:
# 开启驼峰命名自动映射
map-underscore-to-camel-case: true
# 开启日志
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# 开启延迟加载
lazy-loading-enabled: true
2.3 实体类
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
private String username;
private String email;
private Integer age;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
2.4 Mapper接口
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Optional;
@Mapper
public interface UserMapper {
List<User> findAll();
Optional<User> findById(Long id);
int insert(User user);
int update(User user);
int deleteById(Long id);
}
2.5 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.mapper.UserMapper">
<!-- 结果映射 -->
<resultMap id="UserResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
<result property="age" column="age"/>
<result property="status" column="status"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<!-- 查询所有 -->
<select id="findAll" resultMap="UserResultMap">
SELECT id, username, email, age, status, create_time, update_time
FROM users
</select>
<!-- 根据ID查询 -->
<select id="findById" parameterType="Long" resultMap="UserResultMap">
SELECT id, username, email, age, status, create_time, update_time
FROM users
WHERE id = #{id}
</select>
<!-- 插入 -->
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, email, age, status, create_time)
VALUES (#{username}, #{email}, #{age}, #{status}, NOW())
</insert>
<!-- 更新 -->
<update id="update" parameterType="User">
UPDATE users
SET username = #{username}, email = #{email}, age = #{age},
update_time = NOW()
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="Long">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
2.6 #{} 与 ${} 的区别
| 语法 | 说明 | 安全性 | 用途 |
|---|---|---|---|
| #{ } | 预编译参数占位符 | ✅ 安全(防SQL注入) | 传参(99%场景) |
| ${ } | 字符串直接替换 | ❌ 不安全 | 动态列名、表名、ORDER BY |
<!-- ✅ 安全:参数化查询 -->
<select id="findById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- ⚠️ 动态列名场景必须用${},但要确保参数来源可信 -->
<select id="findByColumn" resultType="User">
SELECT * FROM users ORDER BY ${columnName} ${order}
</select>
三、XML映射详解
3.1 查询结果映射
<!-- 1. 自动映射(列名与属性名一致,或开启驼峰映射) -->
<select id="findById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- 2. 手动映射(推荐,明确映射关系) -->
<resultMap id="UserResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="user_name"/>
<result property="createTime" column="create_time"/>
</resultMap>
<!-- 3. 关联查询映射(一对一) -->
<resultMap id="UserWithDeptMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<association property="department" javaType="Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
</resultMap>
<!-- 4. 集合映射(一对多) -->
<resultMap id="DeptWithUsersMap" type="Department">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="users" ofType="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
</collection>
</resultMap>
3.2 关联查询
<!-- 方式1:嵌套结果(一次查询,推荐) -->
<select id="findUserWithDept" resultMap="UserWithDeptMap">
SELECT u.id, u.username, d.id AS dept_id, d.name AS dept_name
FROM users u
LEFT JOIN departments d ON u.dept_id = d.id
WHERE u.id = #{id}
</select>
<!-- 方式2:嵌套查询(多次查询,N+1问题) -->
<resultMap id="UserWithDeptLazyMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<association property="department" javaType="Department"
select="findDeptById" column="dept_id"/>
</resultMap>
<select id="findDeptById" resultType="Department">
SELECT * FROM departments WHERE id = #{id}
</select>
四、注解开发
对于简单SQL,可以直接使用注解替代XML:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
Optional<User> findById(Long id);
@Select("SELECT * FROM users WHERE status = #{status}")
List<User> findByStatus(Integer status);
@Insert("INSERT INTO users (username, email, age) VALUES (#{username}, #{email}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}")
int update(User user);
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteById(Long id);
// 复杂SQL使用@SelectProvider
@SelectProvider(type = UserSqlProvider.class, method = "findUsers")
List<User> findUsers(UserQuery query);
}
// SQL构建器
public class UserSqlProvider {
public String findUsers(UserQuery query) {
return new SQL() {{
SELECT("id, username, email, age");
FROM("users");
if (query.getUsername() != null) {
WHERE("username LIKE CONCAT('%', #{username}, '%')");
}
if (query.getMinAge() != null) {
WHERE("age >= #{minAge}");
}
ORDER_BY("create_time DESC");
}}.toString();
}
}
五、动态SQL
5.1 if 标签
<select id="findUsers" resultType="User">
SELECT * FROM users
WHERE 1 = 1
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="status != null">
AND status = #{status}
</if>
</select>
5.2 where 标签
<!-- where标签自动去除多余的AND/OR -->
<select id="findUsers" resultType="User">
SELECT * FROM users
<where>
<if test="username != null">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
</where>
</select>
5.3 choose/when/otherwise
<!-- 类似switch-case -->
<select id="findUsers" resultType="User">
SELECT * FROM users
<where>
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="username != null">
AND username = #{username}
</when>
<otherwise>
AND status = 1
</otherwise>
</choose>
</where>
</select>
5.4 set 标签
<!-- set标签自动去除多余的逗号 -->
<update id="updateSelective" parameterType="User">
UPDATE users
<set>
<if test="username != null">username = #{username},</if>
<if test="email != null">email = #{email},</if>
<if test="age != null">age = #{age},</if>
<if test="status != null">status = #{status},</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>
5.5 foreach 标签
<!-- 批量查询 IN -->
<select id="findByIds" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="List">
INSERT INTO users (username, email, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.email}, #{user.age})
</foreach>
</insert>
<!-- 批量更新 -->
<foreach collection="list" item="user" separator=";">
UPDATE users SET username = #{user.username} WHERE id = #{user.id}
</foreach>
5.6 sql片段
<!-- 定义可复用的SQL片段 -->
<sql id="userColumns">
id, username, email, age, status, create_time, update_time
</sql>
<select id="findAll" resultType="User">
SELECT <include refid="userColumns"/> FROM users
</select>
<select id="findById" resultType="User">
SELECT <include refid="userColumns"/> FROM users WHERE id = #{id}
</select>
六、MyBatis-Plus入门
6.1 什么是MyBatis-Plus
MyBatis-Plus(简称MP) 是MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,简化开发、提升效率。
6.2 核心特性
| 特性 | 说明 |
|---|---|
| 基础CRUD | 内置通用Mapper和Service,无需编写基础SQL |
| 条件构造器 | 强大的LambdaQueryWrapper |
| 代码生成器 | 根据数据库表自动生成Entity/Mapper/Service/Controller |
| 分页插件 | 内置分页插件,物理分页 |
| 逻辑删除 | 支持逻辑删除,数据不物理移除 |
| 自动填充 | 创建时间、更新时间等字段自动填充 |
| 乐观锁 | 内置乐观锁插件 |
6.3 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
6.4 实体类注解
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("users") // 指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
private String username;
private String email;
private Integer age;
@TableField("status") // 指定列名
private Integer status;
@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充
private LocalDateTime updateTime;
@TableLogic // 逻辑删除字段
private Integer deleted;
@Version // 乐观锁版本号
private Integer version;
}
6.5 Mapper接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 继承BaseMapper即可获得基础CRUD能力,无需编写任何SQL
}
6.6 基础CRUD操作
@Service
@RequiredArgsConstructor
public class UserServiceImpl {
private final UserMapper userMapper;
// ========== 查询 ==========
// 根据ID查询
public User getById(Long id) {
return userMapper.selectById(id);
}
// 查询所有
public List<User> list() {
return userMapper.selectList(null);
}
// 条件查询
public List<User> findActiveUsers() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
.ge(User::getAge, 18)
.orderByDesc(User::getCreateTime);
return userMapper.selectList(wrapper);
}
// 统计
public long countByAge(Integer age) {
return userMapper.selectCount(
new LambdaQueryWrapper<User>().ge(User::getAge, age)
);
}
// ========== 新增 ==========
public void insert(User user) {
userMapper.insert(user);
}
// ========== 更新 ==========
public void updateById(User user) {
userMapper.updateById(user);
}
// 条件更新
public void updateStatus(Long id, Integer status) {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, id)
.set(User::getStatus, status);
userMapper.update(null, wrapper);
}
// ========== 删除 ==========
public void deleteById(Long id) {
userMapper.deleteById(id); // 逻辑删除:UPDATE users SET deleted=1 WHERE id=?
}
}
七、MyBatis-Plus进阶
7.1 LambdaQueryWrapper详解
// 查询条件构建
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 等值查询
wrapper.eq(User::getStatus, 1);
// 不等查询
wrapper.ne(User::getStatus, 0);
// 模糊查询
wrapper.like(User::getUsername, "张");
wrapper.likeLeft(User::getUsername, "张"); // %张
wrapper.likeRight(User::getUsername, "张"); // 张%
// 范围查询
wrapper.between(User::getAge, 18, 30);
wrapper.in(User::getStatus, Arrays.asList(1, 2, 3));
wrapper.notIn(User::getStatus, Arrays.asList(0, -1));
// NULL判断
wrapper.isNull(User::getEmail);
wrapper.isNotNull(User::getEmail);
// 排序
wrapper.orderByAsc(User::getAge);
wrapper.orderByDesc(User::getCreateTime);
// 只查询部分字段
wrapper.select(User::getId, User::getUsername, User::getEmail);
// 分组
wrapper.groupBy(User::getStatus);
// HAVING
wrapper.having("count(*) > {0}", 5);
// OR条件
wrapper.eq(User::getStatus, 1)
.or()
.eq(User::getStatus, 2);
// 嵌套条件
wrapper.and(w -> w.eq(User::getAge, 18).or().eq(User::getAge, 20));
// 链式调用
List<User> users = userMapper.selectList(wrapper);
7.2 LambdaUpdateWrapper
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(User::getStatus, 0)
.set(User::getStatus, 1)
.set(User::getUpdateTime, LocalDateTime.now());
userMapper.update(null, updateWrapper);
7.3 条件拼接工具类
/**
* 条件拼接,避免大量if判断
*/
public LambdaQueryWrapper<User> buildQuery(UserQuery query) {
return new LambdaQueryWrapper<User>()
.like(StringUtils.hasText(query.getUsername()),
User::getUsername, query.getUsername())
.eq(query.getStatus() != null,
User::getStatus, query.getStatus())
.ge(query.getMinAge() != null,
User::getAge, query.getMinAge())
.le(query.getMaxAge() != null,
User::getAge, query.getMaxAge())
.orderByDesc(User::getCreateTime);
}
7.4 自动填充
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
7.5 逻辑删除配置
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 逻辑删除字段
logic-delete-value: 1 # 已删除的值
logic-not-delete-value: 0 # 未删除的值
// 执行删除时,实际执行:UPDATE users SET deleted=1 WHERE id=?
userMapper.deleteById(1L);
// 查询时自动过滤已删除数据:SELECT * FROM users WHERE deleted=0
userMapper.selectList(null);
7.6 乐观锁
// 1. 注册乐观锁插件
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
// 2. 使用乐观锁
public void updateWithLock(User user) {
// 先查询获取当前版本号
User current = userMapper.selectById(user.getId());
user.setVersion(current.getVersion());
// 更新时自动带上版本条件
// UPDATE users SET username=?, version=version+1 WHERE id=? AND version=?
userMapper.updateById(user);
}
八、代码生成器
8.1 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
8.2 代码生成器使用
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Map;
public class CodeGenerator {
public static void main(String[] args) {
String projectPath = System.getProperty("user.dir");
FastAutoGenerator.create(
"jdbc:mysql://localhost:3306/testdb", "root", "123456")
// 全局配置
.globalConfig(builder -> {
builder.author("yourname")
.outputDir(projectPath + "/src/main/java")
.disableOpenDir() // 生成后不打开目录
.enableSwagger(); // 启用Swagger注解
})
// 包配置
.packageConfig(builder -> {
builder.parent("com.example")
.moduleName("demo")
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.controller("controller")
.pathInfo(Map.of(
OutputFile.xml, projectPath + "/src/main/resources/mapper"
));
})
// 策略配置
.strategyConfig(builder -> {
builder.addInclude("users", "orders", "products") // 要生成的表
.addTablePrefix("t_", "sys_") // 表前缀
// Entity策略
.entityBuilder()
.enableLombok()
.enableTableFieldAnnotation()
.logicDeleteColumnName("deleted")
// Mapper策略
.mapperBuilder()
.enableMapperAnnotation()
// Service策略
.serviceBuilder()
.formatServiceFileName("%sService")
// Controller策略
.controllerBuilder()
.enableRestStyle();
})
// 使用Freemarker模板引擎
.templateEngine(new FreemarkerTemplateEngine())
// 执行生成
.execute();
}
}
8.3 生成的文件结构
src/main/java/com/example/demo/
├── entity/
│ └── User.java
├── mapper/
│ └── UserMapper.java
├── service/
│ ├── UserService.java
│ └── impl/
│ └── UserServiceImpl.java
└── controller/
└── UserController.java
src/main/resources/mapper/
└── UserMapper.xml
九、分页插件
9.1 配置分页插件
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(500L); // 单页最大500条
interceptor.addInnerInterceptor(paginationInterceptor);
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
9.2 分页查询
@Service
@RequiredArgsConstructor
public class UserServiceImpl {
private final UserMapper userMapper;
/**
* 简单分页
*/
public IPage<User> page(int pageNum, int pageSize) {
Page<User> page = new Page<>(pageNum, pageSize);
return userMapper.selectPage(page, null);
}
/**
* 条件分页
*/
public IPage<User> pageWithCondition(int pageNum, int pageSize, UserQuery query) {
Page<User> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.like(StringUtils.hasText(query.getUsername()), User::getUsername, query.getUsername())
.eq(query.getStatus() != null, User::getStatus, query.getStatus())
.orderByDesc(User::getCreateTime);
return userMapper.selectPage(page, wrapper);
}
}
9.3 分页结果对象
// IPage<User> 包含以下信息:
IPage<User> result = userService.page(1, 10);
result.getRecords(); // 当前页数据列表
result.getTotal(); // 总记录数
result.getPages(); // 总页数
result.getCurrent(); // 当前页码
result.getSize(); // 每页大小
result.hasNext(); // 是否有下一页
result.hasPrevious(); // 是否有上一页
9.4 自定义SQL分页
// Mapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 自定义SQL分页(Page参数必须在第一个位置)
IPage<User> selectUserPage(Page<User> page, @Param("query") UserQuery query);
}
<!-- UserMapper.xml -->
<select id="selectUserPage" resultType="User">
SELECT u.*, d.name AS dept_name
FROM users u
LEFT JOIN departments d ON u.dept_id = d.id
<where>
<if test="query.username != null">
AND u.username LIKE CONCAT('%', #{query.username}, '%')
</if>
<if test="query.status != null">
AND u.status = #{query.status}
</if>
</where>
ORDER BY u.create_time DESC
</select>
十、常见面试题解析
Q1:MyBatis的#{ }和${}有什么区别?
#{}:预编译处理,会将参数替换为?,然后通过PreparedStatement设置参数值,能防止SQL注入${}:字符串替换,直接将参数值拼接到SQL中,存在SQL注入风险
一般参数值使用 #{},动态表名、列名、ORDER BY等只能使用 ${}
Q2:MyBatis的一级缓存和二级缓存?
| 级别 | 作用域 | 默认状态 | 生命周期 |
|---|---|---|---|
| 一级缓存 | SqlSession | 开启 | SqlSession创建到关闭 |
| 二级缓存 | Mapper(namespace) | 关闭 | 应用级别 |
一级缓存在同一个SqlSession中相同查询会命中缓存;二级缓存需要手动开启,跨SqlSession共享。
Q3:MyBatis-Plus和MyBatis的关系?
MyBatis-Plus是MyBatis的增强工具,不是替代品:
- 完全兼容MyBatis的所有功能
- 新增了BaseMapper、条件构造器、代码生成器、分页插件等
- 简单CRUD用MP的内置方法,复杂SQL仍使用MyBatis的XML编写
Q4:如何避免MyBatis的N+1查询问题?
<!-- 1. 使用嵌套结果(JOIN查询)替代嵌套查询 -->
<resultMap id="UserWithDeptMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<association property="dept" javaType="Dept">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
</resultMap>
<select id="findUserWithDept" resultMap="UserWithDeptMap">
SELECT u.*, d.id AS dept_id, d.name AS dept_name
FROM users u LEFT JOIN departments d ON u.dept_id = d.id
</select>
<!-- 2. 使用批量查询 -->
<!-- 3. 使用MyBatis-Plus的batch模式 -->
十一、总结与下篇预告
本篇要点回顾
| 主题 | 核心内容 |
|---|---|
| MyBatis基础 | 配置、Mapper接口、XML映射、#{}与${} |
| XML映射 | 结果映射、关联查询、嵌套结果/查询 |
| 注解开发 | @Select/@Insert/@Update/@Delete、Provider |
| 动态SQL | if/where/set/choose/foreach/sql |
| MyBatis-Plus | BaseMapper、条件构造器、自动填充、逻辑删除 |
| 代码生成器 | FastAutoGenerator、模板引擎 |
| 分页插件 | 配置、IPage、自定义SQL分页 |
💬 互动问题
- 你在项目中使用MyBatis还是MyBatis-Plus?有什么使用心得?
- 复杂的动态SQL你是怎么编写和调试的?
- 遇到过哪些MyBatis的坑?
📢 下篇预告
下一篇:【38 Spring Boot入门】——我们将进入Spring Boot的世界,学习自动配置原理、核心注解、配置文件、多环境切换、Starter机制等核心知识!
2000

被折叠的 条评论
为什么被折叠?



