Spring Boot 4.0 整合 MyBatis-Plus 完整教程
注:Spring Boot 4.0,本教程基于 Spring Boot 4.0 的预览版和新特性预期进行构建。实际发布时可能会有调整。
一、Spring Boot 4.0 新特性概述
1.1 主要新特性
- Java 21+ 支持:基于虚拟线程的响应式编程增强
- GraalVM 原生镜像优化:更完善的 AOT 编译支持
- 响应式编程增强:更好的响应式 MyBatis 支持
- 模块化改进:更好的 Java Module 支持
- AI/机器学习集成:内置 AI 功能支持
1.2 构建工具要求
- JDK 21+
- Maven 3.9+ 或 Gradle 8.5+
- GraalVM 23.0+(可选,用于原生编译)
二、项目初始化
2.1 使用 Spring Initializr
curl https://start.spring.io/starter.zip \
-d type=maven-project \
-d language=java \
-d bootVersion=4.0.0-M1 \
-d baseDir=spring-boot4-mybatisplus \
-d groupId=com.example \
-d artifactId=demo \
-d name=demo \
-d description=Demo+project+for+Spring+Boot+4.0+with+MyBatis-Plus \
-d packageName=com.example.demo \
-d packaging=jar \
-d javaVersion=21 \
-d dependencies=web,data-jpa \
-o demo.zip
2.2 手动创建 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0-M1</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot4-mybatisplus</artifactId>
<version>1.0.0</version>
<name>springboot4-mybatisplus</name>
<description>Spring Boot 4.0 with MyBatis-Plus</description>
<properties>
<java.version>21</java.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<graalvm.version>23.0.0</graalvm.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.asyncer</groupId>
<artifactId>r2dbc-mysql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-tiny:latest</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>${graalvm.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
三、配置 MyBatis-Plus
3.1 应用配置(application.yml)
spring:
application:
name: springboot4-mybatisplus
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
hikari:
thread-factory: org.springframework.boot.jdbc.VirtualThreadExecutor
r2dbc:
url: r2dbc:mysql://localhost:3306/mybatis_plus_demo
username: root
password: 123456
aot:
enabled: true
generate-code: true
native:
mode: native
debug: true
mybatis-plus:
configuration:
auto-mapping-behavior: full
map-underscore-to-camel-case: true
[]
3.2 MyBatis-Plus 配置类
package com.example.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.time.LocalDateTime;
@Configuration
@EnableTransactionManagement
@MapperScan("com.example.demo.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor( ());
interceptor;
}
MetaObjectHandler {
() {
{
.strictInsertFill(metaObject, , LocalDateTime.class, LocalDateTime.now());
.strictInsertFill(metaObject, , LocalDateTime.class, LocalDateTime.now());
.strictInsertFill(metaObject, , Integer.class, );
}
{
.strictUpdateFill(metaObject, , LocalDateTime.class, LocalDateTime.now());
}
};
}
}
四、实体类与 Mapper
4.1 基础实体类
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@Accessors(chain = true)
public class BaseEntity implements Serializable {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private String createBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
}
4.2 用户实体类
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_user")
public class User extends BaseEntity {
private String username;
private String password;
private String email;
private String phone;
private Integer status;
@TableField(exist = false)
private transient String virtualThreadInfo;
}
4.3 Mapper 接口
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.data.repository.query.Param;
import org.springframework.r2dbc.core.DatabaseClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM sys_user WHERE username = #{username}")
User selectByUsername(@Param("username") String username);
@Select("SELECT * FROM sys_user WHERE id = #{id}")
default Mono<User> selectReactiveById(Long id, DatabaseClient databaseClient) {
return databaseClient.sql("SELECT * FROM sys_user WHERE id = :id")
.bind("id", id)
.map((row, metadata) -> {
User user = new User();
user.setId(row.get("id", Long.class));
user.setUsername(row.get("username", String.class));
user.setEmail(row.get("email", String.class));
user.setStatus(row.get("status", Integer.class));
return user;
})
.one();
}
Flux<User> {
databaseClient.sql()
.bind(, status)
.map((row, metadata) -> {
();
user.setId(row.get(, Long.class));
user.setUsername(row.get(, String.class));
user.setEmail(row.get(, String.class));
user.setStatus(row.get(, Integer.class));
user;
})
.all();
}
}
五、Service 层实现
5.1 接口定义
package com.example.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.User;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.concurrent.CompletableFuture;
public interface UserService extends IService<User> {
CompletableFuture<User> findUserByUsernameVirtualThread(String username);
Mono<User> findUserByIdReactive(Long id);
Flux<User> findUsersByStatusReactive(Integer status);
User findUserWithStructuredConcurrency(Long id);
}
5.2 服务实现
package com.example.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
private final UserMapper userMapper;
private final DatabaseClient databaseClient;
@Override
public CompletableFuture<User> findUserByUsernameVirtualThread(String username) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
userMapper.selectOne( <User>().eq(User::getUsername, username));
}, Thread.ofVirtual().factory());
}
Mono<User> {
userMapper.selectReactiveById(id, databaseClient).doOnNext(user -> log.info(, user.getUsername()));
}
Flux<User> {
userMapper.selectReactiveByStatus(status, databaseClient).doOnNext(user -> log.info(, user.getUsername()));
}
User {
( .ShutdownOnFailure()) {
scope.fork(() -> userMapper.selectById(id));
scope.fork(() ->
+ id
);
scope.join();
scope.throwIfFailed();
userFuture.get();
(user != ) {
user.setVirtualThreadInfo(userDetailsFuture.get());
}
user;
} (InterruptedException e) {
Thread.currentThread().interrupt();
(, e);
} (Exception e) {
(, e);
}
}
{
( .ShutdownOnFailure()) {
List<StructuredTaskScope.Subtask<User>> tasks = users.stream()
.map(user -> scope.fork(() -> {
save(user);
user;
}))
.toList();
scope.join();
scope.throwIfFailed();
tasks.forEach(task -> log.info(, task.get().getUsername()));
} (InterruptedException e) {
Thread.currentThread().interrupt();
(, e);
} (Exception e) {
(, e);
}
}
}
六、Controller 层
6.1 传统 REST Controller
package com.example.demo.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@Slf4j
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping
public ResponseEntity<User> create(@RequestBody User user) {
boolean saved = userService.save(user);
return saved ? ResponseEntity.ok(user) : ResponseEntity.badRequest().build();
}
@GetMapping("/page")
public ResponseEntity<IPage<User>> page(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size) {
Page<User> page = <>(current, size);
IPage<User> result = userService.page(page);
ResponseEntity.ok(result);
}
ResponseEntity<User>
ExecutionException, InterruptedException {
CompletableFuture<User> future = userService.findUserByUsernameVirtualThread(username);
ResponseEntity.ok(future.get());
}
ResponseEntity<User> {
userService.findUserWithStructuredConcurrency(id);
ResponseEntity.ok(user);
}
ResponseEntity<Void> {
Thread.ofVirtual().start(() -> {
userService.saveUsersWithVirtualThreads(users);
});
ResponseEntity.accepted().build();
}
}
6.2 响应式 Controller
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
@Slf4j
@RestController
@RequestMapping("/api/v2/users")
@RequiredArgsConstructor
public class UserReactiveController {
private final UserService userService;
@GetMapping("/reactive/{id}")
public Mono<ResponseEntity<User>> findByIdReactive(@PathVariable Long id) {
return userService.findUserByIdReactive(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@GetMapping(value = "/reactive/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamByStatus(@RequestParam Integer status) {
return userService.findUsersByStatusReactive(status)
.delayElements(Duration.ofMillis(100))
.doOnNext(user -> log.info("Streaming user: {}", user.getUsername()));
}
Flux<String> {
Flux.interval(Duration.ofSeconds())
.map(sequence -> String.format(, sequence, java.time.LocalTime.now()));
}
}
七、响应式 Repository
package com.example.demo.repository;
import com.example.demo.entity.User;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Repository
public interface UserReactiveRepository extends R2dbcRepository<User, Long> {
@Query("SELECT * FROM sys_user WHERE username = :username")
Mono<User> findByUsername(String username);
@Query("SELECT * FROM sys_user WHERE status = :status")
Flux<User> findByStatus(Integer status);
@Query("SELECT * FROM sys_user ORDER BY create_time DESC LIMIT :limit OFFSET :offset")
Flux<User> findAllWithPagination(int limit, int offset);
}
八、全局异常处理
package com.example.demo.handler;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.support.WebExchangeBindException;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MybatisPlusException.class)
public ResponseEntity<Map<String, Object>> handleMybatisPlusException(MybatisPlusException e) {
log.error("MyBatis-Plus Exception: {}", e.getMessage(), e);
Map<String, Object> response = new HashMap<>();
response.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
response.put("message", "数据库操作失败");
response.put("detail", e.getMessage());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
@ExceptionHandler(InterruptedException.class)
public ResponseEntity<Map<String, Object>> handleInterruptedException(InterruptedException e) {
log.warn(, e.getMessage());
Map<String, Object> response = <>();
response.put(, HttpStatus.SERVICE_UNAVAILABLE.value());
response.put(, );
response.put(, System.currentTimeMillis());
Thread.currentThread().interrupt();
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
Mono<ResponseEntity<Map<String, Object>>> {
Mono.fromCallable(() -> {
Map<String, Object> response = <>();
response.put(, HttpStatus.BAD_REQUEST.value());
response.put(, );
Map<String, String> errors = <>();
e.getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
response.put(, errors);
response.put(, System.currentTimeMillis());
ResponseEntity.badRequest().body(response);
});
}
}
九、虚拟线程配置
package com.example.demo.config;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.support.TaskExecutorAdapter;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executors;
@Configuration
@EnableAsync
public class VirtualThreadConfig {
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor asyncTaskExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
@Bean
public java.util.concurrent.ExecutorService databaseExecutor() {
return Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("db-virtual-thread-", 0).factory());
}
}
十、AOT 和原生镜像支持
10.1 创建 AOT 处理类
package com.example.demo.aot;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
@Configuration
@ImportRuntimeHints(AotRuntimeHints.MybatisPlusRuntimeHints.class)
public class AotRuntimeHints {
static class MybatisPlusRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerType(com.baomidou.mybatisplus.core.mapper.BaseMapper.class, hint ->
hint.withMembers(org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_METHODS));
hints.resources().registerPattern("mapper/*.xml");
hints.resources().registerPattern("mybatis-config.xml");
hints.serialization().registerType(com.example.demo.entity.User.class);
hints.proxies().registerJdkProxy(org.apache.ibatis.binding.MapperProxy.class);
}
}
}
10.2 native-image.properties
Args = --enable-url-protocols=http,https \
-H:+ReportExceptionStackTraces \
-H:+ReportUnsupportedElementsAtRuntime \
--initialize-at-build-time=com.mysql.cj \
--initialize-at-run-time=io.netty.handler.codec.http.HttpObjectEncoder \
-H:IncludeResourceBundles=com.mysql.cj.LocalizedErrorMessages
十一、测试类
package com.example.demo;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat;
@Slf4j
@SpringBootTest
class SpringBoot4MybatisPlusApplicationTests {
@Autowired
private UserService userService;
@Test
void testVirtualThreadQuery() throws ExecutionException, InterruptedException, TimeoutException {
User user = new User().setUsername("test_virtual").setEmail("[email protected]").setStatus(1);
userService.save(user);
CompletableFuture<User> future = userService.findUserByUsernameVirtualThread("test_virtual");
future.get(, TimeUnit.SECONDS);
assertThat(result).isNotNull();
assertThat(result.getUsername()).isEqualTo();
log.info(, result.getId());
}
{
().setUsername().setEmail().setStatus();
userService.save(user);
Mono<User> userMono = userService.findUserByIdReactive(user.getId());
StepVerifier.create(userMono)
.assertNext(foundUser -> {
assertThat(foundUser).isNotNull();
assertThat(foundUser.getUsername()).isEqualTo();
})
.verifyComplete();
log.info();
}
{
().setUsername().setEmail().setStatus();
userService.save(user);
userService.findUserWithStructuredConcurrency(user.getId());
assertThat(result).isNotNull();
assertThat(result.getUsername()).isEqualTo();
assertThat(result.getVirtualThreadInfo()).contains();
log.info();
}
{
List<User> users = <>();
( ; i < ; i++) {
users.add( ().setUsername( + i).setEmail( + i + ).setStatus());
}
userService.saveUsersWithVirtualThreads(users);
List<User> savedUsers = userService.list( <User>().likeRight(User::getUsername, ));
assertThat(savedUsers).hasSize();
log.info(, savedUsers.size());
}
}
十二、性能监控
package com.example.demo.monitor;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class VirtualThreadMonitor {
private final MeterRegistry meterRegistry;
private final AtomicInteger activeVirtualThreads = new AtomicInteger(0);
@Around("@annotation(com.example.demo.annotation.VirtualThreadMonitor)")
public Object monitorVirtualThread(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
int currentThreads = activeVirtualThreads.incrementAndGet();
try {
stopWatch.start();
Object result joinPoint.proceed();
stopWatch.stop();
meterRegistry.timer().record(stopWatch.getTotalTimeNanos(), java.util.concurrent.TimeUnit.NANOSECONDS);
meterRegistry.gauge(, activeVirtualThreads);
log.info(,
joinPoint.getSignature().getName(), stopWatch.getTotalTimeMillis(), currentThreads);
result;
} {
activeVirtualThreads.decrementAndGet();
}
}
}
十三、启动类
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
@Slf4j
@SpringBootApplication
public class SpringBoot4MybatisPlusApplication {
public static void main(String[] args) {
System.setProperty("spring.aot.enabled", "true");
SpringApplication.run(SpringBoot4MybatisPlusApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
log.info("=========================================");
log.info("Spring Boot 4.0 with MyBatis-Plus Started");
log.info("Java Version: {}", System.getProperty("java.version"));
log.info("Virtual Threads Available: {}", Thread.ofVirtual() != null ? "Yes" : "No");
log.info("Available Processors: {}", Runtime.getRuntime().availableProcessors());
log.info("=========================================");
}
}
十四、部署和优化
14.1 Dockerfile(支持原生镜像)
# 构建阶段
FROM ghcr.io/graalvm/native-image:java21 AS builder
WORKDIR /app
COPY target/*.jar app.jar
RUN native-image -jar app.jar --no-fallback -H:Name=app-native
# 运行阶段
FROM alpine:latest
RUN apk add --no-cache libstdc++
WORKDIR /app
COPY --from=builder /app/app-native /app/app
EXPOSE 8080
ENTRYPOINT ["/app/app"]
14.2 Docker Compose
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- JAVA_OPTS=-XX:+UseZGC -Xmx512m
depends_on:
- mysql
deploy:
resources:
limits:
memory: 512M
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: mybatis_plus_demo
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
mysql_data:
十五、总结
15.1 关键特性使用
- 虚拟线程:通过
Thread.ofVirtual() 和虚拟线程池优化并发性能
- 响应式编程:结合 MyBatis-Plus 和 R2DBC 实现非阻塞数据访问
- 结构化并发:使用 Java 21 的结构化并发 API
- AOT 编译:支持 GraalVM 原生镜像,提升启动速度
- 模块化:更好的 Java Module 支持
15.2 性能优化建议
- 对于 I/O 密集型操作,使用虚拟线程
- 对于高并发场景,使用响应式编程
- 使用 GraalVM 原生镜像减少内存占用和启动时间
- 合理配置连接池和线程池参数
15.3 整合事项
- Spring Boot 4.0 目前是预览版,API 可能会有变化
- 虚拟线程对阻塞操作敏感,避免在虚拟线程中执行长时间 CPU 操作
- 原生镜像编译需要处理反射和动态代理的注册
- 生产环境建议充分测试响应式和虚拟线程的稳定性
本文展示了如何利用 Spring Boot 4.0 的新特性(虚拟线程、响应式编程、AOT 编译等)来整合 MyBatis-Plus,构建高性能的现代 Java 应用程序。