尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

SpringBoot与MyBatis-Plus整合实战:从零构建高效数据层开发

SpringBoot与MyBatis-Plus整合实战:从零构建高效数据层开发 1. 项目概述为什么是SpringBoot MyBatis-Plus如果你正在用Java做Web开发尤其是企业级应用的后端那么SpringBoot和MyBatis-Plus这对组合大概率是你绕不开的技术栈。SpringBoot以其“约定大于配置”的理念极大地简化了Spring应用的初始搭建和开发过程。而MyBatis-Plus简称MP作为MyBatis的增强工具在保留MyBatis所有特性的基础上提供了大量开箱即用的功能比如强大的CRUD操作、分页、性能分析、代码生成器等。把它们俩整合在一起目的非常明确用最少的配置和代码实现最高效、最规范的数据层开发。这不仅仅是“能用”而是追求一种“优雅”和“高效”。想象一下你不再需要为每一个实体类手写增删改查的SQL分页查询只需要几行配置复杂的条件查询可以用Lambda表达式优雅地构建甚至连基础的Service层代码都可以自动生成。这就是整合带来的直接价值——将开发者从重复、繁琐的“体力劳动”中解放出来更专注于核心业务逻辑的实现。这篇文章我会从一个有多年实战经验的开发者角度带你从零开始完成一次“超详细”的整合。不仅仅是把依赖配上去、把Demo跑通更重要的是我会拆解每一步背后的考量分享在实际项目中如何配置更稳妥以及那些官方文档可能不会明说但踩过坑才知道的“潜规则”。无论你是刚接触这个组合的新手还是想优化现有项目的老手相信都能找到有用的东西。2. 环境准备与项目初始化在开始敲代码之前搭建一个干净、标准化的开发环境是第一步。这能避免很多因环境不一致导致的诡异问题。2.1 开发工具与依赖选择我个人的主力IDE是IntelliJ IDEA Ultimate版它对SpringBoot的支持堪称完美。当然如果你习惯使用Eclipse或VS Code配合相应插件也完全没问题。关键在于项目管理工具强烈推荐使用Maven或Gradle。这里我以Maven为例因为它在国内的生态和认知度更广。创建一个SpringBoot项目有多种方式通过IDEA的Spring Initializr这是最直观的方式。在新建项目时选择“Spring Initializr”它本质上是一个图形化的 start.spring.io 。通过官方网页 start.spring.io你可以直接在网页上勾选依赖生成项目压缩包再导入IDE。这种方式更灵活不受IDE限制。手动搭建对于想深入了解项目结构的老手可以创建一个普通的Maven项目然后手动添加SpringBoot的父POM和依赖。对于新手我推荐第一种方式。在IDEA的Spring Initializr界面你需要关注几个关键选择ProjectMaven Project。LanguageJava。Spring Boot选择一个稳定的版本如3.2.x或3.1.x。SpringBoot 3.x需要Java 17如果你的环境是Java 8则需要选择SpringBoot 2.7.x官方已停止功能更新仅维护。本文基于SpringBoot 3.x和Java 17。Project Metadata填写你的Group组织标识如com.example、Artifact项目标识如demo和Package name包名。Dependencies这是核心。我们需要添加Spring Web用于构建Web应用。MySQL Driver数据库驱动根据你用的数据库选择。MyBatis Framework注意这里只选这个不要选成“MyBatis Plus”。因为MyBatis-Plus的依赖我们需要手动在pom.xml里添加以控制其版本。点击创建后IDEA会为你生成一个标准的SpringBoot项目结构。2.2 关键依赖的POM文件配置项目创建好后打开pom.xml文件。Spring Initializr已经为我们添加了SpringBoot父工程和基本的依赖。现在我们需要手动添加MyBatis-Plus的依赖。找到dependencies部分在已有依赖的基础上添加如下内容dependencies !-- Spring Initializr 生成的依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version !-- 版本需与SpringBoot 3.x匹配 -- /dependency !-- 手动添加MyBatis-Plus 核心依赖 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version !-- 请使用当前最新稳定版 -- /dependency !-- 其他常用工具 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies关键点解析版本对齐mybatis-spring-boot-starter的版本必须与你的SpringBoot大版本兼容。SpringBoot 3.x通常对应3.0.x版本。mybatis-plus-boot-starter的版本建议使用官方仓库中的最新稳定版。依赖关系mybatis-plus-boot-starter已经包含了mybatis-spring-boot-starter和MyBatis的核心依赖。但上面我们依然显式声明了mybatis-spring-boot-starter这是为了更清晰地控制版本避免传递依赖可能带来的冲突。这是一种更稳妥的做法。Lombok这是一个非必需但强烈推荐的工具。它通过注解自动生成Getter、Setter、构造函数等代码能让实体类非常简洁。记得在IDEA中安装Lombok插件并启用注解处理。注意依赖添加后记得点击Maven工具栏的刷新按钮让IDEA下载相关的Jar包。如果网络不好导致下载失败可以检查Maven配置的镜像仓库是否为国内源如阿里云镜像。3. 核心配置详解与最佳实践配置是整合的灵魂合理的配置能让后续开发事半功倍也能避免很多运行时错误。3.1 数据库连接与MyBatis-Plus基础配置首先在src/main/resources/application.yml或application.properties中配置数据库连接和MyBatis-Plus的基本参数。我更喜欢YAML格式因为结构更清晰。# 应用服务端口 server: port: 8080 # 数据源配置 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/your_database?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password # MyBatis-Plus 配置 mybatis-plus: configuration: # 控制台打印完整带参数SQL仅开发环境开启 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启驼峰命名自动映射。数据库字段 user_name 会自动映射到实体属性 userName map-underscore-to-camel-case: true # 配置默认的执行器。REUSE 执行器会重用预处理语句性能较好是默认推荐值 default-executor-type: reuse global-config: db-config: # 全局逻辑删除字段名需与实体类注解对应 logic-delete-field: deleted # 逻辑已删除值默认为 1 logic-delete-value: 1 # 逻辑未删除值默认为 0 logic-not-delete-value: 0 # 全局表前缀如果所有表都有共同前缀如 t_ # table-prefix: t_ # 主键类型。AUTO 为数据库自增INPUT 为手动输入ASSIGN_ID 为雪花算法ASSIGN_UUID 为UUID id-type: ASSIGN_ID # 指定Mapper XML文件的位置。如果SQL写在XML里需要配置。如果全用注解或MP自带方法可不配。 mapper-locations: classpath*:/mapper/**/*.xml # 配置类型别名包这样在XML里可以直接写类名不用写全限定名 type-aliases-package: com.example.demo.entity配置项深度解读log-impl: StdOutImpl这是开发阶段的神器。它会将MyBatis执行的SQL语句及其参数完整地打印到控制台方便你调试和检查SQL是否正确。切记在生产环境一定要关闭此配置可以设置为org.apache.ibatis.logging.nologging.NoLoggingImpl或使用更专业的日志框架如SLF4JLogback来控制日志级别。map-underscore-to-camel-case: true这是连接数据库命名习惯下划线和Java命名习惯驼峰的桥梁。强烈建议开启能省去大量TableField注解。id-type: ASSIGN_ID这是MyBatis-Plus雪花算法的主键生成策略。即使数据库主键是自增AUTO在插入数据前实体对象的Id字段也会被预先填充一个雪花算法ID。这有利于在分布式环境下保持ID全局唯一。如果你完全依赖数据库自增就设为AUTO。logic-delete-field逻辑删除配置。这代表你希望用一个字段如deleted来标记数据是否被删除而不是物理删除。配置后MP自带的delete方法会自动变为更新deleted字段select方法会自动加上deleted0的条件。这是一个极其好用的生产特性。3.2 实体类、Mapper与Service的创建规范配置好后我们开始创建核心的三层结构实体类Entity、数据访问层Mapper、服务层Service。MyBatis-Plus为每一层都提供了强大的基类支持。1. 实体类 (Entity)在com.example.demo.entity包下创建User.java。package com.example.demo.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; Data // Lombok注解生成getter, setter, toString等 TableName(sys_user) // 指定对应数据库表名如果表名和类名一致忽略大小写和下划线可省略 public class User { /** * 主键 * ASSIGN_ID使用雪花算法生成ID默认 * 如果数据库是自增可以用 TableId(type IdType.AUTO) */ TableId(type IdType.ASSIGN_ID) private Long id; private String username; private String password; private String email; /** * 字段填充策略。插入时自动填充当前时间 */ TableField(fill FieldFill.INSERT) private LocalDateTime createTime; /** * 字段填充策略。插入和更新时自动填充当前时间 */ TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; /** * 逻辑删除标记。需要配合全局配置使用 * TableLogic 注解优先级高于全局配置 */ TableLogic private Integer deleted; // 对应全局配置的 logic-delete-field }2. Mapper接口在com.example.demo.mapper包下创建UserMapper.java。它只需要继承MyBatis-Plus提供的BaseMapper接口并指定泛型为实体类。package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.demo.entity.User; import org.apache.ibatis.annotations.Mapper; Mapper // 这个注解很重要让Spring知道这是一个MyBatis Mapper。也可以在启动类加 MapperScan 批量扫描 public interface UserMapper extends BaseMapperUser { // 此时你已经拥有了针对User表的CRUD方法无需编写任何XML或注解SQL // 例如insert, deleteById, updateById, selectById, selectList, selectPage, selectMaps, ... }3. Service接口与实现在com.example.demo.service包下创建IUserService.java接口和UserServiceImpl.java实现类。MP同样提供了Service层的增强。// IUserService.java package com.example.demo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.demo.entity.User; public interface IUserService extends IServiceUser { // 可以在此定义一些复杂的、非通用的业务查询方法 User getByUsername(String username); }// UserServiceImpl.java package 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.IUserService; import org.springframework.stereotype.Service; Service // 注册为Spring Bean public class UserServiceImpl extends ServiceImplUserMapper, User implements IUserService { Override public User getByUsername(String username) { // 这里演示一个自定义查询。可以使用baseMapper即UserMapper调用其方法 // 也可以使用lambdaQuery()这种更优雅的方式 return this.lambdaQuery() .eq(User::getUsername, username) .one(); // 查询一条记录 } }核心优势体现通过继承BaseMapper和IService你的UserMapper和IUserService瞬间获得了数十个通用的数据操作方法完全不需要你写一句SQL。这是整合带来的最直接的效率提升。4. 核心功能实战与高级特性基础架子搭好了我们来实际使用它并探索一些高级、实用的特性。4.1 基础CRUD与条件构造器使用让我们在Controller里快速测试一下CRUD。创建UserController.java。package com.example.demo.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.example.demo.entity.User; import com.example.demo.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/user) public class UserController { Autowired private IUserService userService; // 1. 新增 PostMapping public boolean saveUser(RequestBody User user) { // MP会自动处理主键如果配置了ASSIGN_ID和字段填充如createTime return userService.save(user); } // 2. 根据ID查询 GetMapping(/{id}) public User getById(PathVariable Long id) { return userService.getById(id); } // 3. 条件查询使用LambdaQueryWrapper推荐防误写 GetMapping(/list) public ListUser listUsers(RequestParam(required false) String username, RequestParam(required false) String email) { LambdaQueryWrapperUser wrapper Wrappers.lambdaQuery(); if (username ! null !username.isEmpty()) { wrapper.like(User::getUsername, username); // 模糊查询 username like %?% } if (email ! null !email.isEmpty()) { wrapper.eq(User::getEmail, email); // 等值查询 } wrapper.orderByDesc(User::getCreateTime); // 按创建时间倒序 return userService.list(wrapper); } // 4. 更新 PutMapping public boolean updateUser(RequestBody User user) { // updateById 会根据主键更新非空字段 return userService.updateById(user); } // 5. 逻辑删除配置了逻辑删除后调用removeById实际是更新deleted字段 DeleteMapping(/{id}) public boolean deleteUser(PathVariable Long id) { return userService.removeById(id); } }条件构造器Wrapper详解 这是MyBatis-Plus的灵魂功能之一。它让你用Java代码的方式安全、优雅地构建查询条件。上面用的是LambdaQueryWrapper它通过方法引用如User::getUsername来指定字段避免了硬编码字符串可能带来的拼写错误。还有QueryWrapper使用字符串列名在某些动态SQL场景下更灵活。4.2 分页查询的标准化实现分页是Web应用中最常见的需求。MyBatis-Plus的分页插件需要显式配置。1. 配置分页插件创建一个配置类MybatisPlusConfig.java。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(); // 添加分页插件并指定数据库类型这里是MySQL interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 还可以添加其他插件如乐观锁插件 // interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }2. 使用分页查询修改或新增Controller方法。// 在UserController中添加 import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; GetMapping(/page) public IPageUser pageUsers(RequestParam(defaultValue 1) Long current, RequestParam(defaultValue 10) Long size) { // 1. 创建分页对象。current: 当前页size: 每页大小 PageUser page new Page(current, size); // 2. 可以添加查询条件 LambdaQueryWrapperUser wrapper Wrappers.lambdaQuery(); wrapper.orderByDesc(User::getCreateTime); // 3. 执行分页查询。page对象会被填充总记录数、分页记录列表等信息 return userService.page(page, wrapper); }调用/user/page?current2size5返回的结果不仅包含第2页的5条数据还包含total总记录数、pages总页数等分页信息前端可以直接使用。4.3 自动填充与乐观锁实战自动填充我们之前在实体类中用TableField(fill FieldFill.INSERT)标注了createTime和updateTime。现在需要实现一个处理器来告诉MP如何填充。创建处理器MyMetaObjectHandler.javapackage 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 // 注册为Spring组件 public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { // 插入时为有 TableField(fill FieldFill.INSERT) 注解的字段填充值 this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); // 还可以填充其他字段如操作人ID等 // this.strictInsertFill(metaObject, createBy, Long.class, UserContext.getCurrentUserId()); } Override public void updateFill(MetaObject metaObject) { // 更新时为有 TableField(fill FieldFill.INSERT_UPDATE) 或 FieldFill.UPDATE 的字段填充值 this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }乐观锁用于解决并发更新问题。假设我们给User表加一个version字段。在User实体类中添加字段并加上Version注解。Version private Integer version;在MybatisPlusConfig配置类中将乐观锁插件注释打开。interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());更新数据时MP会自动带上version条件。例如update user set name?, version? where id? and version?。如果版本号不匹配更新行数为0你可以在业务层判断并处理更新失败的情况如重试或提示用户。5. 进阶技巧与生产环境考量当项目从Demo走向生产一些进阶配置和考量就变得至关重要。5.1 多数据源与动态表名多数据源一个项目连接多个数据库。SpringBoot官方不直接提供多数据源支持但可以通过配置多个DataSource、SqlSessionFactory和TransactionManager来实现。MyBatis-Plus有一个第三方扩展dynamic-datasource-spring-boot-starter可以简化这一过程。其核心思路是使用注解DS(“数据源名称”)在Service或Mapper方法上切换数据源。引入该依赖后配置会变得相对简单但需要仔细处理事务边界问题。动态表名例如按月份分表的场景。MyBatis-Plus提供了ITableNameHandler接口。你可以实现这个接口根据当前线程上下文或参数动态返回表名。然后在配置中将其设置为全局表名处理器。这通常用于日志表、流水表等数据量巨大且按时间分割的场景。5.2 代码生成器效率倍增器对于拥有大量表的项目手动创建Entity、Mapper、Service、Controller是非常枯燥的。MyBatis-Plus提供了一个强大的代码生成器AutoGenerator。你可以编写一个简单的生成器主类配置好数据源、包路径、策略是否生成Controller、Service等、模板引擎默认Velocity运行一次即可生成所有基础代码。这能节省你80%以上的重复劳动。生成的代码风格统一且已经集成了MP的基类开箱即用。这是中大型项目必备的利器。官方文档有详细示例核心是配置DataSourceConfig、PackageConfig、StrategyConfig和TemplateConfig。5.3 性能监控与SQL优化建议关闭开发日志如前所述生产环境务必关闭mybatis-plus.configuration.log-impl的控制台输出或将其级别设为DEBUG并通过日志框架控制。使用P6Spy或Log4j2格式化输出如果你在生产环境仍需监控SQL通常只在特定排查时开启可以使用P6Spy这类第三方组件它能输出格式化、可读性更强的SQL并记录执行时间。警惕N1查询问题这是ORM框架的通病。当你使用MP查询出一个列表N条然后循环遍历每条数据去查询其关联对象时就会产生N1条SQL。解决方案是一次查询手动组装先查询主列表收集所有关联ID再用in语句一次查出所有关联数据在内存中通过Map进行组装。使用MyBatis的collection或association在XML中编写包含JOIN的复杂SQL实现结果映射。这超出了MP通用Mapper的范畴需要回归到MyBatis本身。使用MP的TableField(exist false)自定义查询方法在实体类中定义非数据库字段并提供一个自定义的Mapper方法用一条SQL查询出所有需要的数据并手动填充到这些字段中。合理使用索引MP生成的SQL条件顺序取决于你使用Wrapper添加条件的顺序。确保你的查询条件能够命中数据库索引。对于复合索引要特别注意条件的顺序。6. 常见问题排查与实战心得整合过程很少一帆风顺这里记录几个我踩过的坑和解决方案。6.1 启动报错与依赖冲突问题启动时报java.lang.ClassNotFoundException: org.mybatis.logging.LoggerFactory或类似关于MyBatis的类找不到。排查这通常是依赖冲突或版本不匹配。运行mvn dependency:tree命令查看依赖树检查是否有多个不同版本的mybatis或mybatis-spring被引入。SpringBoot的spring-boot-starter-*系列依赖会管理其内部组件的版本但手动添加的依赖可能会打破这种管理。解决确保mybatis-plus-boot-starter的版本与你的SpringBoot版本兼容。在pom.xml中对冲突的依赖使用exclusions标签排除掉低版本或不需要的传递依赖。统一使用MyBatis-Plus的BOMBill of Materials进行版本管理这是最推荐的方式。6.2 字段映射失败与空值问题问题查询结果中某个字段始终为null但数据库里有值。排查首先检查application.yml中的map-underscore-to-camel-case是否设置为true。如果字段是user_name实体属性是userName这个配置必须开启。如果字段名和属性名不一致例如数据库字段is_adminJava属性admin需要使用TableField(is_admin)进行显式映射。检查实体类属性的类型是否与数据库字段类型匹配。例如数据库是datetimeJava用LocalDateTime数据库是tinyint(1)Java用Boolean。问题使用updateById更新时想把某个字段更新为null但执行后数据库里该字段没变。解决MP的默认更新策略是“非空更新”。即实体对象中为null的字段不会参与到UPDATE语句的SET部分。如果你想将字段更新为null有两种方式使用UpdateWrappernew UpdateWrapperUser().set(email, null).eq(id, 1)。在实体类字段上加上注解TableField(strategy FieldStrategy.IGNORED)但这样会全局忽略该字段的空值判断需谨慎。6.3 分页插件失效与总数查询慢问题调用了page方法但返回的IPage对象里records是全部数据total也是错的好像没分页。排查没有配置分页插件这是最常见的原因。确保MybatisPlusConfig配置类被Spring扫描到并且MybatisPlusInterceptor中加入了PaginationInnerInterceptor。存在多个SqlSessionFactory或MybatisPlusInterceptor在多数据源等复杂配置下可能无意中覆盖了配置。问题分页查询count语句查询总数非常慢尤其是在大表且条件复杂时。优化自定义count查询MP允许你为分页查询指定一个优化的count语句。可以通过创建XML文件定义一个id为方法名_COUNT的查询来实现。例如Mapper方法叫selectUserPage那么可以定义一个selectUserPage_COUNT的查询。关闭count查询如果业务上不需要知道总条数例如手机端无限滚动加载可以在创建Page对象时传入第三个参数falsenew Page(current, size, false)。这样就不会执行count语句。数据库层面优化为count查询涉及的where条件字段建立合适的索引。6.4 事务管理须知SpringBoot默认集成了Spring的事务管理。在Service层的方法上使用Transactional注解即可声明事务。但需要注意MyBatis-Plus的Service方法诸如save,updateById,removeById等方法内部并没有添加Transactional。如果你在一个非事务方法内连续调用多个这样的方法每个方法都会是一个独立的事务。如果需要原子性需要在调用它们的Service方法上添加Transactional。Lambda查询与事务lambdaQuery(),lambdaUpdate()等链式调用本身不涉及事务。事务边界仍然由你添加的Transactional注解决定。多数据源事务这是一个复杂话题。标准的Transactional只能管理一个数据源。如果需要跨数据源的分布式事务需要考虑使用JTA、Seata等分布式事务解决方案这超出了基础整合的范围。整合SpringBoot和MyBatis-Plus本质上是在SpringBoot的自动化配置基础上引入MP的一系列增强约定和工具。核心思想是“配置化”和“约定优于配置”。把基础的连接、事务、映射交给框架开发者聚焦在业务逻辑和那些框架无法自动生成的复杂SQL上。从简单的CRUD到动态数据源从代码生成到性能优化这套组合拳能覆盖从创业项目到大型企业应用的绝大多数场景。关键在于你要理解每一层配置和注解背后的意图这样才能在遇到问题时快速定位在需要扩展时游刃有余。
返回列表