Spring Boot整合MyBatis-Plus保姆级教程:CRUD、分页、逻辑删除与乐观锁
前两天帮组里的新人梳理项目结构发现大家对 Spring Boot 整合 MyBatis-Plus 这件事普遍处在“能跑通但不懂原理”的阶段。跟着博客敲一遍能出结果换一个真实业务场景就卡住半天要么被一个小配置绊倒要么说不清为什么这么配。我干脆把从零搭工程、整合、写 CRUD到分页、自动填充、逻辑删除、乐观锁这些高频功能以及自己实际踩过的坑完整整理了一遍照着敲就行。这篇文章默认你懂 Java 基础、会用 Maven 和 IDEA但不要求你之前用过 MyBatis。代码全部给全配置逐行说明图片需要的地方用文字描述清楚。保姆级不是只给结果重点是告诉你每一步背后的原因这样换一个项目你也能自己搞定。1. 为什么要在 Spring Boot 项目里引入 MyBatis-Plus1.1 它把原生 MyBatis 里最琐碎的重复工作给干掉了先想一个最普通的场景一张用户表你要写新增、按 ID 查询、按 ID 更新、按 ID 删除。用原生 MyBatis你得在 Mapper.xml 里写四个 SQL每个 SQL 要处理参数映射、结果集映射再写一个 Mapper 接口。单表还能忍表一多这种重复代码会占掉你大量的开发时间而且通常一点技术含量都没有。MyBatis-Plus 的做法是提供一个BaseMapperT你只要让 Mapper 接口继承它上面说的单表 CRUD 就全部有了。不需要写 XML不需要写 SQLinsert、deleteById、selectById、updateById 这些方法直接调用。我见过不少第一次用的人很怀疑真的不用写 SQL 吗真的不用。MyBatis-Plus 会在运行时根据实体类上的注解自动生成 SQL比如selectById内部拼出来的就是SELECT id,name,age,email FROM user WHERE id?。这样省下来的时间不是一点点。一个项目二三十张表每张表省四五个 SQL那就是一百多个不必要维护的模板代码。团队新人有更多精力去写真正的业务逻辑而不是天天复制粘贴 SQL。1.2 它和 Spring Data JPA 不是一个赛道别纠结选谁很多人在选 ORM 的时候会拿 MyBatis-Plus 和 Spring Data JPA 做对比。JPA 走的是全自动 ORM 路线实体类关联关系映射得很完善父类继承、懒加载这些做得非常细而 MyBatis-Plus 本质上是 MyBatis 的增强工具没有改变 MyBatis 的 SQL 灵活性只是在单表操作层面帮你去掉了重复劳动。项目里到底用哪个主要看团队写 SQL 的习惯。如果你的团队喜欢把复杂 SQL 牢牢握在自己手里用 MyBatis-Plus 会舒服很多。它不替你做多表关联也不会在你的复杂查询里帮倒忙。需要写 join 的时候老老实实写 XML完全不受影响。而 JPA 查复杂报表时往往要借助 Specification 或者 JPQL学习成本和纠错成本都偏高。1.3 为什么现在这么多项目都在用核心原因就是四个字非侵入式。你的 Mapper 还是那个 Mapper你的实体类也还是普通 POJOMyBatis-Plus 只是提供了通用方法和插件你不用它的时候项目也可以正常跑。对老项目改造很友好对新项目又足够快。加上它把分页、逻辑删除、自动填充、乐观锁这些日常必备功能都做成了插件程序员不需要自己造轮子。项目要的是按时交付而不是把时间花在写一个通用分页插件上。MyBatis-Plus 这套东西的使用门槛不高文档也一直有人维护社区有大量现成踩坑记录企业在选型时也会考虑这个因素。不过也别把它神话。它擅长的是单表 CRUD 和通用能力,真正复杂的业务查询、多表关联分析、大数据量报表该用原生 SQL 还是要用。正确的心态是用它来提高效率而不是以为它能替代你写 SQL 的能力。2. 从零搭建工程骨架版本选型比代码先决定成败2.1 先准备一套能直接复制的环境参数搭建工程之前先确定 JDK、Spring Boot、MyBatis-Plus 之间的版本对应关系。很多人项目启动就报错翻半天发现是 Spring Boot 3 用了 MyBatis-Plus 旧版本包名javax和jakarta不兼容导致的。我目前的建议组合如下。场景JDKSpring BootMyBatis-Plus数据库传统稳定方案JDK 82.7.x3.5.x 任意新版本MySQL 5.7 / 8.0新项目推荐JDK 173.2.x / 3.3.x3.5.3 或 3.5.x 最新版MySQL 8.0Spring Boot 3 必须配 JDK 17 以上同时 MyBatis-Plus 要选择mybatis-plus-spring-boot3-starter注意这个 starter 从 3.5.3 版本开始才稳定可用。如果你在 Spring Boot 3 项目里用了旧的mybatis-plus-boot-starter很可能启动直接报 ClassNotFoundException。数据库驱动也要注意Spring Boot 2.x 时代默认用 MySQL Connector/J 8.xSpring Boot 3 里依赖管理也内置了正确版本不需要手动指定。2.2 用 Spring Initializr 创建最基础的 Spring Boot 工程打开 IDEA新建项目选择 Spring Initializr。Group 填你公司的域名反写Artifact 填项目名Java 版本按前面表格选 17 或 8。关键点在依赖选择这里。刚开始学习时只勾选Spring Web和MySQL Driver就够了其他像 MyBatis、Redis、Security 后面全用 Maven 坐标手动加。为什么要这么保守因为 IDEA 的依赖选择界面里 MyBatis 相关项并不是 MyBatis-Plus直接选容易误导入原生 MyBatis。等 Maven 打包过一遍搞清楚哪些依赖是干什么的再逐步加东西排查问题会轻松很多。创建完之后你会得到标准的目录结构src/main/java下是代码src/main/resources下是配置启动类上一篇文章口吻里的那个main方法入口。先把项目跑起来一次什么都不写启动日志显示 Tomcat started 就说明基础环境没问题。2.3 引入 MyBatis-Plus 依赖的正确姿势打开pom.xml在dependencies里加下面这段dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.7/version /dependency如果项目是 Spring Boot 3依赖要换成dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency版本号以 Maven 中央仓库显示的最新稳定版为准这里只是示例。注意不要手动再加mybatis和mybatis-spring的依赖这个 starter 内部已经带好了自己加很容易出现版本冲突导致启动时出现Invalid value type for attribute factoryBeanObjectType之类的莫名其妙报错。2.4 准备数据库和配置文件在 MySQL 里建一个测试库表结构尽量贴近真实业务。示例 SQL 如下CREATE DATABASE IF NOT EXISTS demo_mp DEFAULT CHARACTER SET utf8mb4; USE demo_mp; CREATE TABLE user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, age INT DEFAULT NULL, email VARCHAR(100) DEFAULT NULL, create_time DATETIME DEFAULT NULL, update_time DATETIME DEFAULT NULL, deleted TINYINT NOT NULL DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;deleted字段先留着后面讲逻辑删除会用到。然后打开src/main/resources/application.yml写入spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo_mp?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: automap-underscore-to-camel-case这一项非常关键。数据库字段习惯用下划线命名Java 实体类习惯用小驼峰user_name对应userName开启这项才能自动映射。MyBatis-Plus 默认是开启的但建议自己在配置里写出来因为后期排查问题时你一眼能确认而不是靠默认值猜。2.5 启动类上加上 MapperScanMyBatis-Plus 需要知道你哪些接口是 Mapper。两种方式在每个 Mapper 接口上加Mapper或者直接在启动类上扫描整个包。我推荐后者SpringBootApplication MapperScan(com.example.demo.mapper) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }MapperScan的作用是把指定包下的接口注册到 Spring 容器里这样 Mapper 才能被注入到 Service。扫描路径要和你的 Mapper 包路径一致包名不一致是新手最容易犯的错误启动后找不到 Mapper 的报错基本都是这里的问题。3. 核心 CRUD 打通从实体类到 Controller3.1 实体类注解每个字段都要有交代在com.example.demo.entity包下创建Userpackage com.example.demo.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.time.LocalDateTime; TableName(user) public class User { TableId(type IdType.AUTO) private Long id; private String name; private Integer age; private String email; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; TableLogic TableField(value deleted) private Integer deleted; // getter/setter 省略实际代码要写上 }三个核心注解要理解透TableName(user)实体类对应的表名。如果类名和表名不一样比如UserEntity对应sys_user不加这个注解就会报错。我习惯哪怕名字一样也显式写上代码自解释性更强。TableId(type IdType.AUTO)主键策略。数据库主键自增就配 AUTO如果是雪花算法生成的主键就不需要这个注解默认分配 ID。AUTO 和 INPUT 的区别后面踩坑部分详细说。TableField(value deleted)当字段名和表字段不一致时用 value 指定列名fill FieldFill.INSERT表示插入时自动填充后面配合 MetaObjectHandler 使用。注意实体类的字段类型建议用包装类型Long、Integer不要用long、int。原因很简单如果查询结果是 NULL基本类型拆箱会直接抛NullPointerException。3.2 Mapper 接口继承 BaseMapper 就算完成数据访问层创建com.example.demo.mapper.UserMapperpackage com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.demo.entity.User; public interface UserMapper extends BaseMapperUser { }这样你不需要写一行 SQL就已经拥有了insert、deleteById、updateById、selectById、selectList、selectCount等常用方法。BaseMapper 内置方法里几个高频率的方法作用selectById按主键查询单条selectList(Wrapper)按条件查询列表selectOne(Wrapper)按条件查询单条多条会报错insert插入一条记录updateById按主键更新null 字段不更新deleteById按主键删除Wrapper是 MyBatis-Plus 的条件构造器后面在 Controller 里演示。在真实项目中我知道很多人喜欢直接在 Controller 注入 Mapper 用但这会把你带到坑里去。正确做法是 Service 依赖 MapperController 依赖 Service保持分层清晰。3.3 Service 层用 IService 减少大量样板代码新建UserServicepackage com.example.demo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.demo.entity.User; public interface UserService extends IServiceUser { }再建实现类UserServiceImplpackage com.example.demo.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.entity.User; import com.example.demo.mapper.UserMapper; import com.example.demo.service.UserService; import org.springframework.stereotype.Service; Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { }是不是很抽象为什么接口和实现类都是空的因为IService和ServiceImpl已经把save、getById、list、updateById、removeById、lambdaQuery、lambdaUpdate这些方法全部实现了你要做的只是指定实体类型和 Mapper。这么做的最大价值是Controller 调用userService.save(user)时走的是 Service 层事务边界哪天你想在保存用户后加一条短信通知逻辑直接在 ServiceImpl 里重写save方法覆盖默认行为即可不用去改 Controller 和 Mapper。这就是分层带来的扩展性。3.4 Controller写一个能直接测试的 CRUD 接口package com.example.demo.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/user) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } PostMapping public User save(RequestBody User user) { userService.save(user); return user; } GetMapping(/{id}) public User getById(PathVariable Long id) { return userService.getById(id); } GetMapping public ListUser list(RequestParam(required false) String name) { LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); if (name ! null !name.isBlank()) { wrapper.like(User::getName, name); } return userService.list(wrapper); } PutMapping public boolean update(RequestBody User user) { return userService.updateById(user); } DeleteMapping(/{id}) public boolean delete(PathVariable Long id) { return userService.removeById(id); } }Controller 里演示了LambdaQueryWrapper。这个类是 MyBatis-Plus 最常用的条件构造器用方法引用User::getName指定字段编译期就能发现字段名写错的问题比字符串写法安全得多。这里的wrapper.like(User::getName, name)最终会变成 SQLWHERE name LIKE %值%。测试一下启动项目用 IDEA 自带的 HTTP Client 或 Postman 发起请求。POST 请求体{ name: 张三, age: 25, email: zhangsanexample.com }返回数据里会有生成的 ID 和自动填充的时间字段自动填充在下一章配置。到这里工程搭建和 CRUD 就算完整跑通了。4. 高频功能分页、自动填充、逻辑删除、乐观锁一次配齐4.1 分页查询只加 limit 思路在 MyBatis-Plus 里行不通直接在代码里拼LIMIT offset, size是可以分页但真正的业务系统通常要求分页查询同时返回当前页数据 总条数而且 SQL 里如果带着参数偏移量写起来又丑又容易漏条件。MyBatis-Plus 的方式是配置一个分页插件然后调用分页方法。在新版本3.4.0 之后必须自定义MybatisPlusInterceptor老版本那个PaginationInterceptor已经废弃了。新建配置类package com.example.demo.config; 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 { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }DbType.MYSQL一定要设置分页插件要依据不同数据库语法拼 SQLMySQL 是LIMITPostgreSQL 是LIMIT OFFSETSQL Server 是OFFSET FETCH NEXT。setMaxLimit(500L)是防止有人恶意传超大页码超过 500 条自动不给查。Controller 里分页GetMapping(/page) public PageUser page(RequestParam(defaultValue 1) long current, RequestParam(defaultValue 10) long size) { PageUser page new Page(current, size); return userService.page(page); }返回的Page对象里自带records、total、current、size这些字段。前端做分页组件时拿这个结构直接渲染即可。注意如果你发现调用page方法后返回的 total 是 0 或者数据是全量查询先别查 SQL检查MybatisPlusInterceptor有没有被 Spring 容器管理。这个配置类最常见的坑是没有加Configuration或者被 ComponentScan 排除外了。4.2 create_time / update_time 自动填充业务代码里不再手动 set很多表都有create_time、update_time字段。如果没有自动填充每次新增和修改都要写user.setCreateTime(LocalDateTime.now())漏一次就会为根因排查浪费一小时。MyBatis-Plus 提供了字段自动填充机制。实体类里已经给字段加了注解TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;再写一个处理器实现MetaObjectHandlerpackage com.example.demo.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; 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()); } }strictInsertFill和strictUpdateFill是推荐写法因为它在字段已经有值的时候不会覆盖。比如你在新增前手动setCreateTime了一个特殊时间自动填充就不会抢走它。这个功能上线后你会发现所有新增修改的代码瘦身很明显原子性的时间和数据库操作在一起不会出现某个请求忘了记录创建时间的尴尬。4.3 逻辑删除数据别真删除非你想被业务同学追着要数据现在的业务系统里物理删除是高风险操作。用户删错了数据是找不回来的。逻辑删除的思路是在表里加一个标记位比如deleted字段删除时 UPDATE 这个标记查询时自动过滤掉标记过的数据。实体类上已经写了TableLogic TableField(deleted) private Integer deleted;再配合全局配置让 MyBatis-Plus 知道逻辑删除的约定规则。在application.yml中补充mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0来一组实际效果操作实际执行的 SQLremoveById(1)UPDATE user SET deleted1 WHERE id1 AND deleted0getById(1)SELECT ... FROM user WHERE id1 AND deleted0selectList(...)SELECT ... FROM user WHERE ... AND deleted0也就是说你什么都不用改删除操作自动从 DELETE 变成 UPDATE查询自动带上deleted0条件。这是 MyBatis-Plus 非常漂亮的设计对业务代码无侵入对旧数据兼容性好。注意TableLogic只能配一个字段且逻辑删除字段在实体类里的类型基本用Integer存 0 和 1。假如你有个复杂查询要查被删除的数据那就只能写原生 SQL因为绕过 MyBatis-Plus 的方法才不受逻辑删除拦截。4.4 乐观锁解决并发更新最后写入覆盖的问题举一个简单场景两个人同时打开一个订单页面A 把价格改成 100 并保存B 紧接着把价格改成 80 并保存。如果没有任何锁机制B 的操作直接覆盖了 AA 认为的数据被偷偷改了。悲观锁用数据库for update解决但并发量高时性能受影响。乐观锁的思路是更新前检查数据版本号是否还和当初读到的一致。MyBatis-Plus 实现乐观锁分三步。第一步表加 version 字段ALTER TABLE user ADD COLUMN version INT NOT NULL DEFAULT 0;第二步实体类加字段和Version注解Version private Integer version;第三步配置类里加入乐观锁插件pagination.addInnerInterceptor(new OptimisticLockerInnerInterceptor());注意OptimisticLockerInnerInterceptor也要加到MybatisPlusInterceptor中。完整代码public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }之后用userService.updateById(user)时只要 user 对象里带了 version 字段MyBatis-Plus 会自动在 UPDATE 语句里加上WHERE version?并把versionversion1一起更新。如果更新影响行数为 0说明数据被别人改过了你的更新不会生效业务层就有机会提醒用户数据已变更请刷新后重试。乐观锁适合读多写少的场景比如库存、订单版本、配置项更新。写并发特别高的场景还是要配合其他方案综合评估。5. 实际项目里踩过的坑每条都让人卡过半小时以上5.1 主键策略选错导致插入失败我接手过一个项目数据库主键是自增但实体类没有加TableId(type IdType.AUTO)。MyBatis-Plus 默认的IdType在没配置时是在程序里生成雪花 ID结果每次 insert 都会把雪花 ID 塞给主键列数据库自增设置完全失效。数据量大了之后主键顺序乱成一团分页排序逻辑也受牵连。正确判断标准如果是 MySQL 自增主键实体字段指定TableId(type IdType.AUTO)如果分库分表或者分布式环境才用默认雪花 ID。如果主键是业务系统里自己传入的字符串要用IdType.INPUT。5.2 updateById 不会更新 null 字段这是最容易被误解的行为使用updateById(user)时传进来的 user 对象某个字段是 nullMyBatis-Plus 默认不会把这个字段更新到 NULL而是直接跳过。这通常是我们想要的更新部门时不需要把不传的字段置空。但有些业务就是要清空某个字段比如用户取消备注期望把remark更新为 NULL。这时就得用LambdaUpdateWrapper显式设置userService.lambdaUpdate() .eq(User::getId, 1L) .set(User::getRemark, null) .update();如果你希望整个项目更新时允许 null 字段覆盖数据库数据可以把实体字段注解改成TableField(updateStrategy FieldStrategy.IGNORED)但我不建议全局这么干危险性太大一个不小心就把别人的数据清空了。5.3 分页插件不生效查出来还是全部数据这是分页里最经典的坑。表象是前端传了 page1、size10接口返回 100 条。排查链路照这个顺序走先打印 SQL 日志看打印出来的 SQL 末尾有没有LIMIT。没有 LIMIT说明分页插件根本没拦截。看配置类是否注册了MybatisPlusInterceptorBean配置类有没有被 Spring 扫描到。最容易犯的是把配置类写在启动类子包之外。确认拦截器顺序上没有早退。在MybatisPlusInterceptor里分页插件和乐观锁插件顺序无所谓但不要使用多个MybatisPlusInterceptorBean。Spring Boot 3 项目里用旧包名com.baomidou.mybatisplus.extension.plugins的老式配置已经失效必须用inner.Interceptor。5.4 字段映射不上报Unknown column错误数据库字段是user_name实体字段是userName。虽然 MyBatis-Plus 默认开启驼峰映射但如果你在配置里手抖覆盖了map-underscore-to-camel-case: false或表字段里有非常规命名就会出现查询报Unknown column username这种问题。排查技巧先看控制台打印的 SQL 语句拿 SQL 去数据库客户端执行观察是 SELECT 部分字段名拼错了还是 WHERE 条件字段拼错了。解决方式是给实体字段加TableField(user_name)显式指定列名不要依赖全局映射。毕竟全局配置只管驼峰管不了所有命名怪癖。5.5 数据库字段用了保留字或特殊字符我给一张业务表加字段时顺手把字段命名成了order。在 MySQL 里order是排序关键字生成的 SQL 直接语法报错。这时候用TableField明确指定列名并加反引号TableField(order) private String orderNo;类似的还有desc、group、rank。写实体类之前先确认字段名不和数据库保留字冲突省得后面所有 SQL 都带着反引号非常难受。5.6 不要重复引入 mybatis 相关依赖MyBatis-Plus 的 starter 内部已经引入了mybatis和mybatis-spring。有些同学为了保险又手动加了一份结果版本冲突项目启动时报出各种奇怪的 bean 加载失败。记住一个原则整合 MyBatis-Plus 时pom 里只保留一个 MyBatis-Plus 依赖其他mybatis相关依赖全部不要手动添加除非你很清楚自己在干什么。排查依赖冲突可以用 IDEA 自带的 Maven 插件执行dependency:tree看到同一个构件出现多个版本就是问题信号。5.7 自动填充失效时间字段一直是 null自动填充失效最常见的原因有三个注解fill没写、MetaObjectHandler没被 Spring 管理、字段名和实体字段名不一致。特别是第三个我在代码生成器里发现生成出来的实体字段createTime但MetaObjectHandler里写成了create_timestrictInsertFill找不到目标字段就直接跳过了。自动填充的字段名必须以实体字段名为准不是数据库列名。所以说新手阶段遇到问题第一反应千万别是重装依赖换框架版本而是先开 SQL 日志看实际语句。SQL 会告诉你它到底执行了什么很多看似玄学的问题本质都是配置没生效而配置没生效的根因往往就在被忽略的基础知识点上。最后再补充一个我自己的使用习惯。公司里的新项目我一般会配合 MyBatis-Plus 的代码生成器一起用把实体类、Mapper、Service、Controller 一次性生成出来生成后手工只改业务相关部分。但代码生成器默认生成的实体类里TableField注解经常带着FieldFill的旧写法所以生成完一定要对着我这篇文章的配置检查一遍特别是createTime、updateTime、deleted、version这四个字段。养成这个习惯后基本不会在初始化阶段浪费时间去调那些早就被踩烂的配置问题。

相关新闻

ASP.NET WebForms值班管理系统开发实践与避坑指南

ASP.NET WebForms值班管理系统开发实践与避坑指南

简介:这是一份面向ASP.NET开发者的值班管理系统源码,基于Visual Studio 2010平台、Access数据库构建,适用于单位内部值班排班、交接班记录、历史查询等日常管理场景。压缩包共包含77个文件,以aspx页面为主,配合ascx用户…

2026/10/18 10:18:41 阅读更多 →
AI日报制作全流程:从信息采集到结构化压缩的工程实践

AI日报制作全流程:从信息采集到结构化压缩的工程实践

1. 一份“AI 日报”到底在记录什么每天早上打开信息流,铺天盖地的“某模型又刷新榜单”“某工具一夜爆火”“某大厂开源新框架”,看多了会发现一个尴尬的事实:信息量很大,但真正能沉淀下来的判断很少。我做 AI 领域的内容整理差不…

2026/10/18 10:17:41 阅读更多 →
WebdriverIO terminateApp参数缺失:从根因到实战解决方案

WebdriverIO terminateApp参数缺失:从根因到实战解决方案

终极指南:解决WebdriverIO中terminateApp命令参数缺失的实战方案做移动端自动化测试的,尤其是用WebdriverIO跑Appium这套体系的,应该对terminateApp这个命令不陌生。它用来在测试结束或者某个用例场景中强制终止一个App,比driver.…

2026/10/18 10:17:41 阅读更多 →

最新新闻

基于深度学习的目标检测道路坑洼识别:YOLOv8训练与部署实践

基于深度学习的目标检测道路坑洼识别:YOLOv8训练与部署实践

毕业设计选“基于深度学习的道路坑洼识别”这个方向的同学,最近明显多了起来。原因不难理解:既能用到Python、深度学习、图像识别这些当下最热的技术词汇,又有一个清晰可落地的应用场景,数据也相对容易获取。但真正动手做起来&…

2026/10/18 11:13:00 阅读更多 →
机器学习与深度学习道路坑洼识别:从数据集到部署的完整实践指南

机器学习与深度学习道路坑洼识别:从数据集到部署的完整实践指南

在开始写之前,我先说句题外话。每年这个时间,都有很多同学在课程设计和毕业设计的选题边缘反复横跳,既想找个有区分度的方向,又怕最后做不出来。道路坑洼识别这个题目,说实话,方向是好的——机器学习和深度…

2026/10/18 11:13:00 阅读更多 →
2026年AI接口聚合服务实力横评:词元之河与硅基流动稳定性对比

2026年AI接口聚合服务实力横评:词元之河与硅基流动稳定性对比

这轮横评的方法论先交代清楚:七十二小时连续压测、十二类典型调用场景模拟,再叠加对三十七位一线开发者的访谈,围绕协议兼容、稳定性、成本透明、密钥治理等八项指标逐一打分。行业背景也不容忽视——《中国AI基础设施白皮书》的调研显示&…

2026/10/18 11:13:00 阅读更多 →
MySQL 8.0免安装版配置指南:从解压到多实例部署

MySQL 8.0免安装版配置指南:从解压到多实例部署

简介:这是一份面向后端开发、运维及数据库初学者的 MySQL 8.0 免安装绿色版资源,主打解压即用,无需繁琐的安装与初始化流程,适合需要快速搭建本地测试环境、临时验证 SQL 语句或学习数据库原理的场景。压缩包共 420 个文件&#x…

2026/10/18 11:13:00 阅读更多 →
AI辅助教材写作如何降低查重率?五个阶段实操指南

AI辅助教材写作如何降低查重率?五个阶段实操指南

1. 先别急着让AI“写”,先搞清楚查重系统到底在“查”什么干过教材编写的人应该都有同感:最让人头疼的不是“没内容可写”,而是辛辛苦苦熬了几个通宵弄出来的稿子,往查重系统里一丢,重复率直接飙到百分之三四十。这时候…

2026/10/18 11:13:00 阅读更多 →
虚拟专题:联邦学习 | 基于 TaoToken 统一 API 通道的联邦学习研究综述与工程落地指南

虚拟专题:联邦学习 | 基于 TaoToken 统一 API 通道的联邦学习研究综述与工程落地指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 11:12:00 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →