Mybatis Common Mapper响应式编程:Spring WebFlux集成指南
【免费下载链接】Mapper Mybatis Common Mapper - Easy to use 项目地址: https://gitcode.com/gh_mirrors/ma/Mapper
1. 响应式编程与传统ORM的冲突
在微服务架构中,响应式编程(Reactive Programming)通过非阻塞I/O和异步数据流处理,显著提升了系统的吞吐量和资源利用率。Spring WebFlux作为Spring生态中的响应式Web框架,采用Reactor库实现了完全异步非阻塞的编程模型。然而,传统ORM框架如MyBatis及其增强工具Mybatis Common Mapper(以下简称Mapper)基于JDBC同步阻塞模型设计,直接集成会导致:
- 线程阻塞:JDBC操作会阻塞WebFlux的事件循环线程(Event Loop Thread)
- 资源浪费:无法充分利用响应式编程的背压(Backpressure)机制
- 性能瓶颈:同步数据库操作成为整个响应式链路的性能短板
本文将系统讲解如何通过异步适配层实现Mapper与Spring WebFlux的高效集成,构建真正端到端的响应式数据访问层。
2. 技术架构与适配原理
2.1 核心架构设计
2.2 关键技术点
-
线程模型隔离
- 使用Spring的
@Async或自定义线程池隔离JDBC阻塞操作 - 避免阻塞WebFlux的Netty事件循环线程(默认
reactor-http-nio-*)
- 使用Spring的
-
响应式类型转换
- 将Mapper返回的POJO/List转换为Reactor的
Mono(单个结果)/Flux(数据流) - 异常处理适配:将
SQLException转换为响应式错误信号
- 将Mapper返回的POJO/List转换为Reactor的
-
事务管理
- 使用
ReactiveTransactionManager替代传统事务管理器 - 通过
TransactionalOperator实现响应式事务控制
- 使用
3. 环境准备与依赖配置
3.1 核心依赖
<!-- Spring Boot 父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<dependencies>
<!-- WebFlux 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Mybatis Common Mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>4.2.3</version>
</dependency>
<!-- 数据库驱动 (以MySQL为例) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- Reactor 测试支持 -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.2 配置文件
# application.yml
spring:
r2dbc: # 注意:实际使用JDBC时无需此配置,此处仅为对比展示
url: r2dbc:mysql://localhost:3306/test
username: root
password: password
datasource:
url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
mapper:
mappers: tk.mybatis.mapper.common.Mapper
not-empty: false
identity: MYSQL
4. 异步适配层实现
4.1 线程池配置
@Configuration
public class AsyncConfig {
@Bean(name = "mapperAsyncPool")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(25); // 队列容量
executor.setThreadNamePrefix("Mapper-Async-"); // 线程名前缀
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
4.2 响应式Mapper代理
/**
* 响应式Mapper适配器,将同步Mapper方法转换为异步响应式方法
*/
public class ReactiveMapperAdapter<T> {
private final Mapper<T> mapper;
private final Executor executor;
public ReactiveMapperAdapter(Mapper<T> mapper, Executor executor) {
this.mapper = mapper;
this.executor = executor;
}
// 将单个结果查询转换为Mono
public Mono<T> selectByPrimaryKeyAsync(Object id) {
return Mono.fromCallable(() -> mapper.selectByPrimaryKey(id))
.subscribeOn(Schedulers.fromExecutor(executor));
}
// 将列表查询转换为Flux
public Flux<T> selectAllAsync() {
return Flux.fromIterable(() -> mapper.selectAll().iterator())
.subscribeOn(Schedulers.fromExecutor(executor));
}
// 插入操作返回影响行数
public Mono<Integer> insertAsync(T entity) {
return Mono.fromCallable(() -> mapper.insert(entity))
.subscribeOn(Schedulers.fromExecutor(executor));
}
// 更多方法...
}
4.3 服务层实现
@Service
public class UserReactiveService {
private final ReactiveMapperAdapter<User> reactiveUserMapper;
@Autowired
public UserReactiveService(UserMapper userMapper,
@Qualifier("mapperAsyncPool") Executor executor) {
this.reactiveUserMapper = new ReactiveMapperAdapter<>(userMapper, executor);
}
// 根据ID查询用户
public Mono<User> getUserById(Long id) {
return reactiveUserMapper.selectByPrimaryKeyAsync(id)
.switchIfEmpty(Mono.error(new NotFoundException("User not found with id: " + id)));
}
// 查询所有用户
public Flux<User> getAllUsers() {
return reactiveUserMapper.selectAllAsync()
.sort(Comparator.comparing(User::getCreateTime).reversed());
}
// 创建用户
@Transactional
public Mono<Integer> createUser(User user) {
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
return reactiveUserMapper.insertAsync(user);
}
}
5. WebFlux控制器实现
@RestController
@RequestMapping("/api/users")
public class UserReactiveController {
private final UserReactiveService userService;
@Autowired
public UserReactiveController(UserReactiveService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public Mono<ResponseEntity<User>> getUserById(@PathVariable Long id) {
return userService.getUserById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@GetMapping
public Flux<User> getAllUsers(
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "20") int size) {
return userService.getAllUsers()
.skip((long) page * size)
.take(size);
}
@PostMapping
public Mono<ResponseEntity<Void>> createUser(@RequestBody Mono<User> userMono) {
return userMono
.flatMap(userService::createUser)
.map(rows -> ResponseEntity
.created(URI.create("/api/users/" + user.getId()))
.build());
}
}
6. 高级特性实现
6.1 响应式事务管理
@Configuration
public class ReactiveTransactionConfig {
@Bean
public ReactiveTransactionManager transactionManager(DataSource dataSource) {
return new R2dbcTransactionManager(ConnectionFactory.from(dataSource));
}
@Bean
public TransactionalOperator transactionalOperator(ReactiveTransactionManager tm) {
return TransactionalOperator.create(tm);
}
}
// 在服务层使用
@Service
public class OrderReactiveService {
private final ReactiveMapperAdapter<Order> orderMapper;
private final ReactiveMapperAdapter<OrderItem> orderItemMapper;
private final TransactionalOperator transactionalOperator;
// 构造函数注入...
public Mono<Void> createOrder(Order order, List<OrderItem> items) {
return orderMapper.insertAsync(order)
.thenMany(Flux.fromIterable(items)
.flatMap(item -> {
item.setOrderId(order.getId());
return orderItemMapper.insertAsync(item);
}))
.then()
.as(transactionalOperator::transactional);
}
}
6.2 分页与排序
public Flux<User> getUsersByPage(int page, int size, String sortField, String direction) {
Example example = Example.builder(User.class)
.orderBy(sortField)
.orderByAsc(direction.equalsIgnoreCase("asc"))
.build();
RowBounds rowBounds = new RowBounds((page - 1) * size, size);
return Flux.fromCallable(() -> mapper.selectByExampleAndRowBounds(example, rowBounds))
.flatMapIterable(Function.identity())
.subscribeOn(Schedulers.fromExecutor(executor));
}
6.3 响应式Example查询
public Flux<User> getUsersByCondition(UserQuery query) {
return Mono.fromCallable(() -> {
Example example = Example.builder(User.class)
.where(Sqls.custom()
.andEqualTo("status", query.getStatus())
.andGreaterThan("createTime", query.getStartTime())
.andLessThan("createTime", query.getEndTime()))
.build();
return mapper.selectByExample(example);
})
.flatMapIterable(Function.identity())
.subscribeOn(Schedulers.fromExecutor(executor));
}
7. 性能优化策略
7.1 线程池调优
| 参数 | 建议值 | 说明 |
|---|---|---|
| 核心线程数 | CPU核心数 + 1 | 保证基本并发能力 |
| 最大线程数 | (CPU核心数 * 2) + 1 | 应对流量峰值 |
| 队列容量 | 100-500 | 根据业务吞吐量调整 |
| 空闲线程存活时间 | 60秒 | 避免频繁创建销毁线程 |
7.2 结果缓存
// 使用Caffeine缓存热门查询结果
@Service
public class CachedUserService {
private final UserReactiveService userService;
private final LoadingCache<Long, Mono<User>> userCache;
@Autowired
public CachedUserService(UserReactiveService userService) {
this.userService = userService;
this.userCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(id -> userService.getUserById((Long) id));
}
public Mono<User> getCachedUserById(Long id) {
return userCache.get(id);
}
}
7.3 批处理优化
// 批量插入优化
public Mono<Integer> batchInsertUsers(List<User> users) {
return Mono.fromCallable(() -> {
// 使用MyBatis的BatchExecutor
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserMapper batchMapper = session.getMapper(UserMapper.class);
int total = 0;
for (int i = 0; i < users.size(); i++) {
total += batchMapper.insert(users.get(i));
// 每50条提交一次
if (i % 50 == 0) {
session.flushStatements();
}
}
session.commit();
session.close();
return total;
}).subscribeOn(Schedulers.fromExecutor(executor));
}
8. 常见问题与解决方案
8.1 线程阻塞问题诊断
解决方案:
- 使用
BlockHound检测意外阻塞:BlockHound.install(builder -> builder.allowBlockingCallsInside("com.mysql.cj.jdbc.ConnectionImpl", "close") ); - 通过
Spring Boot Actuator监控线程池状态:management: endpoints: web: exposure: include: threadpool,health,metrics
8.2 事务传播行为
问题:响应式事务不支持传统的PROPAGATION_REQUIRES_NEW传播行为
解决方案:使用flatMap显式隔离事务边界:
public Mono<Void> complexOperation() {
return transactionalOperator.transactional(
Mono.fromCallable(() -> {/* 事务1操作 */})
).then(transactionalOperator.transactional(
Mono.fromCallable(() -> {/* 事务2操作 */})
));
}
8.3 异常处理策略
public Mono<User> safeGetUserById(Long id) {
return userService.getUserById(id)
.onErrorResume(SQLException.class, e -> {
log.error("Database error occurred", e);
return Mono.error(new ServiceUnavailableException("Database temporarily unavailable"));
})
.onErrorResume(NotFoundException.class, e -> {
log.warn("User not found: {}", id);
return Mono.just(new User()); // 返回默认用户或空对象
});
}
9. 测试策略与实践
9.1 单元测试
@SpringBootTest
class UserReactiveServiceTest {
@MockBean
private UserMapper userMapper;
@Autowired
private UserReactiveService userService;
@Test
void getUserById_ShouldReturnUser() {
// Arrange
User mockUser = new User(1L, "test", LocalDateTime.now());
when(userMapper.selectByPrimaryKey(1L)).thenReturn(mockUser);
// Act & Assert
StepVerifier.create(userService.getUserById(1L))
.expectNextMatches(user -> user.getId().equals(1L))
.verifyComplete();
}
@Test
void getUserById_WhenNotFound_ShouldReturnError() {
// Arrange
when(userMapper.selectByPrimaryKey(99L)).thenReturn(null);
// Act & Assert
StepVerifier.create(userService.getUserById(99L))
.expectError(NotFoundException.class)
.verify();
}
}
9.2 集成测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerIntegrationTest {
@LocalServerPort
private int port;
@Autowired
private WebTestClient webTestClient;
@Test
void getAllUsers_ShouldReturnSortedList() {
webTestClient.get().uri("/api/users")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBodyList(User.class)
.consumeWith(list -> {
List<User> users = list.getResponseBody();
// 验证排序
for (int i = 1; i < users.size(); i++) {
assertTrue(users.get(i-1).getCreateTime()
.isAfter(users.get(i).getCreateTime()));
}
});
}
}
10. 总结与展望
Mybatis Common Mapper与Spring WebFlux的集成虽然不是原生响应式方案,但通过异步适配层可以在保留Mapper强大功能的同时,获得响应式编程的大部分优势。这种方案特别适合:
- 已有大量Mapper代码的存量项目迁移
- 需要兼顾开发效率和运行性能的场景
- 逐步向全响应式架构过渡的中间阶段
未来展望:
- MyBatis 4.x可能提供官方响应式支持
- R2DBC实现成熟后可考虑全面迁移
- 数据库厂商原生异步驱动(如MySQL 8.0+的
mysql-async-driver)
通过本文介绍的方法,开发者可以构建既具备响应式系统高并发特性,又保留Mybatis Common Mapper便捷开发体验的应用架构。建议在实际项目中根据业务场景和性能需求,合理选择同步/异步混合架构,实现系统的最优性能。
扩展资源:
本文示例代码仓库:
git clone https://gitcode.com/gh_mirrors/ma/Mapper
cd Mapper/examples/webflux-integration
mvn spring-boot:run
下期预告:《Mybatis Common Mapper与Kotlin协程集成实践》
【免费下载链接】Mapper Mybatis Common Mapper - Easy to use 项目地址: https://gitcode.com/gh_mirrors/ma/Mapper
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



