MyBatis+MyBatis-Plus 全套 CRUD 数据库实战
MyBatis+MyBatis-Plus 全套 CRUD 数据库实战
写 SQL 写到怀疑人生?MyBatis-Plus 了解一下——连 XML 都能省掉大半,CRUD 写得像点菜一样简单。
一、MyBatis vs MyBatis-Plus:一对亲兄弟
很多新手以为 MyBatis-Plus 是 MyBatis 的替代品,其实它是增强工具包,在 MyBatis 基础上做了大幅强化。两者的关系可以这么理解:
| 对比维度 | MyBatis | MyBatis-Plus |
|---|---|---|
| SQL 编写 | XML / 注解手写 SQL | 内置 CRUD,零 SQL 完成基本操作 |
| 分页 | 手动写 LIMIT + COUNT | 分页插件一行搞定 |
| 条件查询 | 手写 WHERE 拼接 | QueryWrapper 链式调用 |
| 代码生成 | 无 | AutoGenerator 一键生成 |
| 主键策略 | 手动处理 | @TableId 注解,雪花算法/自增按需选 |
一句话总结:MyBatis-Plus 只做增强不做改变,引入它不会影响你原有的 MyBatis 代码,丝滑升级。
二、数据库准备
先搞定 MySQL。假设你已有 MySQL 环境,执行以下建库建表 SQL:
CREATEDATABASEIFNOTEXISTSdemo_dbDEFAULTCHARSETutf8mb4;USEdemo_db;CREATETABLE`user`(`id`BIGINTNOTNULLAUTO_INCREMENTCOMMENT'主键',`username`VARCHAR(50)NOTNULLCOMMENT'用户名',`age`INTDEFAULTNULLCOMMENT'年龄',`email`VARCHAR(100)DEFAULTNULLCOMMENT'邮箱',`create_time`DATETIMEDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COMMENT='用户表';三、SpringBoot 整合 MyBatis-Plus
3.1 pom.xml 依赖
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.5</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency>3.2 application.yml 配置
spring:datasource:url:jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf8mb4username:rootpassword:123456driver-class-name:com.mysql.cj.jdbc.Drivermybatis-plus:configuration:log-impl:org.apache.ibatis.logging.stdout.StdOutImpl# 控制台打印SQLmapper-locations:classpath:mapper/*.xml# XML映射文件路径(没用XML可省略)四、实体类注解:字段映射三板斧
@Data@TableName("user")// 映射表名,类名和表名一致可省略publicclassUser{@TableId(type=IdType.AUTO)// 主键策略:AUTO=数据库自增, ASSIGN_ID=雪花算法privateLongid;@TableField("username")// 字段名映射,驼峰自动转下划线时可省略privateStringusername;privateIntegerage;privateStringemail;@TableField(fill=FieldFill.INSERT)privateLocalDateTimecreateTime;}三个核心注解:
@TableName:指定表名,类名转下划线和表名一致时不用写@TableId:主键注解,IdType.AUTO 是数据库自增,ASSIGN_ID 是雪花算法(分布式推荐)@TableField:字段映射,exist=false ��排除非数据库字段
五、BaseMapper 内置 CRUD——零 SQL 数据库操作
只需定义一个 Mapper 接口继承BaseMapper<T>,所有基础 CRUD 开箱即用:
@MapperpublicinterfaceUserMapperextendsBaseMapper<User>{// 继承BaseMapper后,以下方法全部免费获得,不需要写一行代码}内置方法速览:
// 插入userMapper.insert(user);// 根据ID删除userMapper.deleteById(1L);// 根据ID更新userMapper.updateById(user);// 根据ID查询Useruser=userMapper.selectById(1L);// 查询全部List<User>list=userMapper.selectList(null);// 批量查询List<User>users=userMapper.selectBatchIds(Arrays.asList(1L,2L,3L));拿deleteById举例:MyBatis-Plus 自动生成DELETE FROM user WHERE id = ?,你一行 SQL 都不用写。
六、条件构造器:QueryWrapper 链式查询杀器
当查询条件复杂起来,光靠selectList(null)可不够,这时条件构造器登场:
// QueryWrapper 写法(直接写数据库字段名)QueryWrapper<User>query=newQueryWrapper<>();query.eq("age",25)// age = 25.like("email","@qq.com")// email LIKE '%@qq.com%'.ge("create_time","2024-01-01").orderByDesc("id");List<User>list=userMapper.selectList(query);更推荐LambdaQueryWrapper,用 lambda 表达式避免字段名字符串硬编码:
LambdaQueryWrapper<User>lambda=newLambdaQueryWrapper<>();lambda.eq(User::getAge,25).like(User::getEmail,"@qq.com").in(User::getId,Arrays.asList(1L,2L,3L)).orderByDesc(User::getCreateTime);List<User>list=userMapper.selectList(lambda);// 自动生成SQL:SELECT * FROM user WHERE age = ? AND email LIKE ? AND id IN (?,?,?) ORDER BY create_time DESC常用条件函数:
eq等于 /ne不等于gt大于 /ge大于等于 /lt小于 /le小于等于like模糊查询 /notLike不匹配in包含 /notIn不包含isNull为空 /isNotNull不为空orderByAsc升序 /orderByDesc降序between区间查询
七、分页插件配置
MyBatis-Plus 的分页功能需先注册插件:
@ConfigurationpublicclassMybatisPlusConfig{@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}}使用分页只需两行:
Page<User>page=newPage<>(1,10);// 第1页,每页10条Page<User>result=userMapper.selectPage(page,null);System.out.println("总记录数:"+result.getTotal());System.out.println("总页数:"+result.getPages());System.out.println("当前页数据:"+result.getRecords());输出的 SQL 会自动拼接LIMIT 0,10和SELECT COUNT(*),免去手写分页的繁琐。
八、完整 CRUD 实战:Controller + Service + Mapper
下面给出标准的 Controller-Service-Mapper 三层写法:
// Mapper 层@MapperpublicinterfaceUserMapperextendsBaseMapper<User>{}// Service 接口publicinterfaceUserServiceextendsIService<User>{}// Service 实现@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{}// Controller 层@RestController@RequestMapping("/user")publicclassUserController{@AutowiredprivateUserServiceuserService;@PostMappingpublicResultadd(@RequestBodyUseruser){userService.save(user);// IService 提供的 save 方法returnResult.ok();}@DeleteMapping("/{id}")publicResultdelete(@PathVariableLongid){userService.removeById(id);returnResult.ok();}@PutMappingpublicResultupdate(@RequestBodyUseruser){userService.updateById(user);returnResult.ok();}@GetMapping("/{id}")publicResultgetById(@PathVariableLongid){returnResult.ok(userService.getById(id));}@GetMapping("/page")publicResultpage(@RequestParamintcurrent,@RequestParamintsize){Page<User>page=newPage<>(current,size);LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.orderByDesc(User::getCreateTime);returnResult.ok(userService.page(page,wrapper));}}是的,Service 层也继承了IService和ServiceImpl,同样获得一套现成的 CRUD,连 Service 都不用写逻辑——典型的偷懒利器。
九、常见问题排查
问题一:字段映射不生效
检查mybatis-plus.configuration.map-underscore-to-camel-case是否设为 true(默认就是 true),确认实体字段和数据库字段的驼峰-下划线对应关系。
问题二:分页不生效
最容易踩的坑——分页插件没注册。检查是否有@Configuration类注入MybatisPlusInterceptor并添加PaginationInnerInterceptor。没有这个 Bean,MyBatis-Plus 不做任何分页干预,Page对象就是个摆设。
问题三:updateById 只更新了部分字段
MyBatis-Plus 默认的更新策略是NOT_NULL,即实体字段为 null 时不更新。如果想把某个字段更新为 null,需要在字段上加@TableField(updateStrategy = FieldStrategy.IGNORED)。
MyBatis-Plus 的价值在于把重复劳动自动化,让你聚焦核心业务逻辑。从 BaseMapper 到 IService,从条件构造器到分页插件,每一步都在践行"开发者体验优先"。当然也别因为太方便就忘了学原生 SQL——调试时看不懂控制台打印的 SQL,等于白搭。
