springboot3+r2dbc——响应式编程实践

2023-11-06 20:11

本文主要是介绍springboot3+r2dbc——响应式编程实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

Spring boot3已经M1了,最近群佬们也开始蠢蠢欲动的开始整活Reactive+Spring Boot3,跟着大家的步伐,我也来整一篇工程入门,我们将用java17+Spring Boot3+r2dbc+Reactive栈来讲述,欢迎大家来讨论。(关于响应式,请大家异步到之前的文章里,有详细介绍。)

r2dbc

Reactor还有基于其之上的Spring WebFlux框架。包括vert.xrxjava等等reactive技术。我们实际上在应用层已经有很多优秀的响应式处理框架。

但是有一个问题就是所有的框架都需要获取底层的数据,而基本上关系型数据库的底层读写都还是同步的。

为了解决这个问题,出现了两个标准,一个是oracle提出的 ADBC (Asynchronous Database Access API),另一个就是Pivotal提出的R2DBC (Reactive Relational Database Connectivity)。

R2DBC是基于Reactive Streams标准来设计的。通过使用R2DBC,你可以使用reactive API来操作数据。

同时R2DBC只是一个开放的标准,而各个具体的数据库连接实现,需要实现这个标准。

今天我们以r2dbc-h2为例,讲解一下r2dbcSpring 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链接信息

r2dbc:url: r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE

配置类

用于配置默认链接,创建初始化数据

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;}@Beanpublic 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());}@Beanpublic 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 {@Idprivate 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;}@Overridepublic 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 {@Autowiredprivate 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——响应式编程实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/358809

相关文章

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4

Spring Boot 常用注解详解与使用最佳实践建议

《SpringBoot常用注解详解与使用最佳实践建议》:本文主要介绍SpringBoot常用注解详解与使用最佳实践建议,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录一、核心启动注解1. @SpringBootApplication2. @EnableAutoConfi

Python 异步编程 asyncio简介及基本用法

《Python异步编程asyncio简介及基本用法》asyncio是Python的一个库,用于编写并发代码,使用协程、任务和Futures来处理I/O密集型和高延迟操作,本文给大家介绍Python... 目录1、asyncio是什么IO密集型任务特征2、怎么用1、基本用法2、关键字 async1、async

Redis实现分布式锁全解析之从原理到实践过程

《Redis实现分布式锁全解析之从原理到实践过程》:本文主要介绍Redis实现分布式锁全解析之从原理到实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景介绍二、解决方案(一)使用 SETNX 命令(二)设置锁的过期时间(三)解决锁的误删问题(四)Re

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

Spring Boot 整合 SSE的高级实践(Server-Sent Events)

《SpringBoot整合SSE的高级实践(Server-SentEvents)》SSE(Server-SentEvents)是一种基于HTTP协议的单向通信机制,允许服务器向浏览器持续发送实... 目录1、简述2、Spring Boot 中的SSE实现2.1 添加依赖2.2 实现后端接口2.3 配置超时时

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

html5的响应式布局的方法示例详解

《html5的响应式布局的方法示例详解》:本文主要介绍了HTML5中使用媒体查询和Flexbox进行响应式布局的方法,简要介绍了CSSGrid布局的基础知识和如何实现自动换行的网格布局,详细内容请阅读本文,希望能对你有所帮助... 一 使用媒体查询响应式布局        使用的参数@media这是常用的

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java