Spring Boot 4.0 + MyBatis-Plus 实战响应式编程的能力实战

Spring Boot 4.0 + MyBatis-Plus 实战响应式编程的能力实战

Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!

目前 MyBatis-Plus 官方还不完全支持响应式编程,但我们可以结合 R2DBC 和 MyBatis-Plus 的部分特性来实现。这里提供两种方案:

方案一:使用 MyBatis-Plus 增强 R2DBC (推荐)

Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!

1. 项目依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
    <!-- 注意:只使用其工具类,不启用SQL执行 -->
</dependency>

<!-- 数据库驱动 -->
<dependency>
    <groupId>io.asyncer</groupId>
    <artifactId>r2dbc-mysql</artifactId>
    <version>1.0.2</version>
</dependency>

<!-- 或 PostgreSQL -->
<!--
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>r2dbc-postgresql</artifactId>
    <scope>runtime</scope>
</dependency>
-->

2. 配置类

java 复制代码
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;
import org.springframework.data.r2dbc.config.EnableR2dbcAuditing;

@Configuration
@EnableR2dbcAuditing
public class R2dbcMybatisConfig {
    
    /**
     * 只使用 MyBatis-Plus 的分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

3. 实体类 (使用 MyBatis-Plus 注解)

Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!

java 复制代码
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.relational.core.mapping.Table;

import java.time.LocalDateTime;

@Data
@Table("users")
public class User {
    
    @TableId(type = IdType.AUTO)
    private Long id;
    
    @TableField("username")
    private String username;
    
    @TableField("email")
    private String email;
    
    @TableField("password")
    private String password;
    
    @TableField("age")
    private Integer age;
    
    @TableLogic
    @TableField("deleted")
    private Integer deleted = 0;
    
    @TableField(value = "version", fill = FieldFill.INSERT)
    @Version
    private Integer version = 1;
    
    @CreatedDate
    @TableField("create_time")
    private LocalDateTime createTime;
    
    @LastModifiedDate
    @TableField("update_time")
    private LocalDateTime updateTime;
    
    // 响应式编程友好的构造方法
    public static Mono<User> of(String username, String email) {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        return Mono.just(user);
    }
}

4. Repository 接口 (R2DBC)

java 复制代码
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 UserR2dbcRepository extends R2dbcRepository<User, Long> {
    
    Mono<User> findByUsername(String username);
    
    Mono<User> findByEmail(String email);
    
    Flux<User> findByAgeGreaterThan(Integer age);
    
    @Query("SELECT * FROM users WHERE username LIKE :keyword OR email LIKE :keyword")
    Flux<User> searchUsers(String keyword);
    
    @Query("UPDATE users SET age = :age WHERE id = :id")
    Mono<Integer> updateAgeById(Long id, Integer age);
}

5. Service 层 (结合 MyBatis-Plus 工具)

java 复制代码
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.time.Duration;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class ReactiveUserService {
    
    private final UserR2dbcRepository userRepository;
    private final DatabaseClient databaseClient;
    
    public Mono<User> createUser(User user) {
        return userRepository.save(user);
    }
    
    public Mono<User> getUserById(Long id) {
        return userRepository.findById(id)
            .switchIfEmpty(Mono.error(new RuntimeException("User not found")));
    }
    
    public Flux<User> getAllUsers() {
        return userRepository.findAll()
            .delayElements(Duration.ofMillis(100)) // 模拟流处理
            .subscribeOn(Schedulers.boundedElastic());
    }
    
    public Mono<User> updateUser(Long id, User user) {
        return userRepository.findById(id)
            .flatMap(existing -> {
                existing.setUsername(user.getUsername());
                existing.setEmail(user.getEmail());
                existing.setAge(user.getAge());
                return userRepository.save(existing);
            });
    }
    
    @Transactional
    public Mono<Void> deleteUser(Long id) {
        return userRepository.deleteById(id)
            .then(Mono.fromRunnable(() -> 
                System.out.println("User deleted: " + id)));
    }
    
    /**
     * 使用 MyBatis-Plus 的 QueryWrapper 构建查询条件
     * 然后转换为 R2DBC 查询
     */
    public Flux<User> queryUsers(Map<String, Object> params) {
        // 使用 MyBatis-Plus 的 QueryWrapper 构建条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        if (params.containsKey("username")) {
            queryWrapper.like("username", params.get("username"));
        }
        if (params.containsKey("email")) {
            queryWrapper.like("email", params.get("email"));
        }
        if (params.containsKey("minAge")) {
            queryWrapper.ge("age", params.get("minAge"));
        }
        if (params.containsKey("maxAge")) {
            queryWrapper.le("age", params.get("maxAge"));
        }
        
        queryWrapper.orderByDesc("create_time");
        
        // 将 QueryWrapper 转换为 SQL
        String sql = buildQueryWrapperSql(queryWrapper);
        
        // 执行响应式查询
        return databaseClient.sql(sql)
            .fetch()
            .all()
            .map(row -> {
                User user = new User();
                user.setId((Long) row.get("id"));
                user.setUsername((String) row.get("username"));
                user.setEmail((String) row.get("email"));
                user.setAge((Integer) row.get("age"));
                return user;
            });
    }
    
    /**
     * 响应式分页查询
     */
    public Mono<Page<User>> getUsersPage(Pageable pageable) {
        // 使用 MyBatis-Plus 的 Page 对象
        Page<User> mybatisPage = new Page<>(
            pageable.getPageNumber(), 
            pageable.getPageSize()
        );
        
        // 计算总数
        Mono<Long> countMono = databaseClient.sql("SELECT COUNT(*) FROM users")
            .map(row -> row.get(0, Long.class))
            .one();
        
        // 查询数据
        Flux<User> usersFlux = databaseClient.sql(
                "SELECT * FROM users ORDER BY create_time DESC LIMIT :limit OFFSET :offset")
            .bind("limit", pageable.getPageSize())
            .bind("offset", pageable.getOffset())
            .fetch()
            .all()
            .map(this::mapRowToUser);
        
        return Mono.zip(countMono, usersFlux.collectList())
            .map(tuple -> {
                mybatisPage.setTotal(tuple.getT1());
                mybatisPage.setRecords(tuple.getT2());
                return mybatisPage;
            });
    }
    
    private String buildQueryWrapperSql(QueryWrapper<User> queryWrapper) {
        // 简化示例,实际需要更复杂的转换
        return "SELECT * FROM users WHERE " + queryWrapper.getTargetSql();
    }
    
    private User mapRowToUser(Map<String, Object> row) {
        User user = new User();
        user.setId((Long) row.get("id"));
        user.setUsername((String) row.get("username"));
        user.setEmail((String) row.get("email"));
        user.setAge((Integer) row.get("age"));
        return user;
    }
    
    /**
     * 批量保存
     */
    public Flux<User> saveAll(Flux<User> users) {
        return userRepository.saveAll(users)
            .onErrorContinue((error, user) -> 
                System.err.println("Error saving user: " + error.getMessage()));
    }
    
    /**
     * 流式查询
     */
    public Flux<User> streamUsers() {
        return databaseClient.sql("SELECT * FROM users")
            .fetch()
            .all()
            .delayElements(Duration.ofMillis(50)) // 控制流速度
            .map(this::mapRowToUser);
    }
}

6. Controller 层

Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!

java 复制代码
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
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 javax.validation.Valid;
import java.time.Duration;
import java.util.Map;

@RestController
@RequestMapping("/api/reactive/users")
@RequiredArgsConstructor
public class ReactiveUserController {
    
    private final ReactiveUserService userService;
    
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<ResponseEntity<User>> create(@Valid @RequestBody User user) {
        return userService.createUser(user)
            .map(saved -> ResponseEntity.status(HttpStatus.CREATED).body(saved))
            .onErrorResume(e -> 
                Mono.just(ResponseEntity.badRequest().build()));
    }
    
    @GetMapping("/{id}")
    public Mono<ResponseEntity<User>> getById(@PathVariable Long id) {
        return userService.getUserById(id)
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @GetMapping
    public Flux<User> getAll() {
        return userService.getAllUsers();
    }
    
    @GetMapping("/search")
    public Flux<User> search(@RequestParam Map<String, Object> params) {
        return userService.queryUsers(params);
    }
    
    @GetMapping("/page")
    public Mono<Page<User>> getPage(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        PageRequest pageRequest = PageRequest.of(page, size);
        return userService.getUsersPage(pageRequest);
    }
    
    @PutMapping("/{id}")
    public Mono<ResponseEntity<User>> update(
            @PathVariable Long id,
            @Valid @RequestBody User user) {
        return userService.updateUser(id, user)
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> delete(@PathVariable Long id) {
        return userService.deleteUser(id)
            .then(Mono.just(ResponseEntity.noContent().<Void>build()))
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    /**
     * Server-Sent Events (SSE) 流式接口
     */
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> stream() {
        return userService.streamUsers();
    }
    
    /**
     * WebFlux WebSocket 支持
     */
    @MessageMapping("users.chat")
    public Flux<UserMessage> userChat(Flux<UserMessage> messages) {
        return messages
            .doOnNext(message -> 
                System.out.println("Received: " + message.getContent()))
            .map(message -> new UserMessage("Server: " + message.getContent()))
            .delayElements(Duration.ofSeconds(1));
    }
    
    /**
     * 批量操作
     */
    @PostMapping("/batch")
    public Flux<User> batchCreate(@RequestBody Flux<User> users) {
        return userService.saveAll(users);
    }
}

7. 自定义响应式 Repository

Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!

java 复制代码
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Repository
public interface CustomReactiveRepository {
    
    /**
     * 使用 MyBatis-Plus 的 Lambda 查询
     */
    Flux<User> findUsersByCondition(LambdaQueryWrapper<User> wrapper);
    
    /**
     * 响应式分页查询
     */
    Mono<Page<User>> findPage(Page<User> page, LambdaQueryWrapper<User> wrapper);
}

8. 响应式事务配置

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.reactive.TransactionalOperator;

@Configuration
public class ReactiveTransactionConfig {
    
    @Bean
    public TransactionalOperator transactionalOperator(
            ReactiveTransactionManager transactionManager) {
        return TransactionalOperator.create(transactionManager);
    }
}

方案二:使用 MyBatis-Plus 响应式扩展 (第三方)

有一些第三方项目正在尝试为 MyBatis-Plus 添加响应式支持:
Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!

1. 添加依赖

xml 复制代码
<!-- 第三方响应式扩展 -->
<dependency>
    <groupId>com.github.yulichang</groupId>
    <artifactId>mybatis-plus-join</artifactId>
    <version>1.4.6</version>
</dependency>

2. 自定义响应式 Mapper

java 复制代码
import org.apache.ibatis.annotations.SelectProvider;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface ReactiveBaseMapper<T> {
    
    Mono<Integer> insertReactive(T entity);
    
    Mono<Integer> updateByIdReactive(T entity);
    
    Mono<T> selectByIdReactive(Serializable id);
    
    Flux<T> selectListReactive(Wrapper<T> queryWrapper);
    
    Mono<Integer> deleteByIdReactive(Serializable id);
}

重要提示

  1. MyBatis-Plus 官方还不完全支持响应式,上述方案是结合 R2DBC 和 MyBatis-Plus 的工具类
  2. 真正的响应式编程需要使用 R2DBC 或 MongoDB Reactive
  3. 如果需要复杂 SQL 查询,可以使用 DatabaseClient 或 R2DBC Entity Callbacks
  4. 生产环境建议使用成熟的响应式数据库驱动

完整配置 application.yml

Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!

yaml 复制代码
spring:
  r2dbc:
    url: r2dbc:mysql://localhost:3306/reactive_db
    username: root
    password: password
    pool:
      initial-size: 5
      max-size: 20
      max-idle-time: 30m
  
  webflux:
    base-path: /api
    static-path-pattern: /static/**
  
  codec:
    max-in-memory-size: 10MB

logging:
  level:
    org.springframework.r2dbc: DEBUG
    io.r2dbc: DEBUG

这种架构结合了 MyBatis-Plus 的便利性和 R2DBC 的响应式能力,适合需要复杂查询的场景。

相关推荐
周杰伦_Jay2 小时前
【Golang 核心特点与语法】简洁高效+并发原生
开发语言·后端·golang
张较瘦_2 小时前
Springboot3 | JUnit 5 使用详解
spring boot·junit
by__csdn2 小时前
javascript 性能优化实战:垃圾回收优化
java·开发语言·javascript·jvm·vue.js·性能优化·typescript
李慕婉学姐2 小时前
Springboot遇见宠物生活馆系统设计与实现n6ea5118(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·宠物
IT_陈寒2 小时前
Java 21新特性实战:5个杀手级功能让你的代码效率提升50%
前端·人工智能·后端
BAStriver2 小时前
关于Flowable的使用小结
java·spring boot·spring·flowable
扶苏-su2 小时前
Java---泛型
java·开发语言·泛型
Dolphin_Home2 小时前
Java Stream 实战:订单商品ID过滤技巧(由浅入深)
java·开发语言·spring boot
毕设源码-钟学长2 小时前
【开题答辩全过程】以 高校健康申报系统为例,包含答辩的问题和答案
java·tomcat·echarts