springboot3+r2dbc響應式編程實踐
Spring boot3已經M1瞭,最近群佬們也開始蠢蠢欲動的開始整活Reactive+Spring Boot3,跟著大傢的步伐,我也來整一篇工程入門,我們將用java17+Spring Boot3+r2dbc+Reactive棧來講述,歡迎大傢來討論。(關於響應式,請大傢異步到之前的文章裡,有詳細介紹。)
r2dbc
Reactor還有基於其之上的Spring WebFlux框架。包括vert.x,rxjava等等reactive技術。我們實際上在應用層已經有很多優秀的響應式處理框架。
但是有一個問題就是所有的框架都需要獲取底層的數據,而基本上關系型數據庫的底層讀寫都還是同步的。
為瞭解決這個問題,出現瞭兩個標準,一個是oracle提出的 ADBC (Asynchronous Database Access API),另一個就是Pivotal提出的R2DBC (Reactive Relational Database Connectivity)。
R2DBC是基於Reactive Streams標準來設計的。通過使用R2DBC,你可以使用reactive API來操作數據。
同時R2DBC隻是一個開放的標準,而各個具體的數據庫連接實現,需要實現這個標準。
今天我們以r2dbc-h2為例,講解一下r2dbc在Spring webFlux中的使用。
工程依賴
以下是 pom.xml清單
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0-M1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>wang.datahub</groupId> <artifactId>springboot3demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot3demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-groovy-templates</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>io.r2dbc</groupId> <artifactId>r2dbc-h2</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <!-- <version>3.4.14</version>--> <!-- <scope>compile</scope>--> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </pluginRepository> </pluginRepositories> </project>
配置文件
這裡我們隻配置瞭r2dbc鏈接信息
配置類
用於配置默認鏈接,創建初始化數據
package wang.datahub.springboot3demo.config; import io.netty.util.internal.StringUtil; import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Flux; import static io.r2dbc.spi.ConnectionFactoryOptions.*; @Configuration @ConfigurationProperties(prefix = "r2dbc") public class DBConfig { private String url; private String user; private String password; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Bean public ConnectionFactory connectionFactory() { System.out.println("url ==> "+url); ConnectionFactoryOptions baseOptions = ConnectionFactoryOptions.parse(url); ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions); if (!StringUtil.isNullOrEmpty(user)) { ob = ob.option(USER, user); } if (!StringUtil.isNullOrEmpty(password)) { ob = ob.option(PASSWORD, password); } return ConnectionFactories.get(ob.build()); } @Bean public CommandLineRunner initDatabase(ConnectionFactory cf) { return (args) -> Flux.from(cf.create()) .flatMap(c -> Flux.from(c.createBatch() .add("drop table if exists Users") .add("create table Users(" + "id IDENTITY(1,1)," + "firstname varchar(80) not null," + "lastname varchar(80) not null)") .add("insert into Users(firstname,lastname)" + "values('Jacky','Li')") .add("insert into Users(firstname,lastname)" + "values('Doudou','Li')") .add("insert into Users(firstname,lastname)" + "values('Maimai','Li')") .execute()) .doFinally((st) -> c.close()) ) .log() .blockLast(); } }
bean
創建用戶bean
package wang.datahub.springboot3demo.bean; import org.springframework.data.annotation.Id; public class Users { @Id private Long id; private String firstname; private String lastname; public Users(){ } public Users(Long id, String firstname, String lastname) { this.id = id; this.firstname = firstname; this.lastname = lastname; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } @Override public String toString() { return "User{" + "id=" + id + ", firstname='" + firstname + '\'' + ", lastname='" + lastname + '\'' + '}'; } }
DAO
dao代碼清單如下,包含查詢列表、按id查詢,以及創建用戶等操作
package wang.datahub.springboot3demo.dao; import io.r2dbc.spi.Connection; import io.r2dbc.spi.ConnectionFactory; import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.data.relational.core.query.Query; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import wang.datahub.springboot3demo.bean.Users; import static org.springframework.data.r2dbc.query.Criteria.where; import static org.springframework.data.relational.core.query.Query.query; @Component public class UsersDao { private ConnectionFactory connectionFactory; private R2dbcEntityTemplate template; public UsersDao(ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; this.template = new R2dbcEntityTemplate(connectionFactory); } public Mono<Users> findById(long id) { return this.template.selectOne(query(where("id").is(id)),Users.class); // return Mono.from(connectionFactory.create()) // .flatMap(c -> Mono.from(c.createStatement("select id,firstname,lastname from Users where id = $1") // .bind("$1", id) // .execute()) // .doFinally((st) -> close(c))) // .map(result -> result.map((row, meta) -> // new Users(row.get("id", Long.class), // row.get("firstname", String.class), // row.get("lastname", String.class)))) // .flatMap( p -> Mono.from(p)); } public Flux<Users> findAll() { return this.template.select(Users.class).all(); // return Mono.from(connectionFactory.create()) // .flatMap((c) -> Mono.from(c.createStatement("select id,firstname,lastname from users") // .execute()) // .doFinally((st) -> close(c))) // .flatMapMany(result -> Flux.from(result.map((row, meta) -> { // Users acc = new Users(); // acc.setId(row.get("id", Long.class)); // acc.setFirstname(row.get("firstname", String.class)); // acc.setLastname(row.get("lastname", String.class)); // return acc; // }))); } public Mono<Users> createAccount(Users account) { return Mono.from(connectionFactory.create()) .flatMap(c -> Mono.from(c.beginTransaction()) .then(Mono.from(c.createStatement("insert into Users(firstname,lastname) values($1,$2)") .bind("$1", account.getFirstname()) .bind("$2", account.getLastname()) .returnGeneratedValues("id") .execute())) .map(result -> result.map((row, meta) -> new Users(row.get("id", Long.class), account.getFirstname(), account.getLastname()))) .flatMap(pub -> Mono.from(pub)) .delayUntil(r -> c.commitTransaction()) .doFinally((st) -> c.close())); } private <T> Mono<T> close(Connection connection) { return Mono.from(connection.close()) .then(Mono.empty()); } }
controller
controller代碼清單如下,包含瞭查詢列表、按id查詢,以及創建用戶等操作
package wang.datahub.springboot3demo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import wang.datahub.springboot3demo.bean.Users; import wang.datahub.springboot3demo.dao.UsersDao; @RestController public class UsersController { @Autowired private final UsersDao usersDao; public UsersController(UsersDao usersDao) { this.usersDao = usersDao; } @GetMapping("/users/{id}") public Mono<ResponseEntity<Users>> getUsers(@PathVariable("id") Long id) { return usersDao.findById(id) .map(acc -> new ResponseEntity<>(acc, HttpStatus.OK)) .switchIfEmpty(Mono.just(new ResponseEntity<>(null, HttpStatus.NOT_FOUND))); } @GetMapping("/users") public Flux<Users> getAllAccounts() { return usersDao.findAll(); } @PostMapping("/createUser") public Mono<ResponseEntity<Users>> createUser(@RequestBody Users user) { return usersDao.createAccount(user) .map(acc -> new ResponseEntity<>(acc, HttpStatus.CREATED)) .log(); } }
啟動類清單:
package wang.datahub.springboot3demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import wang.datahub.springboot3demo.config.DBConfig; @SpringBootApplication @EnableConfigurationProperties(DBConfig.class) public class WebFluxR2dbcApp { public static void main(String[] args) { SpringApplication.run(WebFluxR2dbcApp.class, args); } }
好瞭,致此我們整個 Demo 就實現完成瞭
參考鏈接:
https://zhuanlan.zhihu.com/p/299069835
到此這篇關於springboot3+r2dbc響應式編程實踐的文章就介紹到這瞭,更多相關springboot3 r2dbc響應式編程內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- SpringBoot深入分析webmvc和webflux的區別
- Springboot入門案例及部署項目的詳細過程
- 如何創建SpringBoot項目
- SpringBoot環境Druid數據源使用及特點
- Springboot基礎學習之初識SpringBoot