如果您处理大量流数据,反应性应用程序可以让您更好地扩展。它们是非阻塞的,并且往往更有效率,因为它们在等待事情发生时不会占用处理。
反应系统包含异步I / O. 异步I / O背后的概念很简单:通过回收资源来缓解低效的资源利用率,否则这些资源在等待I / O活动时会处于空闲状态。异步I / O反转了I / O处理的正常设计:客户端会收到新数据的通知而不是主动请求拉取数据; 这样可以让客户端在等待数据通知来之前做其他事情。
如果您要构建一个响应式应用程序,您将需要它一直Reactive到您的数据库。在Spring WebFlux中使用阻塞JDBC驱动程序,你会对其性能感到失望。使用像Cassandra,MongoDB,Couchbase和Redis这样的反应性NoSQL数据库 - 你则会对它的性能印象深刻。
在本教程中,您将学习如何使用Spring Boot,Spring WebFlux和Spring Data创建一个与NoSQL数据库后端(在本例中为MongoDB)对话的响应式Web服务。
什么是NoSQL以及为什么选择MongoDB?
NoSQL是任何非关系数据库的术语。在关系数据库(想想SQL,MySQL等等)中,数据存储在表格列中具有强类型和明确定义的关系的表中。关系数据库的紧密,明确的结构既是他们的优势,也是他们的弱点。这是一种权衡。NoSQL数据库打破了这个模型,并提供了其他模型,允许更大的灵活性和易于扩展。
缩放的微服务/集群模型为关系数据库带来了许多问题。它们不是为了在多台机器上运行并保持数据同步而构建的。而部分地使用NoSQL数据库可以来解决这个问题,通常,NoSQL在构建时考虑了聚合类和水平缩放。
NoSQL数据库的另一个潜在好处是它们的灵活性。像MongoDB这样的基于文档的NoSQL数据库可以在文档中存储任意数据。可以动态地将字段添加到存储的文档中,而不需要表迁移的开销。当然,这并没有解决版本控制的问题,它仍然取决于应用程序来处理不断变化的数据结构(并不总是微不足道),但至少你不是在与数据库作斗争(基于数据库编程)。
SQL /关系数据库已经经过验证,快速且超级可靠。在某些用例中,它们更便宜,更容易。例如,对于简单的网站或博客来说,MySQL就很胜任。但即使在企业环境中,有时您也需要关系数据库强制执行的结构。如果您有一个相当静态的数据模型,并且不需要扩展到Internet互联网规模,那么SQL可能是最佳选择。
我在本教程中使用的是MongoDB,因为从一开始就轻而易举。如果你使用Spring Data MongoDB,那就更容易了!
Reactive反应式编程
Reactive是另一个很大的行话。这种感觉就像人们喜欢在派对和会议上挥之不去,只是模糊地了解它实际意味着什么。就像“存在主义”或“恩赐”一样,让我们来定义它。
如果您查看 Spring WebFlux文档 ,它们可以非常好地概述反应意味着什么。
术语“反应性”是指围绕变化做出反应的编程模型 - 对I / O事件做出反应的网络组件,对鼠标事件做出反应的UI控制器等。从这个意义上说,非阻塞是Reactive响应式的,因为我们现在处于一种模式,即在操作完成或数据可用时对通知作出反应。
因此,反应/响应意味着:非阻塞,异步,并以流处理为中心。
构建Spring Boot资源服务器
从GitHub存储库克隆启动项目并检查启动分支:
git clone -b start https:<font><i>//github.com/oktadeveloper/okta-spring-boot-mongo-webflux-example.git</i></font><font> </font>
入门项目是一个简单的Spring Boot入门项目,其中已包含必要的依赖项build.gradle。
让我们快速浏览一下依赖项:
compile('org.springframework.boot:spring-boot-starter-webflux') compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') compileOnly('org.projectlombok:lombok') compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo')
第一个是Spring WebFlux,Spring MVC的反应版本。第二个引入Spring需要的响应式MongoDB依赖项。第三个是名为Lombok的项目,它使我们在Java代码中无需键入一堆构造函数,getter和setter(您可以在 他们的网页 上查看项目)。最后一个依赖项是嵌入式内存中的MongoDB数据库。这个数据库非常适合测试,这样的简单教程,并且不会真正持久化。
可以使用简单的Gradle命令运行应用程序:
./gradlew bootRun
为MongoDB定义模型类
模型类:
@Document @Data @AllArgsConstructor @NoArgsConstructor <b>public</b> <b>class</b> Kayak { <b>private</b> String name; <b>private</b> String owner; <b>private</b> Number value; <b>private</b> String makeModel; }
@Document注释是NoSQL的类似关系数据库的@Entity。它告诉Spring Boot定义了一个数据模型。在NoSQL世界中,这意味着创建文档而不是表条目。其他三个注释是自动生成getter,setter和构造函数的Lombok助手。
Kayak文档有五个属性:名称name,所有者owner,值value和类型makeModel。它们会自动映射到MongoDB的相应BSON类型。
什么是BSON类型?看一下 关于这个主题的MongoDB文档 。它们是用于在MongoDB文档中保留数据的二进制序列化类型。它们定义了可以存储在MongoDB数据库中的基元类型。
将ReactiveMongoRepository添加到Spring Boot App
使用@Document注释定义Kayak类告诉Spring Boot有关数据结构的信息,但实际上并没有给我们任何保存或加载数据库数据的方法。为此,您需要定义一个存储库。
这个代码非常简单:KayakRepository.java:
<b>import</b> org.springframework.data.mongodb.repository.ReactiveMongoRepository; <b>public</b> <b>interface</b> KayakRepository <b>extends</b> ReactiveMongoRepository<Kayak, Long> { }
这实际上为您提供了从数据库创建,更新,读取和删除文档所需的所有基本方法。要了解如何,特别是深入研究ReactiveMongoRepository班级和各种其他超类ReactiveCrudRepository。看看 该文档 的ReactiveCrudRepository看到实现的方法。
ReactiveCrudRepository实际上提供了一套基本而完整的CRUD方法。ReactiveMongoRepository在此基础上构建,以提供一些特定于MongoDB的查询功能。
用Spring WebFlux实现一个Controller
添加存储库后,您就可以以编程方式操作数据。但是,我们还没有定义Web端点。必须明确定义公共Web端点KayakController.java:
@Controller @RequestMapping(path = <font>"/kayaks"</font><font>) <b>public</b> <b>class</b> KayakController { <b>private</b> KayakRepository kayakRepository; <b>public</b> KayakController(KayakRepository kayakRepository) { <b>this</b>.kayakRepository = kayakRepository; } @PostMapping() <b>public</b> @ResponseBody Mono<Kayak> addKayak(@RequestBody Kayak kayak) { <b>return</b> kayakRepository.save(kayak); } @GetMapping() <b>public</b> @ResponseBody Flux<Kayak> getAllKayaks() { <b>return</b> kayakRepository.findAll(); } } </font>
该控制器添加两个端点:
该类使用Spring依赖注入将KayakRepository实例自动装入控制器,您将看到如何使用存储库持久化Kayak域类。
(与传统控制器区别是Spring WebFlux返回Flux类型和Mono类型)
测试Spring Boot Server
此时,您拥有一个完全可操作的皮划艇REST资源服务器。在测试之前,请将以下方法添加到您的MainApplication课程中。这只是在应用程序加载时将一些测试数据注入数据库。
@Bean ApplicationRunner init(KayakRepository repository) { Object[][] data = { {<font>"sea"</font><font>, </font><font>"Andrew"</font><font>, 300.12, </font><font>"NDK"</font><font>}, {</font><font>"creek"</font><font>, </font><font>"Andrew"</font><font>, 100.75, </font><font>"Piranha"</font><font>}, {</font><font>"loaner"</font><font>, </font><font>"Andrew"</font><font>, 75, </font><font>"Necky"</font><font>} }; <b>return</b> args -> { repository .deleteAll() .thenMany( Flux .just(data) .map(array -> { <b>return</b> <b>new</b> Kayak((String) array[0], (String) array[1], (Number) array[2], (String) array[3]); }) .flatMap(repository::save) ) .thenMany(repository.findAll()) .subscribe(kayak -> System.out.println(</font><font>"saving "</font><font> + kayak.toString())); }; } </font>
HTTPie是一个很棒的命令行实用程序,可以轻松地对资源服务器运行请求。如果您没有安装HTTPie,请使用安装它brew install httpie。或者前往 他们的网站 并实现目标。
确保您的Spring Boot应用程序正在运行。如果不是,请使用它
./gradlew bootRun。
对资源服务器运行GET请求:http :8080/kayaks这是简写
http GET http://localhost:8080/kayaks。
你会得到这个:
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 transfer-encoding: chunked <p>[ { <font>"makeModel"</font><font>: </font><font>"NDK"</font><font>, </font><font>"name"</font><font>: </font><font>"sea"</font><font>, </font><font>"owner"</font><font>: </font><font>"Andrew"</font><font>, </font><font>"value"</font><font>: 300.12 }, { </font><font>"makeModel"</font><font>: </font><font>"Piranha"</font><font>, </font><font>"name"</font><font>: </font><font>"creek"</font><font>, </font><font>"owner"</font><font>: </font><font>"Andrew"</font><font>, </font><font>"value"</font><font>: 100.75 }, { </font><font>"makeModel"</font><font>: </font><font>"Necky"</font><font>, </font><font>"name"</font><font>: </font><font>"loaner"</font><font>, </font><font>"owner"</font><font>: </font><font>"Andrew"</font><font>, </font><font>"value"</font><font>: 75 } ] </font>
现在尝试POST一条新的皮划艇到服务器。
http POST :8080/kayaks name="sea2" owner="Andrew" value="500" makeModel="P&H"
你应该看到:
HTTP/1.1 200 OK Content-Length: 62 Content-Type: application/json;charset=UTF-8 { <font>"makeModel"</font><font>: </font><font>"P&H"</font><font>, </font><font>"name"</font><font>: </font><font>"sea2"</font><font>, </font><font>"owner"</font><font>: </font><font>"Andrew"</font><font>, </font><font>"value"</font><font>: 500 } </font>