JDK 17与Spring Boot 3环境下MyBatis-Plus整合与高级应用实战
在实际 Java 后端开发中技术栈的升级换代是持续进行的。从 JDK 8 到 JDK 17从 Spring Boot 2.x 到 Spring Boot 3.x每一次升级都带来了性能提升、语法简化以及新的 API。MyBatis-Plus 作为 MyBatis 的增强工具在简化开发、提升效率方面表现出色但将其与 JDK 17 和 Spring Boot 3 进行整合时会遇到一些因版本跨度带来的新问题例如依赖冲突、配置方式变化、新特性适配等。本文将围绕如何在一个全新的 Spring Boot 3 项目中基于 JDK 17 环境稳定、高效地集成 MyBatis-Plus并解决单页查询限制、字段加解密、枚举映射等实际开发中常见的高级需求提供一个从环境搭建到生产级实践的全流程指南。1. 环境准备与项目初始化在开始编码之前确保开发环境与项目基础架构正确是后续一切工作的前提。JDK 17 和 Spring Boot 3 对最低环境版本有明确要求MyBatis-Plus 也需要选择对应的兼容版本。1.1 JDK 17 安装与配置JDK 17 是一个长期支持LTS版本带来了诸如密封类、模式匹配、新的垃圾收集器等特性。对于 Windows 系统安装后通常需要手动配置环境变量而 macOS 或 Linux 则可能有更简便的包管理工具安装方式。下载与安装从 Oracle 官网或 Adoptium 等开源发行版网站下载对应操作系统的 JDK 17 安装包。Windows 系统运行安装程序建议安装路径不要包含中文或空格。环境变量配置Windows新建系统变量JAVA_HOME值为 JDK 的安装路径例如C:\Program Files\Java\jdk-17。编辑系统变量Path添加%JAVA_HOME%\bin。验证安装打开命令行执行java -version应输出类似openjdk version 17.0.10的信息。注意如果你的机器上存在多个 JDK 版本需要在 IDE如 IntelliJ IDEA中明确指定项目的 SDK 为 JDK 17。在 IDEA 中可以通过File - Project Structure - Project - SDK进行设置。1.2 创建 Spring Boot 3 项目使用 Spring Initializr 是创建项目最快捷的方式。确保选择 Spring Boot 3.x 版本。通过 IDEA 创建新建项目选择Spring Initializr。选择 JDK 17 作为 Project SDK。在Dependencies中至少添加Spring Web和MySQL Driver或其他数据库驱动。MyBatis-Plus 的依赖我们稍后手动添加以确保版本可控。通过网站生成访问 start.spring.io 选择Project: MavenLanguage: JavaSpring Boot: 3.x.xDependencies: Spring Web, MySQL Driver 下载生成的项目并导入 IDE。1.3 引入 MyBatis-Plus 依赖这是关键一步版本选择不当会导致启动失败。Spring Boot 3 基于 Jakarta EE 9其包名从javax.*变为了jakarta.*因此需要 MyBatis-Plus 3.5.0 及以上版本才能兼容。在项目的pom.xml文件中添加以下依赖dependencies !-- Spring Boot 基础依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- MyBatis-Plus 核心依赖 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version !-- 使用与Spring Boot 3兼容的版本 -- /dependency !-- 代码生成器按需 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-generator/artifactId version3.5.5/version scopetest/scope /dependency dependency groupIdorg.apache.velocity/groupId artifactIdvelocity-engine-core/artifactId version2.3/version scopetest/scope /dependency !-- Lombok 简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies完成依赖添加后建议执行mvn clean compile检查是否有依赖冲突。常见的冲突可能发生在mybatis-spring或javax.persistence等传递依赖上Maven 的依赖调解机制通常能解决若不能则需要手动排除。2. 基础配置与数据层搭建环境就绪后需要配置数据库连接和 MyBatis-Plus 的基本行为并创建对应的数据实体、Mapper 接口。2.1 数据库与 MyBatis-Plus 配置在application.yml或application.properties中配置数据源和 MyBatis-Plus。YAML 格式示例如下spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/your_database?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: your_username password: your_password mybatis-plus: configuration: # 控制台打印完整带参数SQL log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启下划线转驼峰映射默认true通常保持开启 map-underscore-to-camel-case: true global-config: db-config: # 全局主键类型例如AUTO-数据库自增 INPUT-手动输入 id-type: AUTO # 逻辑删除字段名若使用逻辑删除 logic-delete-field: deleted # 逻辑已删除值 logic-delete-value: 1 # 逻辑未删除值 logic-not-delete-value: 0 # 指定Mapper XML文件位置如果XML文件在resources下 mapper-locations: classpath*:/mapper/**/*.xmllog-impl配置为StdOutImpl对于开发阶段调试 SQL 非常有用但在生产环境应关闭或切换为文件日志。2.2 实体类与 Mapper使用 MyBatis-Plus 可以极大简化 DAO 层代码。首先定义一个实体类并使用注解进行映射。import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; Data TableName(sys_user) // 指定表名若类名与表名遵循驼峰下划线转换规则可省略 public class User { /** * 主键 */ TableId(type IdType.AUTO) // 主键策略数据库自增 private Long id; /** * 用户名 */ private String username; /** * 密码实际项目中应加密存储 */ private String password; /** * 邮箱 */ private String email; /** * 用户状态枚举例如0-禁用1-正常 */ private Integer status; /** * 创建时间 */ TableField(fill FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; /** * 更新时间 */ TableField(fill FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; /** * 逻辑删除标记配合全局配置 */ TableLogic private Integer deleted; }接着创建对应的 Mapper 接口。只需继承BaseMapper并指定泛型即可获得丰富的 CRUD 方法。import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; Mapper // 或在启动类上加 MapperScan 批量扫描 public interface UserMapper extends BaseMapperUser { // 可以在此定义自定义的复杂SQL方法 // 例如ListUser selectUsersByCondition(Param(condition) UserQueryCondition condition); }为了让TableField(fill ...)注解生效需要实现一个元对象处理器MetaObjectHandler来定义填充规则。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()); } }至此一个基于 MyBatis-Plus 的基础数据访问层就搭建完成了。你可以通过注入UserMapper并调用其selectList、insert等方法进行测试。3. 高级特性与实战问题解决基础 CRUD 满足大部分需求但在实际项目中我们总会遇到一些需要特殊处理的场景。3.1 突破单页 500 条限制MyBatis-Plus 的分页插件默认单页最大记录数为 500这是为了防止内存溢出而设置的安全限制。但在数据导出等特定场景下可能需要查询更多数据。错误现象当使用Page对象设置size大于 500 时实际查询结果仍被限制在 500 条。解决方案自定义分页插件调整最大单页限制。你需要显式配置分页插件PaginationInnerInterceptor。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 paginationInnerInterceptor new PaginationInnerInterceptor(DbType.MYSQL); // 设置最大单页限制数量-1 表示不受限制生产环境慎用 paginationInnerInterceptor.setMaxLimit(1000L); // 例如调整为1000 // 开启 count 查询优化 paginationInnerInterceptor.setOptimizeJoin(true); interceptor.addInnerInterceptor(paginationInnerInterceptor); // 可以继续添加其他插件如乐观锁插件 // interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }注意将MaxLimit设置为-1或一个很大的值在生产环境中是危险的可能导致单次查询消耗大量内存和数据库资源甚至引发 OOM。务必结合业务场景并考虑使用流式查询或分页查询多次处理大数据量。3.2 实现字段自动加解密对于手机号、身份证号等敏感信息我们希望在存入数据库时自动加密查询时自动解密。这可以通过实现 MyBatis-Plus 的TypeHandler或使用拦截器来完成。这里展示更通用的TypeHandler方式。首先定义一个加解密工具类示例使用简单的 AES 算法生产环境应使用更安全的密钥管理方式import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class CryptoUtil { private static final String ALGORITHM AES; private static final String TRANSFORMATION AES/ECB/PKCS5Padding; private static final byte[] KEY Your-16-Byte-Key.getBytes(); // 密钥需16/24/32字节 public static String encrypt(String data) throws Exception { Cipher cipher Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(KEY, ALGORITHM)); byte[] encrypted cipher.doFinal(data.getBytes()); return Base64.getEncoder().encodeToString(encrypted); } public static String decrypt(String encryptedData) throws Exception { Cipher cipher Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(KEY, ALGORITHM)); byte[] decoded Base64.getDecoder().decode(encryptedData); byte[] decrypted cipher.doFinal(decoded); return new String(decrypted); } }然后为需要加解密的字段编写自定义的TypeHandlerimport org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class EncryptTypeHandler extends BaseTypeHandlerString { Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { try { // 写入数据库前加密 ps.setString(i, CryptoUtil.encrypt(parameter)); } catch (Exception e) { throw new SQLException(Failed to encrypt data, e); } } Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { String encrypted rs.getString(columnName); return decryptString(encrypted); } Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String encrypted rs.getString(columnIndex); return decryptString(encrypted); } Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String encrypted cs.getString(columnIndex); return decryptString(encrypted); } private String decryptString(String encrypted) { if (encrypted null) { return null; } try { // 从数据库读取后解密 return CryptoUtil.decrypt(encrypted); } catch (Exception e) { // 解密失败可能数据本身未加密直接返回原值根据业务逻辑调整 return encrypted; } } }最后在实体类的对应字段上使用TableField注解指定该TypeHandlerpublic class User { // ... 其他字段 TableField(typeHandler EncryptTypeHandler.class) private String phoneNumber; // 手机号字段 }这样当执行insert或update时phoneNumber会被自动加密后存储当执行select时查询结果中的phoneNumber会被自动解密。这种方式对业务代码透明是处理字段级加解密的优雅方案。3.3 枚举类型映射将数据库的整型或字符串字段与 Java 枚举类进行映射能极大提升代码的可读性和类型安全性。MyBatis-Plus 提供了EnumValue注解来简化这一过程。首先定义一个枚举类import com.baomidou.mybatisplus.annotation.EnumValue; import lombok.Getter; Getter public enum UserStatusEnum { DISABLED(0, 禁用), NORMAL(1, 正常), LOCKED(2, 锁定); EnumValue // 标记数据库存储的值 private final Integer code; private final String desc; UserStatusEnum(Integer code, String desc) { this.code code; this.desc desc; } }然后在实体类中使用该枚举类型public class User { // ... 其他字段 // MyBatis-Plus 会自动根据 EnumValue 进行映射 private UserStatusEnum status; }为了让 MyBatis-Plus 能够处理枚举需要在配置中开启默认的枚举处理器Spring Boot 3 下默认已包含但最好确认。在application.yml中mybatis-plus: configuration: # 确保默认枚举处理器被注册 default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler现在当你保存一个User对象时status字段的code值如1会被存入数据库。当你查询时MyBatis-Plus 会根据这个code值自动构造出对应的UserStatusEnum.NORMAL枚举实例。4. 生产环境考量与最佳实践将技术组合应用于生产环境除了功能实现还需要关注稳定性、可维护性和性能。4.1 配置与代码分离敏感信息数据库密码、加密密钥等绝不应硬编码在代码或配置文件中。应使用环境变量、配置中心如 Nacos、Apollo或云服务商提供的密钥管理服务。多环境配置使用application-{profile}.yml如application-dev.yml,application-prod.yml来管理不同环境的配置并通过spring.profiles.active激活。4.2 日志与监控SQL 日志生产环境不应使用StdOutImpl。应配置为Slf4jImpl并与项目的日志框架如 Logback集成将 SQL 日志输出到文件并设置合理的日志级别和滚动策略。慢 SQL 监控除了 MyBatis-Plus 的日志还应结合数据库自身的慢查询日志和 APM 工具如 SkyWalking, Arthas来监控和优化 SQL 性能。4.3 常见问题排查清单当集成出现问题时可以按照以下顺序进行排查问题现象可能原因检查点应用启动失败报ClassNotFoundException或NoClassDefFoundError依赖冲突或版本不兼容1. 检查pom.xml中 MyBatis-Plus 版本是否 3.5.0。2. 执行mvn dependency:tree查看是否有多个不同版本的mybatis-spring等 jar 包进行排除。启动报错javax相关类找不到Spring Boot 3 使用了jakarta包确保所有依赖特别是数据库连接池、Servlet API相关都兼容 Jakarta EE 9。MapperScan扫描不到 Mapper包路径错误或注解缺失1. 确认MapperScan(“com.yourpackage.mapper”)路径正确。2. 或在每个 Mapper 接口上添加Mapper注解。分页查询不生效分页插件未配置检查是否在配置类中正确添加了PaginationInnerInterceptor到MybatisPlusInterceptor。字段加解密未生效TypeHandler未注册或注解错误1. 确认实体类字段上的TableField(typeHandler ...)注解正确。2. 确认TypeHandler类已被 Spring 扫描到如在同一个包或子包下。枚举字段存入/读取为 null枚举处理器未配置或EnumValue注解错误1. 检查application.yml中的default-enum-type-handler配置。2. 确认枚举类中用于存储的字段通常是 code标记了EnumValue。4.4 性能与资源优化建议连接池使用高性能的连接池如 HikariCP它是 Spring Boot 的默认选择。在application.yml中合理配置maximum-pool-size、connection-timeout等参数。二级缓存对于读远多于写且数据更新不频繁的场景可以考虑启用 MyBatis 的二级缓存。但要注意分布式环境下的缓存一致性问题通常建议使用集中式缓存如 Redis。批量操作对于大批量数据插入或更新使用 MyBatis-Plus 的saveBatch或updateBatchById方法并配合在application.yml中设置mybatis-plus.global-config.db-config.logic-delete-field等全局配置这些方法在内部会进行优化例如 3.5.0 版本默认使用RewriteBatchedStatements优化。避免 N1 查询在涉及关联查询时使用 MyBatis-Plus 的TableField(exist false)配合自定义查询方法或使用TableName的resultMap属性而不是在循环中频繁查询数据库。将 MyBatis-Plus 与 JDK 17、Spring Boot 3 整合核心在于依赖版本的精准控制和对新特性如 Jakarta 命名空间的适配。完成基础整合后通过分页插件、类型处理器和枚举映射等功能可以优雅地解决单页限制、数据加解密和类型安全等进阶需求。在生产部署前务必完成配置外置、日志优化和性能调优并建立针对依赖冲突、映射失败等常见问题的快速排查能力。这套组合为构建现代化、类型安全且易于维护的 Java 后端数据访问层提供了坚实的技术基础。