ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot 3整合MyBatis-Plus连接MySQL完整指南与避坑实践

Spring Boot 3整合MyBatis-Plus连接MySQL完整指南与避坑实践 1. 为什么 Spring Boot 3 让很多人卡在了第一步说个真实经历我接手过一个老项目升级的活儿pom 文件里 Spring Boot 版本从 2.7 升到 3.2结果编译报错差点把人整崩溃。不是业务代码的问题而是依赖之间的版本兼容关系全变了。Spring Boot 3 的这次升级本质上是一次“底层地基更换”从 Java EE 规范迁移到了 Jakarta EE 9包名javax.*全部改成了jakarta.*Java 版本基线直接抬到了 17。如果你用的是 Spring Boot 3但 JDK 还停留在 8那连运行都起不来。这也就直接导致了一个非常现实的问题很多网上搜到的教程尤其是 2022 年之前写的拿过来大概率是跑不通的。光是spring-boot-starter-web里那个javax.servlet改成jakarta.servlet就足够让一堆复制粘贴的同学浪费一整天。而 MySQL 连接、MyBatis-Plus 整合这块踩的坑更多——驱动类名、数据库方言、分页插件、逻辑删除、自动填充这些每一个点在新版本里都有讲究。先说结论Spring Boot 3 下连接 MySQL 并整合 MyBatis-Plus核心就三件事——依赖引对、配置写对、 插件配好。听起来简单但每一件都有细节。这篇文章不整虚的给的是我在实际项目里反复验证过的完整流程以及踩坑之后的良药。1.1 版本基线JDK 17、Spring Boot 3.x、MyBatis-Plus 3.5.3这可能是整个项目最容易被忽略的一环。很多人一上来就去写配置结果项目都启动不了回头才发现基础环境压根没达标。我现在的标准配置是组件版本要求说明JDK17Spring Boot 3 强制要求推荐用 17 LTSSpring Boot3.2.x / 3.3.x3.2 之后对 MyBatis-Plus 兼容更稳定MyBatis-Plus3.5.5建议 3.5.73.5.3 开始才支持 Spring Boot 3MySQL5.7 / 8.08.0 建议用mysql-connector-j驱动Maven3.6.3低版本对 JDK 17 支持不友好这里有个很容易踩的雷如果你用mybatis-plus-boot-starter一定要确认版本号。3.5.3 之前的老版本根本没有针对 Spring Boot 3 做过适配启动时会报ClassNotFoundException: javax.sql.DataSource之类的异常。尤其是从网上抄的依赖版本经常是老的 3.4.x、3.5.1那基本是必炸。顺带提一句网上有一批教程还在教用com.mysql:mysql-connector-java这个坐标。Spring Boot 3 之后官方把驱动坐标统一改成了com.mysql:mysql-connector-j。虽然老的坐标在大多数情况下也能拉下来但依赖传递时容易和 Spring Boot 自带的版本管理打架建议直接用新的。2. 初始化 Spring Boot 3 工程的两种姿势如果你想从零开始搭建有两条路可以走我都试过各有优劣。2.1 通过 Spring Initializr 创建推荐打开 start.spring.io 项目类型选 Maven语言 JavaSpring Boot 选 3.2.x 或更高版本依赖里搜索并添加Spring Web、MySQL Driver就足够起步了。有一点必须提醒Initializr 生成的工程默认是 JDK 17 编译级别。如果本机装的 Maven 是 3.5 以下的构建时会直接报错。建议直接用 Maven 3.8或者干脆用 IDEA 自带的 Maven 也行。2.2 手动创建 Maven 工程理解结构更好的方式如果你喜欢把每一个依赖都在自己掌控下手动创建也行。核心 pom 依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MySQL 8.x 驱动Spring Boot 3 依赖管理会帮我们锁定版本 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- MyBatis-Plus Spring Boot3 专用 starter -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意看mybatis-plus-spring-boot3-starter这个坐标它和老的mybatis-plus-boot-starter不是同一个。Spring Boot 3 场景必须用带spring-boot3的版本这个是 MyBatis-Plus 官方在 3.5.3 时做的拆分很多老教程没提到这点抄错就白折腾了。3. MySQL 数据源配置看似简单坑全在细节里依赖解决了接下来就是application.yml里的数据源配置。这里建议用 YAML 格式代码整洁而且能写注释方便维护。3.1 基础配置spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mybatis_plus_demo?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456这一段有四个细节值得注意driver-class-name 到底写什么MySQL 8 驱动类名是com.mysql.cj.jdbc.Driver不是老教程里的com.mysql.jdbc.Driver。后者在 MySQL 8 驱动里已经被标记为过时真跑起来可能会有兼容问题。serverTimezoneAsia/Shanghai这是必须的。如果不指定时区MySQL 8 驱动默认会用服务器时区往往和本机差 8 小时查出来的时间全是错的。这个坑我印象太深了之前有个项目查create_time总是慢 8 小时排查了半天最后发现就是没配时区。allowPublicKeyRetrievaltrue这个比较特殊。MySQL 8 的 caching_sha2_password 加密插件默认要求 SSL 或 RSA 公钥传输如果不加这个参数首次连接时会报Public Key Retrieval is not allowed。本地开发环境直接加上线上环境再看情况处理。useSSLfalse本地开发不要启用 SSL否则每次连接都多一次握手慢不说偶尔还会报证书相关的警告。3.2 日志配置与 SQL 输出整合了 MyBatis-Plus 之后第一步肯定是想看看 SQL 到底是怎么执行的。在application.yml里加一段mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这样会把你项目里所有的 SQL 语句和参数打印到控制台。格式长这样 Preparing: SELECT id,name,age,email,deleted FROM user WHERE deleted0 AND id? Parameters: 1(Long) Total: 1生产环境记得把这行删掉或者改成日志级别控制不然 SQL 全量输出到日志文件里不仅占空间还可能有敏感数据泄露风险。3.3 连接池参数基于 HikariCPSpring Boot 3 默认的数据库连接池是 HikariCP基本不需要额外配置也能用但高并发场景下建议做一些显式调优spring: datasource: hikari: minimum-idle: 5 maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000这些参数通常不需要动但是如果你在压测时发现连接获取超时第一反应应该是来看这里的值。最大连接数不是越大越好20 在绝大多数业务场景下已经够用了。4. MyBatis-Plus 3.5.x 整合从写实体类到跑通 CRUD依赖和数据源都齐了接下来就是 MyBatis-Plus 的正式整合。这个环节是重头戏很多问题都出在这里。4.1 核心配置与大写映射关系mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case: true意味着数据库字段user_name能自动映射到 Java 属性userName这是 MyBatis-Plus 最省心的地方。如果不想用自动映射注解TableField可以手动指定列名但能省则省。id-type: auto表示主键使用数据库自增策略这样新增操作时不用手动设置 id。关于logic-delete-field这是 MyBatis-Plus 的逻辑删除配置全局指定之后所有带deleted字段的表都会自动处理逻辑删除查询时自动追加deleted0条件删除会自动变成UPDATE ... SET deleted1。这一点很重要等会儿专门说。4.2 实体类与数据表的映射关系以一张最简单的用户表为例CREATE TABLE user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30), age INT, email VARCHAR(50), deleted TINYINT(1) DEFAULT 0, create_time DATETIME, update_time DATETIME );对应的实体类Data TableName(user) public class User { TableId(type IdType.AUTO) private Long id; private String name; private Integer age; private String email; TableLogic private Integer deleted; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }几个点解释一下TableName如果类名和表名一致可以省略。不一致时必须指定大小写不敏感。TableLogic逻辑删除注解配合全局配置使用。也可以用TableLogic(value 0, delval 1)显式指定逻辑未删除值和已删除值防止全局配置漏配。TableField(fill FieldFill.INSERT)这是自动填充的关键注解配合MetaObjectHandler一起用后面细说。注意create_time和update_time这些字段类型用了 LocalDateTime千万别再用java.util.Date在 JDK 17 环境下java.util.Date处理 LocalDateTime 的序列化问题会很麻烦。4.3 Mapper 层与扫描配置Mapper 接口继承BaseMapperT顺手就把单表 CRUD 全拿下了Mapper public interface UserMapper extends BaseMapperUser { }Mapper注解可以写在每个 Mapper 接口上也可以写在启动类上通过MapperScan( com.example.demo.mapper)统一扫描。我建议用MapperScan一个注解全搞定不用在接口上反复标记。MyBatis-Plus 3.5.x 里BaseMapper自带的方法已经覆盖了绝大多数单表操作selectById、selectList、selectOne、insert、deleteById、updateById、selectPage等。真正需要手写 SQL 的业务场景其实越来越少。4.4 启动类与测试启动类不用特殊处理确保在根包下就行SpringBootApplication MapperScan(com.example.demo.mapper) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }写个测试类验证一下整套链路通不通SpringBootTest class UserMapperTest { Autowired private UserMapper userMapper; Test void contextLoads() { User user new User(); user.setName(张三); user.setAge(25); user.setEmail(zhangsanexample.com); int rows userMapper.insert(user); System.out.println(插入条数: rows); System.out.println(回填主键: user.getId()); } }注意一个细节MyBatis-Plus 的insert成功之后会把数据库自增的主键回填到传入对象的id属性上。这是很实用的功能后续如果要用到这个 id 去操作关联数据就不用再查一遍了。5. 分页插件MyBatis-Plus 最实用也是最容易配错的功能分页应该是日常开发里用到最多的功能之一。MyBatis-Plus 的分页插件配好了就是神器配不好就是各种分页失效、内存分页的假象。5.1 配置方式注意版本差异老版本的配置方式是通过Bean向容器里注入PaginationInnerInterceptor配置类上还有一个MapperScan注解。Spring Boot 3 下面依然是这个套路但如果你用的是 3.5.9 之后的版本MybatisPlusInterceptor的包名和 API 基本没变放心用Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(DbType.MYSQL); // 设置单页最大条数防止有人恶意传一个超大页码 paginationInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }DbType.MYSQL一定要指定别偷懒省略。PaginationInnerInterceptor会根据数据库类型生成不同的分页 SQL如果数据库类型传错或为空某些操作会走通用的内存分页数据量一大就卡死。5.2 分页查询的实际用法PageUser page new Page(1, 10); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getAge, 20) .orderByAsc(User::getId); PageUser userPage userMapper.selectPage(page, wrapper); System.out.println(总记录数: userPage.getTotal()); System.out.println(当前页数据: userPage.getRecords());这里想强调一个细节selectPage返回的Page对象里total默认是自动查询的。Page构造第一个参数是页数从 1 开始第二个参数是每页条数。有些新手会把页数传 0那查出来的结果就会不对。MyBatis-Plus 的Page如果页码从 0 开始有些版本会直接把第一页数据当成第二页返回这个要特别留意。5.3 手写 SQL 时如何让分页生效项目里难免有一些复杂的多表联查需要手写 SQL。如果想让自定义 SQL 也支持 MyBatis-Plus 分页Mapper 接口里写成这样Mapper public interface UserMapper extends BaseMapperUser { PageUser selectUserPage(PageUser page, Param(age) Integer age); }XML 里正常写查询语句不需要带LIMITselect idselectUserPage resultTypecom.example.demo.entity.User SELECT * FROM user where if testage ! null AND age #{age} /if /where /selectMyBatis-Plus 的分页插件会自动在 SQL 后面拼接LIMIT你只需要保证 Mapper 方法的第一个参数是Page对象即可。这个约定要是忘了分页插件就不会拦截执行方法会被当成普通查询处理。6. 逻辑删除、自动填充、乐观锁日常开发高频能力这三个能力是 MyBatis-Plus 的杀手锏级特性配置对了能让代码量断崖式减少。6.1 逻辑删除的完整链路逻辑删除的核心价值是“不删数据、只标记状态”在审计类系统里几乎是标配。全局配置mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0实体类上对应的字段加TableLogic之后调用userMapper.deleteById(1L)时真实执行的 SQL 是UPDATE user SET deleted1 WHERE deleted0 AND id?执行selectById(1L)时自动追加SELECT id,name,age,email,deleted FROM user WHERE deleted0 AND id?这个设计的好处显而易见误删也能救。但坏处也很隐蔽数据库唯一索引要小心。比如用户表手机号设置了唯一索引用户点删除之后deleted变成了 1此时再注册同手机号的新用户会触发唯一索引冲突。我们当时处理方案有两种一种是把唯一索引改成(phone, deleted)联合索引另一种是删除时给 phone 拼一个时间戳后缀类似13800000000_deleted_1699999999。逻辑删除字段索引问题。如果没有给deleted建立合适的索引扫表时每次都会带上deleted0这个条件大表上性能会受影响。6.2 自动填充创建时间、更新时间不再手动 set每次 insert/update 都要手动给createTime、updateTime赋值写多了真的会烦。MyBatis-Plus 的自动填充机制就是用来干这个的。先定义字段注解上面实体类里已经提到然后实现一个MetaObjectHandlerComponent 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()); } }这样以后凡是实体类里标了insertFill或updateFill的字段在 MyBatis-Plus 执行 SQL 之前会自动填充值业务代码里再也不用写那两行set了。有一点容易踩坑如果实体类字段用了private LocalDateTime createTime但在实体里自己赋了初值自动填充可能会不生效。strictInsertFill的语义是只在字段为空时才去填充如果你在 setter 里已经给了值它就不覆盖了。所以自动填充的字段最好保持 null让框架来处理。6.3 乐观锁避免超卖和覆盖更新多用户同时修改同一行数据时后提交的人很容易把先提交的人覆盖掉。MyBatis-Plus 提供了乐观锁插件基于版本号机制。配置里加一个拦载器注意顺序Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; }实体类加版本字段Version private Integer version;数据库表加version字段默认值 0。这样执行updateById时框架自动生成UPDATE user SET name?, version1 WHERE id? AND version0如果更新到的行数为 0说明版本已经被别人改了你需要重试或提示用户。这个机制在处理并发更新场景时特别有用比如库存扣减、余额变更。7. 条件构造器LambdaQueryWrapper 的正确打开方式如果你还在拿QueryWrapper拼字符串列名你在 MyBatis-Plus 3.5.x 下的体验会很痛苦。推荐直接用LambdaQueryWrapper编译期就能检验字段名是否写错。LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getAge, 20) .like(User::getName, 张) .between(User::getId, 1L, 100L) .orderByDesc(User::getId); ListUser users userMapper.selectList(wrapper);从User这个 lambda 引用的形式可以看出lambda 写作User::getName写错字段名时编译期报错能够早一点发现问题。但条件构造器也有自己的限制。比较常见的坑是条件构造器里用了 OR 时没有加括号SQL 拼接逻辑直接错了。比如想表达(age 20 OR email LIKE %qq.com) AND deleted 0wrapper.eq(User::getAge, 20) .or() .like(User::getEmail, qq.com) .eq(User::getDeleted, 0);这段代码最终生成的 where 条件会变成age 20 OR (email LIKE %qq.com AND deleted 0)和你想要的完全不一样。正确写法wrapper.and(w - w.eq(User::getAge, 20) .or() .like(User::getEmail, qq.com)) .eq(User::getDeleted, 0);这个陷阱在我刚用 MyBatis-Plus 的时候就踩过导致查出了不少已删除的数据最后定位到 SQL 才发现括号问题。8. 事务Transactional 在 Spring Boot 3 里的正确姿势整合 MyBatis-Plus 之后事务依然是基于 Spring 的Transactional注解这个没变。Service public class UserService { Autowired private UserMapper userMapper; Transactional(rollbackFor Exception.class) public void createWithTransaction(User user) { userMapper.insert(user); // 模拟后续操作抛异常则全部回滚 if (user.getAge() 0) { throw new RuntimeException(年龄非法); } } }两个点必须记住rollbackFor Exception.class这句一定要写。默认情况下 Spring 只对RuntimeException和Error回滚如果你抛的是自定义的 checked exception不加这个参数事务是不会回滚的。这个细节够坑我见过不止一次生产事故就是这个原因。Transactional加在private方法上无效。Spring 的事务是基于 AOP 动态代理实现的private方法根本不会被代理拦截。如果同一个类里有两个方法A()调用B()B加了事务注解也不会生效因为走的是this的引用而非代理对象。9. 手写复杂 SQLXML 还是注解MyBatis-Plus 把单表 CRUD 都包了但复杂查询你还是需要自己写 SQL。这里的选择会影响你整个项目的可维护性我比较推荐 XML 方案。9.1 XML 方式在resources/mapper/目录下建一个UserMapper.xml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.UserMapper select idselectUserWithOrders resultTypecom.example.demo.vo.UserVO SELECT u.*, o.order_no FROM user u LEFT JOIN order o ON o.user_id u.id WHERE u.deleted 0 if testname ! null and name ! AND u.name LIKE CONCAT(%, #{name}, %) /if ORDER BY u.id DESC /select /mapper别忘了在application.yml里配置 mapper XML 的路径mybatis-plus: mapper-locations: classpath*:mapper/**/*.xmlclasspath*:带星号和classpath:不带星号区别很大。带星号可以扫描到多个 jar 包里的 XML 文件如果你的项目是多模块结构建议直接用带星号的形式。9.2 注解方式简单 SQL 用注解确实舒服Select(SELECT * FROM user WHERE age #{age}) ListUser selectByAge(Integer age);但 SQL 一长注解里写起来就很难看而且 XML 可以动态 SQL 的方式处理更复杂的标签组装涉及的 if、where、foreach 在 XML 里也更直观。我的经验是单表操作交给 MyBatis-Plus 内置方法多表查询和复杂统计统一走 XML。10. 从 0 到 1 跑通一个接口的完整案例前面把各种配置和能力都过了一遍现在完整地串一下从数据库表到 GET 接口一个项目是怎么跑起来的。10.1 表结构CREATE TABLE user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30) NOT NULL, age INT DEFAULT 0, email VARCHAR(50), deleted TINYINT(1) DEFAULT 0, version INT DEFAULT 0, create_time DATETIME, update_time DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;version字段是给乐观锁用的deleted给逻辑删除用。10.2 Controller Service MapperController 层RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; GetMapping(/{id}) public ResultUser getUserById(PathVariable Long id) { return Result.success(userService.getById(id)); } GetMapping public ResultPageUser pageUsers(RequestParam(defaultValue 1) Integer current, RequestParam(defaultValue 10) Integer size) { return Result.success(userService.pageUsers(current, size)); } }Service 层Service public class UserService { Autowired private UserMapper userMapper; public User getById(Long id) { return userMapper.selectById(id); } public PageUser pageUsers(Integer current, Integer size) { PageUser page new Page(current, size); return userMapper.selectPage(page, new LambdaQueryWrapperUser().orderByDesc(User::getId)); } }这样一个用户查询接口就通了。请求GET /api/user/1会打到 MySQL执行一条SELECT然后把结果以 JSON 格式返回MyBatis-Plus 自动帮你加了逻辑删除过滤、字段映射等逻辑。11. 踩坑实录那些被版本和配置折磨的夜晚最后这部分把我在实际项目中踩过的、以及在帮别人排查时见到的高频问题做一个汇总能帮你省下大量时间。11.1 Invalid bound statement (not found)这是最常见的报错没有之一。原因基本都是 Mapper XML 文件和 Mapper 接口没有建立映射。排查链路检查 XML 的namespace是否和接口全限定名一致。检查 XML 里的id是否对应方法名。检查mapper-locations是否配置正确文件是否打进了target/classes。检查 Mapper 接口是否被Mapper或MapperScan扫描到。11.2 数据库连接失败Public Key Retrieval is not allowed连接 MySQL 8 时出现这个错误Caused by: java.sql.SQLException: Public Key Retrieval is not allowed原因MySQL 8 默认使用caching_sha2_password认证插件而连接是加密的驱动首次连接需要获取服务器公钥。解决方案在 JDBC URL 上加参数allowPublicKeyRetrievaltrueuseSSLfalse。11.3 mybatis-plus 自动填充不生效检查三点实体字段有没有加TableField(fill FieldFill.X)。MetaObjectHandler有没有被 Spring 管理是不是加了Component。执行 insert 时是不是通过 MyBatis-Plus 的insert/updateById执行的。如果你用自定义 SQL 方式直接 insert自动填充不会干扰你的 SQL注意确保 SQL 里包含了这些字段。11.4 分页插件不生效查出来全部数据可能的原因分页拦载器没配置或没被加载。Mapper 方法第一个参数不是Page类型。你调用的是selectList而不是selectPage。自定义 SQL 语句没有把Page放在第一位参数。11.5 Spring Boot 3 下 javax 包名冲突如果你的项目里出现类似javax.servlet找不到类的错误基本都是因为引了某些老依赖传递了javax包。在 Spring Boot 3 中应该全部使用jakarta.*包。排查时检查mvn dependency:tree把老依赖排除掉即可。12. 项目结构总览与后续扩展方向一套标准化的 Spring Boot 3 MySQL MyBatis-Plus 项目结构长这样com.example.demo ├── DemoApplication.java # 启动类SpringBootApplication MapperScan ├── config │ ├── MybatisPlusConfig.java # 分页插件、乐观锁插件 │ └── MyMetaObjectHandler.java # 自动填充处理器 ├── controller │ └── UserController.java ├── service │ └── UserService.java ├── mapper │ └── UserMapper.java ├── entity │ └── User.java └── vo / dto └── UserVO.javaresources/mapper/下放 XML 文件application.yml统一配置数据源、MyBatis-Plus、日志级别等。整个体系跑通之后后面可以研究的内容还挺多的p6spy格式化控制台 SQL看参数比 StdOutImpl 更舒服。MyBatis-Plus 的Db静态工具类不用注入 Mapper 就能做简单的 CRUD。多数据源切换用dynamic-datasource-spring-boot3-starter。代码生成器用mybatis-plus-generator一键生成实体、Mapper、Service、Controller。分布式场景下的并发控制可以试试Version乐观锁与 Redisson 分布式锁的配合。我个人在实际操作中的体会是MyBatis-Plus 把单表 CRUD 的复杂度几乎打到了零但凡是省事儿的功能背后都有一套自己的约定。配置对了它是效率引擎配置错了它比手写 MyBatis 还让人头大。所以每次搭新项目我第一件事就是把mybatis-plus的版本钉死然后从分页插件和自动填充开始配这两块最容易忘、也最容易出问题。希望这篇内容能帮你跳过那些让人抓狂的夜直接把项目跑起来。
返回列表