Spring Webflux与事务

Spring WebFlux是一个反应式编程框架,用于用 Java 构建非阻塞、异步 Web 应用程序。当我们处理数据库(如数据插入、获取所有数据和其他功能)时,事务扮演着重要的角色。在这里,Spring 通过其事务管理为事务提供了强大的支持。Spring的事务管理器接口提供了对反应式事务管理的支持,我们可以在任何数据库中使用反应式事务管理。我们可以使用@Transactional注解来定义事务。

事务是一系列一个或多个数据库操作。事务性在涉及多个数据库操作的复杂操作期间提供数据完整性和一致性。在Spring框架中,@Transactional用于指示一个方法或类应该用Transaction包装。下面我们为每个 API 端点提供了一个带有 @Transactional 的示例。

项目依赖Gradle :

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

数据库连接
在 Spring Boot 中,我们有一个用于动态配置的文件 application.properties。在这里,对于数据库连接,我们在该文件中配置连接细节。下面我们提供了该文件的配置细节,以供参考。其中,localhost 是主机名,27017 是 MongoDB 端口号,最后一个是正在运行的数据库名称。

spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=working

实体类
Student 类是用于处理数据库操作的 POJO 类之一。该类包含文档名称、字段名称、Getters 和 Setters 方法等信息。下面我们提供 POJO 类代码供参考。学生类包含三个字段,即 id、name 和 age。通过使用该 Java 类,我们可以执行与数据库相关的操作。

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/*
 * @Data 注解来自 Lombok 库,可生成
 * getters、setters、toString、equals 和 hashCode 方法。
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
/*
 * @Document 注解来自 Spring Data MongoDB,
 * 表明该类是 MongoDB 文档,并指定了存储该类实例的集合名称
 *。
 */

@Document(collection =
"studentdata")
public class Student {
   
/*
     * The @Id annotation marks the field as the primary identifier
     * for this document.
     */

    @Id
    private String id;
   
// The name of the student.
    private String name;
   
// The age of the student.
    private String age;
}

存储库类
这是扩展ReactiveMongoRepository 的接口之一,用于使用@Repository注解创建存储库。这个 ReactiveMongoRepository 采用两个输入作为参数。第一个参数是 POJO 类名,这里我们提供 Student,第二个参数是 POJO 类 ID 数据类型。在这个例子中,我们使用了Student POJO类,它的Id类型是String。通过使用这个接口,我们可以进行数据库相关的操作,比如CRUD操作。

/*
 * 该接口代表一个存储库,用于使用 Spring Data MongoDB 以反应方式管理学生实体
 *。
 */

package com.webflux.app;

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
import org.springframework.stereotype.Repository;

/*
 * The @Repository annotation indicates that this interface is a Spring
 * repository bean, allowing it to be automatically discovered and
 * instantiated as a bean.
 */

@Repository
/*
 * @EnableReactiveMongoRepositories 注解启用了反应式 MongoDB
 * 资源库,并指定了 Spring Data MongoDB
 * 将扫描资源库接口的基础包。
 */

@EnableReactiveMongoRepositories
public interface StudentRepo extends ReactiveMongoRepository<Student, String> {

}

配置类
在这个 Java 类中,我们定义了所有 API 端点。通过使用@Configuration注释,我们通过使用@Autowired从ServiceHandler访问API方法来自动连接ServiceHandler 。之后,我们创建了RouterFunction来定义 API 端点,所有 API 都是 POST 方法。

/*
 * This class configures the routing for handling incoming HTTP requests
 * related to student management services in a reactive way using Spring WebFlux.
 */

package com.webflux.app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

/*
 * @Configuration 注解表示该类包含应由 Spring 容器处理的
 * Bean 定义。
 */

@Configuration
public class ServiceRouter {
    
    @Autowired
    private ServiceHandler serviceHandler;
    
   
/*
     * 该方法配置 RouterFunction,以定义路由
     *,用于处理与学生服务相关的各种 HTTP 请求。
     */

    @Bean
    RouterFunction<ServerResponse> routerFunction(){
        return RouterFunctions.route(RequestPredicates.POST(
"api/student/add"),serviceHandler::addStudent)
                .andRoute(RequestPredicates.POST(
"api/student/delete"), serviceHandler::deleteStudentById)
                .andRoute(RequestPredicates.POST(
"api/student/update"), serviceHandler::updateStudentById)
                .andRoute(RequestPredicates.POST(
"api/student/getall"), serviceHandler::getAllStudents);
    }
}

服务处理程序类
在这里,我们又创建了一个 Java 类来处理 API 端点。通过使用@Service注释,我们在这个服务层中创建了它。我们创建了四个 API 端点:addStudent、updateStudentById、deleteStudentById,最后一个是getAllStudents。在这里,我们为每个 API 端点使用了@Transaction注释。我们可以在下面的代码中观察到这一点。之后,我们使用@Autowired注释自动连接 StudentRepo。该接口用于处理每个 API 端点中的数据库操作。

/*
 * 该类实现了处理与学生服务相关的 HTTP 请求的处理程序方法。
 */

package com.webflux.app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

/*
 * @Service 注解表明该类是 Spring 的服务组件。
 */

@Service
public class ServiceHandler {

    @Autowired
    private StudentRepo studentRepo;
    
   
/*
     *此方法可将一名新学生添加到数据库中。
     */

    @Transactional
    public Mono<ServerResponse> addStudent(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return ServerResponse.ok().body(studentRepo.save(data), Student.class);
        });
    }

   
/*
     * 该方法按 ID 从数据库中删除一个学生。
     */

    @Transactional
    public Mono<ServerResponse> deleteStudentById(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return ServerResponse.ok().body(studentRepo.deleteById(data.getId()), Student.class);
        }).switchIfEmpty(ServerResponse.ok().bodyValue(
"No Student Data Found"));
    }

   
/*
     *该方法按 ID 更新数据库中的学生信息。
     */

    @Transactional
    public Mono<ServerResponse> updateStudentById(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return studentRepo.findById(data.getId()).flatMap(change -> {
                change.setId(data.getId());
                change.setName(data.getName());
                change.setAge(data.getAge());
                return ServerResponse.ok().body(studentRepo.save(change), Student.class);
            }).switchIfEmpty(ServerResponse.ok().bodyValue(
"No Student Data Found"));
        });
    }

   
/*
     * 该方法可从数据库中检索所有学生的数据。
     */

    @Transactional(readOnly = true)
    public Mono<ServerResponse> getAllStudents(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return ServerResponse.ok().body(studentRepo.findAll(), Student.class);
        }).switchIfEmpty(ServerResponse.ok().bodyValue(
"No Student Data Found"));
    }
}

在上面的代码中,我们创建了四个API端点,用于使用@Transaction注释处理数据库相关操作。下面我们将详细了解每个 API 端点。

添加学生API

@Transactional 
    public Mono<ServerResponse> addStudent(ServerRequest request) { 
        return request.bodyToMono(Student.class).flatMap(data -> { 
            return ServerResponse.ok().body(studentRepo.save(data), Student.class); } 
        }); 
    }

在上面的 API 代码中,我们在addStudent()方法中使用了@Transactional,这里我们使用StudentRepo对象获取学生详细信息。然后我们使用save()方法保存学生详细信息。

删除学生API
在此 API 端点中使用@Transaction注释进行包装。该API以学生ID作为输入,如果ID存在则删除学生数据,否则它将显示一些错误消息,例如未找到学生数据。

@Transactional 
    public Mono<ServerResponse> deleteStudentById(ServerRequest request) { 
        return request.bodyToMono(Student.class).flatMap(data -> { 
            return ServerResponse.ok().body(studentRepo.deleteById(data.getId()), Student .class); 
        }).switchIfEmpty(ServerResponse.ok().bodyValue("未找到学生数据")); 
    }

更新学生API
该 API 还包含@Transaction注释,这意味着事务管理器围绕该方法创建事务。对于更新学生详细信息,它将使用现有学生 ID 和新学生详细信息。一旦详细信息正确,数据就会成功更新到数据库中。如果未找到 ID,我们会收到类似“未找到学生数据”的错误消息。

@Transactional 
    public Mono<ServerResponse> updateStudentById(ServerRequest request) { 
        return request.bodyToMono(Student.class).flatMap(data -> { 
            return StudentRepo.findById(data.getId()).flatMap(change -> { 
                change.setId (data.getId()); 
                change.setName(data.getName()); 
                change.setAge(data.getAge());
                return ServerResponse.ok().body(studentRepo.save(change),Student.class) ; 
            }).switchIfEmpty(ServerResponse.ok().bodyValue("未找到学生数据")); 
        }); 
    }

获取所有数据API
在此 API 端点中,我们从数据库中获取所有数据。该方法还用 @Transaction 包装,@Transaction 围绕该方法创建事务。在这里,我们使用了 @Transactional(readOnly = true) 意味着只有我们才能以只读格式获取数据。

@Transactional(readOnly = true
    public Mono<ServerResponse> getAllStudents(ServerRequest request) { 
        return request.bodyToMono(Student.class). flatMap(data -> { 
            return ServerResponse.ok().body(studentRepo.findAll(), Student .class); 
        }).switchIfEmpty(ServerResponse.ok().bodyValue("未找到学生数据")); 
    }

结论
在Spring中,我们有事务管理器来处理事务。我们可以在 Spring 框架中借助@Transaction注解来定义事务。大多数事务处理与数据库相关的操作,如数据获取、数据插入、数据更新和其他功能。在此示例中,我们创建了四个 API 端点来处理数据库操作(例如 CRUD 操作)。大多数情况下,此事务用于复杂的数据库相关查询。