R2DBC,Spring Data JDBC和WebFlux案例介紹
本文有關Spring響應式程式設計最新技術示例。
Spring WebFlux已經在Spring 5和Spring Boot 2中引入,Spring 5還引入了支援NoSQL資料庫如Cassandra,MongoDB或Couchbase反應式訪問的庫包。通過R2DBC實現訪問關係資料庫的反應性支援。
值得一提的是關於Spring Data JDBC的一些話。該專案已經發布,可在1.0版本下使用。它是更大的Spring Data框架的一部分。它提供了基於JDBC的儲存庫抽象。建立該庫的主要原因是允許使用Spring Data方式訪問關係資料庫(通過CrudRepository介面)不包括JPA庫到應用程式依賴項。當然,JPA仍然是用於Java應用程式的主要永續性API。Spring Data JDBC的目的是通過不實現延遲載入,快取,髒上下文,會話等流行模式,在概念上比JPA簡單得多。它還僅對基於註釋的對映提供非常有限的支援。最後,它提供了使用R2DBC訪問關係資料庫的反應式儲存庫的實現。雖然該模組仍在開發中(只有SNAPSHOT版本可用),但我們將嘗試在我們的演示應用程式中使用它。讓我們繼續實施。
包括依賴項
我們使用Kotlin來實現。首先,我們包含一些必需的Kotlin依賴項。
<dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-test-junit</artifactId> <version>${kotlin.version}</version> <scope>test</scope> </dependency>
我們還應該新增kotlin-maven-plugin對Spring的支援。
<plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> <configuration> <args> <arg>-Xjsr305=strict</arg> </args> <compilerPlugins> <plugin>spring</plugin> </compilerPlugins> </configuration> </plugin>
然後,我們可以繼續包括演示實現所需的框架。我們需要包含專用於使用R2DBC訪問資料庫的特殊SNAPSHOT版本的Spring Data JDBC。我們還必須新增一些R2DBC庫和Spring WebFlux。正如您在下面看到的,只有Spring WebFlux可用於穩定版本(作為Spring Boot RELEASE的一部分)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jdbc</artifactId>
<version>1.0.0.r2dbc-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-spi</artifactId>
<version>1.0.0.M5</version>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-postgresql</artifactId>
<version>1.0.0.M5</version>
</dependency>
為Spring Data專案設定依賴項管理也很重要。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>Lovelace-RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
庫
我們使用著名名的Spring Data風格的CRUD儲存庫實現。在這種情況下,我們需要建立擴充套件ReactiveCrudRepository介面的介面。
這是用於管理Employee物件的儲存庫的實現。
interface EmployeeRepository : ReactiveCrudRepository<Employee, Int< {
@Query("select id, name, salary, organization_id from employee e where e.organization_id = $1")
fun findByOrganizationId(organizationId: Int) : Flux<Employee>
}
這是儲存庫的另一個實現 - 這次是管理Organization物件。
interface OrganizationRepository : ReactiveCrudRepository<Organization, Int< {
}
實體和DTO
Kotlin通過將實體類宣告為資料類來提供建立實體類的便捷方法。使用Spring Data JDBC時,我們必須通過使用註釋欄位來為實體設定主鍵@Id。它假定金鑰由資料庫自動遞增。如果未使用自動增量列,則必須使用BeforeSaveEvent偵聽器,該偵聽器設定實體的ID。但是,我試圖為我的實體設定這樣的監聽器,但它只是不能與Spring Data JDBC的反應式版本一起使用。
這是Employee實體類的實現。值得一提的是Spring Data JDBC會自動將類欄位對映organizationId到資料庫列organization_id。
data class Employee(val name: String, val salary: Int, val organizationId: Int) {
@Id
var id: Int? = null
}
這是Organization實體類的實現。
data class Organization(var name: String) {
@Id
var id: Int? = null
}
R2DBC不支援任何列表或集合。因為我想Organization在一個API端點中返回帶有員工內部物件的列表,所以我建立了包含如下所示列表的DTO。
data class OrganizationDTO(var id: Int?, var name: String) {
var employees : MutableList = ArrayList()
constructor(employees: MutableList) : this(null, "") {
this.employees = employees
}
}
與建立的實體對應的SQL指令碼在下面可見。欄位型別serial將自動建立序列並將其附加到欄位id。
CREATE TABLE employee ( name character varying NOT NULL, salary integer NOT NULL, id serial PRIMARY KEY, organization_id integer ); CREATE TABLE organization ( name character varying NOT NULL, id serial PRIMARY KEY );
構建示例Web應用程式
為了演示目的,我們將構建兩個獨立的應用程式employee-service和organization-service。應用程式organization-service正在employee-service使用WebFlux進行通訊WebClient。它獲取分配給組織的員工列表,幷包括它們與Organization物件一起響應。示例應用程式原始碼可在GitHub上的儲存庫sample-spring-data-webflux下獲得:https://github.com/piomin/sample-spring-data-webflux。
好的,讓我們從宣告Spring Boot主類開始吧。我們需要通過使用註釋主類來啟用Spring Data JDBC儲存庫@EnableJdbcRepositories。
@SpringBootApplication
@EnableJdbcRepositories
class EmployeeApplication
fun main(args: Array<String>) {
runApplication<EmployeeApplication>(*args)
}
使用R2DBC和Postgres需要一些配置。可能由於Spring Data JDBC和R2DBC開發的早期階段,Postgres沒有Spring Boot自動配置。我們需要在@Configurationbean中宣告連線工廠,客戶端和儲存庫。
@Configuration
class EmployeeConfiguration {
@Bean
fun repository(factory: R2dbcRepositoryFactory): EmployeeRepository {
return factory.getRepository(EmployeeRepository::class.java)
}
@Bean
fun factory(client: DatabaseClient): R2dbcRepositoryFactory {
val context = RelationalMappingContext()
context.afterPropertiesSet()
return R2dbcRepositoryFactory(client, context)
}
@Bean
fun databaseClient(factory: ConnectionFactory): DatabaseClient {
return DatabaseClient.builder().connectionFactory(factory).build()
}
@Bean
fun connectionFactory(): PostgresqlConnectionFactory {
val config = PostgresqlConnectionConfiguration.builder() //
.host("192.168.99.100") //
.port(5432) //
.database("reactive") //
.username("reactive") //
.password("reactive123") //
.build()
return PostgresqlConnectionFactory(config)
}
}
最後,我們可以建立包含我們的反應API方法定義的REST控制器。使用Kotlin它不會佔用太多空間。以下控制器定義包含三種GET方法,這些方法允許按ID查詢所有員工,分配給給定組織的所有員工或單個員工。
@RestController
@RequestMapping("/employees")
class EmployeeController {
@Autowired
lateinit var repository : EmployeeRepository
@GetMapping
fun findAll() : Flux<Employee> = repository.findAll()
@GetMapping("/{id}")
fun findById(@PathVariable id : Int) : Mono<Employee> = repository.findById(id)
@GetMapping("/organization/{organizationId}")
fun findByorganizationId(@PathVariable organizationId : Int) : Flux<Employee> = repository.findByOrganizationId(organizationId)
@PostMapping
fun add(@RequestBody employee: Employee) : Mono<Employee> = repository.save(employee)
}
服務間通訊
因為OrganizationController實現有點複雜。因為organization-service正在與之通訊employee-service,我們首先需要宣告反應式WebFlux WebClient構建器。
@Bean
fun clientBuilder() : WebClient.Builder {
return WebClient.builder()
}
然後,類似於儲存庫bean,構建器被注入控制器。它用於內部findByIdWithEmployees方法呼叫方法GET /employees/organization/{organizationId}通過暴露employee-service。正如您在下面的程式碼片段中看到的那樣,它提供了響應式API和Flux包含已找到員工列表的返回物件。OrganizationDTO使用zipWithReactor方法將此列表注入物件。
@RestController
@RequestMapping("/organizations")
class OrganizationController {
@Autowired
lateinit var repository : OrganizationRepository
@Autowired
lateinit var clientBuilder : WebClient.Builder
@GetMapping
fun findAll() : Flux<Organization> = repository.findAll()
@GetMapping("/{id}")
fun findById(@PathVariable id : Int) : Mono<Organization> = repository.findById(id)
@GetMapping("/{id}/withEmployees")
fun findByIdWithEmployees(@PathVariable id : Int) : Mono<OrganizationDTO> {
val employees : Flux<Employee> = clientBuilder.build().get().uri("http://localhost:8090/employees/organization/$id")
.retrieve().bodyToFlux(Employee::class.java)
val org : Mono = repository.findById(id)
return org.zipWith(employees.collectList())
.map { tuple -> OrganizationDTO(tuple.t1.id as Int, tuple.t1.name, tuple.t2) }
}
@PostMapping
fun add(@RequestBody employee: Organization) : Mono<Organization> = repository.save(employee)
}
這個怎麼執行?
在執行測試之前,我們需要啟動Postgres資料庫。這是用於執行Postgres容器的Docker命令。它正在建立具有密碼的使用者,並設定預設資料庫。
$ docker run -d --name postgres -p 5432:5432 -e POSTGRES_USER=reactive -e POSTGRES_PASSWORD=reactive123 -e POSTGRES_DB=reactive postgres
然後我們需要建立一些測試表,因此您必須執行放置在實現實體和DTO部分中的SQL指令碼。之後,您可以開始我們的測試應用程式。如果不覆蓋application.yml檔案中提供的預設設定,employee-service則在埠8090和organization-service埠8095上進行偵聽。下圖說明了我們的示例系統的體系結構。
彈簧資料-1
現在,讓我們使用應用程式公開的反應API新增一些測試資料。
$ curl -d '{"name":"Test1"}' -H "Content-Type: application/json" -X POST http://localhost:8095/organizations
$ curl -d '{"name":"Name1", "balance":5000, "organizationId":1}' -H "Content-Type: application/json" -X POST http://localhost:8090/employees
$ curl -d '{"name":"Name2", "balance":10000, "organizationId":1}' -H "Content-Type: application/json" -X POST http://localhost:8090/employees
最後,您可以呼叫GET organizations/{id}/withEmployees方法。
至此,響應式reactive API構成了,ofollow,noindex" target="_blank">github