MyBatis-Plus面试题宝典:从基础到高级全面解析(2025持续更新版)

一、核心概念与基础篇

1. MyBatis-Plus是什么?它与原生MyBatis有何本质区别?

MyBatis-Plus(简称MP)是一款强大的MyBatis增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

本质区别:

  • MyBatis:需要开发者自行编写SQL语句,提供了极大的灵活性,但增加了工作量
  • MyBatis-Plus:内置通用Mapper和Service,通过少量配置即可实现单表大部分CRUD操作,同时保留了MyBatis的所有特性

核心关系: MyBatis-Plus ≠ 替代MyBatis,而是 = MyBatis + 增强功能

2. 为什么选择MyBatis-Plus?它的核心优势是什么?

  1. 无侵入性:只做增强不做改变,引入它不会对现有工程产生影响
  2. 损耗小:启动即会自动注入基本CURD,性能基本无损耗
  3. 强大的CRUD操作:内置通用Mapper、通用Service,内置分页插件、性能分析插件等
  4. 强大的条件构造器:支持Lambda形式调用,防止字段拼写错误
  5. 多主键策略支持:支持多种主键策略,包含分布式唯一ID生成器
  6. 热加载:mapper对应的xml支持热加载(特定版本)
  7. 全局拦截插件:提供全表delete、update操作智能分析阻断,预防误操作

二、注解驱动开发:优雅的ORM映射

1. @TableName:解决表名映射问题

当实体类名与数据库表名不一致时使用:

@TableName("sys_user") // 映射到sys_user表
public class User {
    // ...
}

2. @TableId:主键策略详解

MyBatis-Plus提供了多种主键策略:

public class User {
    @TableId(type = IdType.AUTO) // 数据库ID自增
    private Long id;
    
    // 或者
    @TableId(type = IdType.ASSIGN_ID) // 雪花算法生成ID(默认)
    private Long id;
    
    // 或者
    @TableId(type = IdType.ASSIGN_UUID) // UUID
    private String id;
}

各种策略适用场景:

  • AUTO:MySQL等支持自增的数据库
  • ASSIGN_ID:分布式系统,需要全局唯一ID(默认策略)
  • ASSIGN_UUID:需要字符串类型主键的场景
  • INPUT:用户自定义ID

3. @TableField:字段映射解决方案

解决字段名不匹配、非表字段等问题:

public class User {
    @TableField(value = "user_name") // 数据库字段名为user_name
    private String username;
    
    @TableField(exist = false) // 非数据库字段
    private String tempData;
    
    @TableField(select = false) // 查询时不返回该字段
    private String password;
}

4. @Version:乐观锁实现原理

乐观锁实现方式:

  1. 数据库添加version字段
  2. 实体类添加版本字段并加@Version注解
  3. 配置乐观锁插件
public class Product {
    @Version
    private Integer version;
}

执行原理:

-- 查询时获取version
SELECT id, name, price, version FROM product WHERE id = 1;

-- 更新时带version条件
UPDATE product SET price = 100, version = version + 1 
WHERE id = 1 AND version = 1;

5. @TableLogic:逻辑删除优雅实现

逻辑删除配置:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除字段名
      logic-delete-value: 1 # 逻辑已删除值
      logic-not-delete-value: 0 # 逻辑未删除值

实体类配置:

public class User {
    @TableLogic
    private Integer deleted;
}

效果: 调用deleteById方法时实际执行的是UPDATE语句而非DELETE

三、条件构造器:动态SQL的终极解决方案

1. QueryWrapper:复杂查询条件组装

// 查询用户名包含a,年龄在20-30之间,邮箱不为null的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("username", "a")
       .between("age", 20, 30)
       .isNotNull("email")
       .orderByDesc("create_time");

List<User> users = userMapper.selectList(wrapper);

2. LambdaQueryWrapper:类型安全的查询

防止列名拼写错误的最佳实践:

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getUsername, "a")
       .between(User::getAge, 20, 30)
       .isNotNull(User::getEmail)
       .orderByDesc(User::getCreateTime);

List<User> users = userMapper.selectList(wrapper);

3. UpdateWrapper:动态更新语句

UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.set("email", "updated@email.com")
       .set("update_time", new Date())
       .like("username", "admin")
       .gt("age", 20);

userMapper.update(null, wrapper);

四、插件机制:核心功能的扩展基石

1. 分页插件配置与原理

配置方式:

@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

使用方式:

// 分页查询
Page<User> page = new Page<>(1, 10); // 当前页,每页大小
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("username", "a");

IPage<User> userPage = userMapper.selectPage(page, wrapper);

// 获取分页信息
long total = userPage.getTotal();      // 总记录数
long pages = userPage.getPages();      // 总页数
List<User> records = userPage.getRecords(); // 数据列表

底层原理:

  1. 拦截Executor的query方法
  2. 自动生成COUNT查询语句获取总数
  3. 对原始SQL添加LIMIT语句
  4. 封装分页结果到IPage对象

2. 性能分析插件

开发环境配置:

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 性能分析插件,输出SQL及其执行时间
    interceptor.addInnerInterceptor(new PerformanceInnerInterceptor());
    return interceptor;
}

五、高级特性深度解析

1. 自动填充功能

处理创建时间、更新时间等字段的自动填充:

@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());
    }
}

2. 多数据源配置

基于dynamic-datasource-spring-boot-starter实现:

spring:
  datasource:
    dynamic:
      primary: master # 设置默认数据源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/master
          username: root
          password: 123456
        slave:
          url: jdbc:mysql://localhost:3306/slave
          username: root
          password: 123456

使用@DS注解切换数据源:

@Service
@DS("master") // 默认使用主库
public class UserServiceImpl implements UserService {
    
    @DS("slave") // 方法级别切换为从库
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
}

六、经典面试题深度剖析

1. 实体类中,为什么建议使用Integer而不是int?

深度解析:

  • 语义准确性int默认值0与数据库NULL含义不同,Integernull能准确表达"未知"
  • 业务合理性:年龄、分数等字段,0是有意义的数值,而null表示"未填写"
  • 数据库一致性:避免NULL被误转为0导致业务逻辑错误
  • 示例场景:用户积分字段,int默认0表示有0积分,Integer的null表示尚未设置积分

2. #{}和${}的区别及SQL注入防护

对比分析表:

特性#{} (预编译占位符)${} (字符串替换)
处理方式预编译为?,使用PreparedStatement直接拼接字符串
安全性高,防止SQL注入低,有注入风险
引号处理自动添加需手动添加
适用场景值参数传递动态表名、列名

MyBatis-Plus的安全机制:

  • 条件构造器底层全部使用#{}方式
  • 内置SQL注入剥离器
  • 全表更新/删除阻断插件

3. 一级缓存和二级缓存详解

一级缓存(SqlSession级别):

  • 默认开启,基于PerpetualCache
  • 同一个SqlSession内有效
  • 执行DML操作或调用clearCache()时清空

二级缓存(Mapper级别):

  • 需要手动配置开启
  • 基于namespace级别缓存
  • 需要实体类实现Serializable接口
  • 配置方式:
<!-- mapper.xml中 -->
<cache/>
// 实体类
public class User implements Serializable {
    // ...
}

4. 批量操作性能优化

批量插入最佳实践:

// 使用saveBatch方法
List<User> userList = new ArrayList<>();
// ... 添加数据
userService.saveBatch(userList, 1000); // 每批1000条

// 或者使用SQLSession的批量模式
SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH);
UserMapper batchMapper = sqlSession.getMapper(UserMapper.class);
for (User user : userList) {
    batchMapper.insert(user);
}
sqlSession.commit();
sqlSession.close();

5. 多表关联查询解决方案

方案一:使用Wrapper进行简单关联

// 查询VIP用户的订单
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.inSql("user_id", "SELECT id FROM user WHERE level = 'VIP'");
List<Order> orders = orderMapper.selectList(wrapper);

方案二:自定义XML SQL(推荐复杂场景)

// Mapper接口
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
    IPage<OrderVO> selectOrderWithUser(IPage<OrderVO> page, @Param("state") Integer state);
}
<!-- XML映射文件 -->
<select id="selectOrderWithUser" resultType="OrderVO">
    SELECT o.*, u.name as user_name, u.email
    FROM order o 
    LEFT JOIN user u ON o.user_id = u.id
    WHERE o.state = #{state}
</select>

七、MyBatis-Plus优缺点总结

优点:

  1. 开发效率极高:减少80%以上的CRUD代码
  2. 功能丰富强大:分页、乐观锁、逻辑删除等开箱即用
  3. 性能优异:几乎零损耗,与原生MyBatis性能相当
  4. 无侵入设计:与现有MyBatis项目完美兼容
  5. 社区活跃:持续更新,遇到问题容易找到解决方案

缺点:

  1. 学习成本:需要学习Wrapper等特有API
  2. 复杂SQL支持:极度复杂的多表查询仍需XML方式
  3. 版本兼容性:不同版本间配置方式可能有变化

八、面试技巧与实战建议

  1. 理解优于记忆:不要死记硬背,理解每个特性的设计初衷和适用场景
  2. 结合实际项目:用自己的项目经验来说明如何使用MyBatis-Plus解决实际问题
  3. 主动展示深度:在回答基础问题时,适当延伸相关的高级特性和原理
  4. 诚实面对不足:如果遇到不会的问题,坦诚承认并表达学习意愿
  5. 准备代码示例:准备几个典型的代码片段,展示实际使用经验

温馨提示:如需获取更多实战面试题宝典(Spring/MySQL/Redis/MongoDB/Elasticsearch/Kafka等),请持续关注本专栏《面试题宝典》系列文章。

最后建议:MyBatis-Plus虽然强大,但要记住它只是工具。真正的价值在于如何利用它高效地解决业务问题。在面试中展现出你对技术的思考深度和解决问题的实际能力,这才是最重要的。

希望这份宝典能够帮助你在2025的面试中取得优异的成绩!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值