Spring Webflux與事務

banq發表於2024-03-27

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 方法。
 */<font><i>
@Data
@AllArgsConstructor
@NoArgsConstructor
/*
 * @Document 註解來自 Spring Data MongoDB,
 * 表明該類是 MongoDB 文件,並指定了儲存該類例項的集合名稱
 *。
 */
<i>
@Document(collection =
"studentdata")
public class Student {
   
/*
     * The @Id annotation marks the field as the primary identifier
     * for this document.
     */
<i>
    @Id
    private String id;
   
// The name of the student.<i>
    private String name;
   
// The age of the student.<i>
    private String age;
}

儲存庫類
這是擴充套件ReactiveMongoRepository 的介面之一,用於使用@Repository註解建立儲存庫。這個 ReactiveMongoRepository 採用兩個輸入作為引數。第一個引數是 POJO 類名,這裡我們提供 Student,第二個引數是 POJO 類 ID 資料型別。在這個例子中,我們使用了Student POJO類,它的Id型別是String。透過使用這個介面,我們可以進行資料庫相關的操作,比如CRUD操作。

<font>/*
 * 該介面代表一個儲存庫,用於使用 Spring Data MongoDB 以反應方式管理學生實體
 *。
 */
<i>
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.
 */
<i>
@Repository
/*
 * @EnableReactiveMongoRepositories 註解啟用了反應式 MongoDB
 * 資源庫,並指定了 Spring Data MongoDB
 * 將掃描資源庫介面的基礎包。
 */
<i>
@EnableReactiveMongoRepositories
public interface StudentRepo extends ReactiveMongoRepository<Student, String> {

}

配置類
在這個 Java 類中,我們定義了所有 API 端點。透過使用@Configuration註釋,我們透過使用@Autowired從ServiceHandler訪問API方法來自動連線ServiceHandler 。之後,我們建立了RouterFunction來定義 API 端點,所有 API 都是 POST 方法。

<font>/*
 * This class configures the routing for handling incoming HTTP requests
 * related to student management services in a reactive way using Spring WebFlux.
 */
<i>
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 定義。
 */
<i>
@Configuration
public class ServiceRouter {
    
    @Autowired
    private ServiceHandler serviceHandler;
    
   
/*
     * 該方法配置 RouterFunction,以定義路由
     *,用於處理與學生服務相關的各種 HTTP 請求。
     */
<i>
    @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 端點中的資料庫操作。

<font>/*
 * 該類實現了處理與學生服務相關的 HTTP 請求的處理程式方法。
 */
<i>
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 的服務元件。
 */
<i>
@Service
public class ServiceHandler {

    @Autowired
    private StudentRepo studentRepo;
    
   
/*
     *此方法可將一名新學生新增到資料庫中。
     */
<i>
    @Transactional
    public Mono<ServerResponse> addStudent(ServerRequest request) {
        return request.bodyToMono(Student.class).flatMap(data -> {
            return ServerResponse.ok().body(studentRepo.save(data), Student.class);
        });
    }

   
/*
     * 該方法按 ID 從資料庫中刪除一個學生。
     */
<i>
    @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 更新資料庫中的學生資訊。
     */
<i>
    @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"));
        });
    }

   
/*
     * 該方法可從資料庫中檢索所有學生的資料。
     */
<i>
    @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(<font>"未找到學生資料")); 
    }

更新學生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(<font>"未找到學生資料")); 
        }); 
    }

獲取所有資料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(<font>"未找到學生資料")); 
    }

結論
在Spring中,我們有事務管理器來處理事務。我們可以在 Spring 框架中藉助@Transaction註解來定義事務。大多數事務處理與資料庫相關的操作,如資料獲取、資料插入、資料更新和其他功能。在此示例中,我們建立了四個 API 端點來處理資料庫操作(例如 CRUD 操作)。大多數情況下,此事務用於複雜的資料庫相關查詢。
 

相關文章