1、MyBatis Plus概述:ORM思想、MyBatis痛点、MyBatis Plus简介与核心特性

1.1 聊聊ORM思想——它到底解决了什么问题?

先问大家一个问题:你写Java代码时,最烦的是什么?

我个人觉得,最烦的就是跟数据库打交道时,那一堆重复的JDBC代码。你想想看,每次都要获取连接、创建Statement、拼SQL、遍历ResultSet、手动封装对象……一个简单的查询,代码能写几十行。

ORM(Object Relational Mapping)就是来解决这个问题的。说白了,它帮我们把数据库里的表,映射成Java里的对象。你操作对象,就等于操作数据库。

核心思想:让开发者用面向对象的方式操作数据库,而不是写一堆SQL字符串。

我记得刚入行那会儿,公司还在用纯JDBC。有一次我写了个分页查询,光ResultSet的遍历就写了30多行,还因为少了个字段映射,查出来的数据全是null。调试了一下午……从那以后,我就成了ORM的忠实拥趸。

常见的ORM框架有Hibernate、JPA、MyBatis。它们各有千秋,但今天咱们重点聊的是MyBatis Plus——一个站在MyBatis肩膀上的“增强工具”。

1.2 MyBatis的痛点——好用,但不够爽

MyBatis本身已经很优秀了。它灵活、SQL可控、性能好。但用久了你会发现,有些地方确实让人头疼。

痛点一:单表CRUD,重复劳动太多

你想想看,每个实体类都要写一个Mapper接口,再写一个对应的XML文件。里面全是insert、update、delete、selectById这些模板代码。我做过统计,一个普通的业务系统,单表操作占了70%以上。这些代码几乎一模一样,就是换个表名、换个字段名。

我的习惯:后来我干脆写了个代码生成器,自动生成这些模板代码。但每次改表结构,还得重新生成一遍,也挺麻烦的。

痛点二:分页查询,自己动手

MyBatis本身不支持物理分页。你得自己写limit语句,还得算偏移量。更麻烦的是,不同数据库的分页语法还不一样。MySQL是limit,Oracle是rownum,PostgreSQL是offset……每次换数据库,分页代码就得重写。

痛点三:条件拼接,容易出错

动态SQL是MyBatis的强项,但写多了你会发现,标签嵌套来嵌套去,可读性越来越差。尤其是那种查询条件有十几个字段的页面,XML里的if标签能写到让你怀疑人生。

我曾经踩过的坑:有一次写一个复杂的查询,条件里有个字段没判空,结果前端传了个空字符串过来,SQL直接变成了where name = '',查出来一堆错误数据。线上事故啊……从那以后,我对动态SQL的判空格外敏感。

痛点四:代码生成,不够智能

MyBatis官方有代码生成器,但生成的代码比较“死板”。比如生成的实体类没有自动填充功能,Mapper里也没有批量插入的方法。你还得自己手动加。

1.3 MyBatis Plus简介——它到底“Plus”在哪?

MyBatis Plus,简称MP。它不是一个新框架,而是MyBatis的增强工具。说白了,它没改变MyBatis的本质,只是在MyBatis的基础上,加了一层“快捷操作”。

你可以把它理解成:MyBatis是手动挡汽车,MP是自动挡。你想体验驾驶乐趣,可以切回手动模式(写原生SQL);想省心省力,就用自动模式(MP封装好的方法)。

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

我最早接触MP是在2018年。当时公司要重构一个老项目,里面几十张表的CRUD代码看得我头皮发麻。用了MP之后,单表操作几乎不用写SQL了,开发效率至少提升了30%。

1.4 核心特性——MP凭什么受欢迎?

MP的核心特性,我总结了6点。这些特性,每一个都在解决MyBatis的痛点。

特性 说明 解决什么痛点
无侵入 只做增强,不改MyBatis原有逻辑 可以平滑升级,老代码不用改
通用CRUD 内置BaseMapper,单表操作零SQL 不用写重复的insert/update/select
条件构造器 Wrapper链式调用,动态SQL更优雅 告别繁琐的if标签嵌套
分页插件 自动拦截SQL,支持多种数据库 不用手动写limit和算偏移量
代码生成器 一键生成Entity、Mapper、Service 省去大量模板代码编写时间
自动填充 自动处理创建时间、更新时间等字段 不用在每个insert/update里手动赋值

1.5 无侵入设计——为什么说它“不改变MyBatis”?

这一点很重要。MP没有重新造轮子,它只是在MyBatis的Executor层做了拦截和增强。

你原来写的那些XML映射文件、自定义SQL、复杂关联查询,MP完全不动。它只在你调用BaseMapper提供的方法时,才会介入。

我的建议:引入MP时,不要急着把所有代码都改成MP风格。先把单表CRUD替换掉,复杂的多表查询还是用原生MyBatis。这样风险最小,收益最大。

1.6 通用CRUD——零SQL的快乐

这是MP最吸引人的特性之一。你只需要让你的Mapper接口继承BaseMapper,然后……就没有然后了。

public interface UserMapper extends BaseMapper<User> {
    // 这里什么都不用写
}

// 直接调用
userMapper.selectById(1L);
userMapper.insert(user);
userMapper.updateById(user);
userMapper.deleteById(1L);
userMapper.selectList(null);

你想想看,以前这些方法你得在XML里写多少行?现在一行代码就搞定了。

而且MP还提供了批量操作的方法,比如insertBatch、updateBatch。虽然底层还是循环单条执行,但至少你不用自己写循环了。

1.7 条件构造器——动态SQL的优雅写法

这是我最喜欢的特性。以前写动态SQL,XML里全是标签,看得眼花缭乱。现在用Wrapper,链式调用,代码清晰多了。

// 查询年龄大于18且名字包含"张"的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18)
       .like("name", "张");
List<User> users = userMapper.selectList(wrapper);

你可能会问:这样写,SQL注入怎么办?

放心,MP的参数都是预编译的,不存在SQL注入问题。这一点我专门测试过,安全得很。

注意:Wrapper里的字段名是数据库字段名,不是实体类属性名。比如实体类是userName,数据库字段是user_name,那Wrapper里要写"user_name"。这一点容易搞混,我刚开始用的时候就踩过这个坑。

1.8 分页插件——告别手动算偏移量

分页是每个系统都有的功能。MP的分页插件用起来非常简单:

// 配置分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}

// 使用分页
Page<User> page = new Page<>(1, 10); // 第1页,每页10条
Page<User> result = userMapper.selectPage(page, null);
List<User> records = result.getRecords(); // 当前页数据
long total = result.getTotal(); // 总记录数

它会自动帮你在SQL后面拼接limit语句,还会自动查询总记录数。你完全不用关心分页的底层实现。

而且它支持多种数据库。你只需要在配置时指定DbType,MP会自动生成对应数据库的分页语法。换数据库?改一行配置就行。

1.9 代码生成器——一键生成,省时省力

MP的代码生成器非常强大。你只需要配置好数据库连接和模板策略,它就能自动生成Entity、Mapper、Service、Controller。

// 简单的代码生成器配置
AutoGenerator mpg = new AutoGenerator();
mpg.setDataSource(dataSourceConfig);
mpg.setGlobalConfig(globalConfig);
mpg.setPackageInfo(packageConfig);
mpg.setStrategy(strategyConfig);
mpg.execute();

生成出来的代码,直接就能用。BaseMapper的方法都有了,Service层也封装好了基本的CRUD。你只需要关注业务逻辑就行。

我的习惯:我会在代码生成器里配置自动填充策略。比如create_time字段自动填充为当前时间,update_time字段自动填充为当前时间。这样生成的实体类,插入和更新时就不用手动处理时间字段了。

1.10 自动填充——再也不用手动赋值时间字段

很多表都有create_time和update_time字段。以前每次insert和update,都得手动set这两个字段。麻烦不说,还容易漏掉。

MP的自动填充功能,可以帮你自动处理:

@TableField(fill = FieldFill.INSERT)
private Date createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

然后在配置类里实现MetaObjectHandler接口:

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
}

配置好之后,你insert或update时,这两个字段会自动填充。再也不用手动赋值了。

1.11 小结——MP到底值不值得学?

说实话,如果你还在用纯MyBatis写单表CRUD,那MP绝对值得学。它不会增加你的学习成本,反而能帮你省下大量时间。

但也要注意,MP不是万能的。复杂的多表关联查询、存储过程、自定义函数,这些还是得用原生MyBatis。MP的定位是“增强工具”,不是“替代品”。

嗯,这一章就聊到这里。下一章我会带大家搭建MP的开发环境,并跑通第一个Demo。到时候咱们手把手操作,你就能感受到MP的爽快了。

一句话总结:MP让MyBatis的单表操作变得极其简单,但又不影响你写复杂SQL的自由。这就是它最吸引人的地方。