MyBatis-Plus 3.5.17 的一个关键信号是:它开始支持 GraalVM Native Image。对依赖 MyBatis-Plus 做单表 CRUD、XML 配置加载和动态 SQL 注入的 Java 项目来说,这意味着原生镜像部署不再只是框架层面的事情,数据访问增强工具也需要进入 AOT 约束下的运行模型。
为什么这个版本值得后端团队关注
MyBatis-Plus 的定位一直很明确:它不是替代 MyBatis,而是在 MyBatis 之上减少样板代码。常见的 BaseMapper、条件构造器、通用 Service、分页插件,都是为了让团队少写重复 CRUD,把精力放在业务规则上。
这类工具在普通 JVM 下运行得很自然:启动时读取 XML、扫描 Mapper、注入单表 SQL、拼装动态 SQL 脚本。问题在于,GraalVM Native Image 对运行时反射、动态代理、资源加载和类路径扫描都有更严格的要求。一个库如果没有考虑 Native Image,常见结果是:JVM 模式正常,打成原生镜像后 Mapper 找不到、XML 没被打包、反射访问失败。
3.5.17 支持 GraalVM Native Image,说明 MyBatis-Plus 正在补齐这条链路。它对云原生、Serverless、CLI 工具、低内存容器环境都更有意义:启动更快、镜像运行时内存更低,但构建约束也更硬。
Native Image 会放大哪些 MyBatis 使用习惯
MyBatis-Plus 在启动阶段会做不少工作:基于实体和 Mapper 注入单表 SQL 操作,加载 XML 配置,生成动态 SQL 脚本。这些能力正是它提升生产率的来源。
放到 Native Image 里,开发者需要多看几个边界:
- XML Mapper 是否作为资源进入原生镜像;
- 实体类、Mapper、插件相关类型是否涉及反射;
- 动态 SQL 是否依赖运行时无法分析的类加载行为;
- 数据库驱动是否支持或适配 GraalVM Native Image;
- 构建插件是否和当前 Spring Boot、MyBatis、MyBatis-Plus 版本匹配。
也就是说,MyBatis-Plus 3.5.17 的支持降低了库层面的障碍,但项目仍要把资源、驱动、配置一起纳入验证。Native Image 不是把 java -jar 换成一个二进制文件那么简单。
可以这样实践:准备一个最小 Spring Boot + MyBatis-Plus Native 构建
下面示例不是官方声明的唯一方式,而是一个可改造的实践骨架。你需要根据自己的 Spring Boot 版本、数据库驱动和 GraalVM 环境调整版本号。
pom.xml 可以从这样的结构开始:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mp-native-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.3.5</spring-boot.version>
<mybatis-plus.version>3.5.17</mybatis-plus.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.3</version>
</plugin>
</plugins>
</build>
</project>
一个最小实体和 Mapper:
package com.example.demo;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("users")
public class User {
@TableId
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.example.demo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
应用入口和一个简单接口:
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@SpringBootApplication
@MapperScan("com.example.demo")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
class UserController {
private final UserMapper userMapper;
UserController(UserMapper userMapper) {
this.userMapper = userMapper;
}
@GetMapping("/users")
List<User> users() {
return userMapper.selectList(null);
}
}
配套资源文件:
spring:
datasource:
url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password:
sql:
init:
mode: always
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(64) NOT NULL
);
INSERT INTO users(id, name) VALUES (1, 'Ada'), (2, 'Linus');
本地先跑 JVM 模式,再构建 Native Image:
mvn spring-boot:run
curl http://localhost:8080/users
mvn -Pnative native:compile
./target/mp-native-demo
curl http://localhost:8080/users
如果你使用 XML Mapper,务必确认 XML 被放进镜像资源。可以在 src/main/resources/mapper/UserMapper.xml 下放文件,并在配置中声明路径:
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
Native 构建失败时,不要只看最后一行错误。重点搜索 reflection、resource、proxy、ClassNotFoundException、NoSuchMethodException 这些关键词,通常能定位到缺少反射配置、资源没打包或驱动不兼容。
迁移时别只盯着“能不能编过”
引入 Native Image 支持后,最容易犯的错是把构建成功当成上线就绪。数据访问层更适合用一组小而硬的验证来兜底:
- 用真实数据库驱动跑一次集成测试,不只用 H2;
- 覆盖
BaseMapper的增删改查,以及你项目里的自定义 XML SQL; - 检查分页、乐观锁、逻辑删除、自动填充等 MyBatis-Plus 插件是否在 Native 镜像下行为一致;
- 记录 Native 构建时间和 CI 资源消耗,避免拖慢主干流水线;
- 对比 JVM 与 Native 的启动时间、RSS 内存、首个请求延迟,而不是只看理论收益。
MyBatis-Plus 3.5.17 对 GraalVM Native Image 的支持,是一个很实际的基础设施进展。它让熟悉 MyBatis-Plus 的团队有机会把同一套 CRUD 开发体验带到原生镜像部署里。但 Native Image 的收益来自完整链路:框架、ORM 增强、数据库驱动、资源配置、测试流水线都要一起过关。稳妥的做法是先挑一个边界清晰的服务试点,再把经验沉淀成项目模板和 CI 检查项。