不久之前,发布了JDBC驱动程序的反应型。 称为R2DBC。 它允许将数据异步流传输到已预订的任何端点。 通过将R2DBC之类的反应性驱动程序与Spring WebFlux结合使用,可以编写一个完整的应用程序,以异步方式处理数据的接收和发送。 在本文中,我们将重点介绍数据库。 从连接到数据库,然后最终保存和检索数据。 为此,我们将使用Spring Data。 与所有Spring Data模块一样,它为我们提供了现成的配置。 减少为实现应用程序设置而需要编写的样板代码数量。 最重要的是,它在数据库驱动程序上提供了一层,使执行简单任务变得更加容易,而较困难的任务则减轻了一些痛苦。
对于本文的内容,我正在使用Postgres数据库。 在撰写本文时,只有Postgres,H2和Microsoft SQL Server拥有自己的R2DBC驱动程序实现。
I have previously written two posts about reactive Spring Data libraries, one on Mongo and another about Cassandra. You might have noticed that neither of these databases are RDBMS databases. Now there are other reactive drivers available for a long time (I wrote the Mongo post nearly 2 years ago) but at the time of writing a reactive driver for a RDBMS database is still a pretty new thing. This post will follow a similar format to those.
Furthermore, I have also written a post about using Spring WebFlux which I mentioned in the introduction. Feel free to have a look at that if you are interested in producing a fully reactive web application.
Dependencies
org.springframework.boot
spring-boot-starter
org.springframework.data
spring-data-r2dbc
1.0.0.M1
io.r2dbc
r2dbc-postgresql
1.0.0.M6
io.projectreactor
reactor-core
repository.spring.milestone
Spring Milestone Repository
http://repo.spring.io/milestone
这里有几点要指出。
使用Spring Boot的次数越多,您越习惯于导入单个弹簧启动启动器对您想做的很酷的事情的依赖。 例如,我希望会有一个弹簧启动启动器-r2dbc依赖性,但不幸的是,没有依赖性。 然而。 简而言之,该库位于较新的一侧,在编写本文时,它没有自己的Spring Boot模块,该模块包含所需的任何依赖项以及通过自动配置的更快设置。 我确信这些事情会在某个时候出现,并使设置R2DBC驱动程序变得更加容易。
现在,我们将需要手动填写一些额外的依赖项。
此外,R2DBC库仅具有Milestone版本(更多证明是新版本),因此我们需要确保引入Spring Milestone存储库。 当我获得发布版本时,将来可能需要更新此帖子。
Connecting to the database
由于Spring Data为我们做了很多工作,因此唯一需要手动创建的Bean是连接工厂包含数据库的连接详细信息:
@Configuration
@EnableR2dbcRepositories
class DatabaseConfiguration(
@Value("\${spring.data.postgres.host}") private val host: String,
@Value("\${spring.data.postgres.port}") private val port: Int,
@Value("\${spring.data.postgres.database}") private val database: String,
@Value("\${spring.data.postgres.username}") private val username: String,
@Value("\${spring.data.postgres.password}") private val password: String
) : AbstractR2dbcConfiguration() {
override fun connectionFactory(): ConnectionFactory {
return PostgresqlConnectionFactory(
PostgresqlConnectionConfiguration.builder()
.host(host)
.port(port)
.database(database)
.username(username)
.password(password).build()
)
}
}
这里首先要注意的是抽象R2dbc配置。 此类包含大量不再需要手动创建的Bean。 实施中connectionFactory是类的唯一要求,因为创建数据库客户端豆。 这种结构是Spring Data模块的典型结构,因此在尝试其他结构时会感到非常熟悉。 此外,我希望一旦有自动配置功能就可以删除此手动配置,并且只能通过application。properties。
我已经包括了港口属性在此处,但是如果您还没有使用Postgres配置,则可以依赖默认值5432。
四个属性:主办,数据库,用户名和密码由...定义PostgresqlConnectionFactory are the bare minimum to get it working. Any less和you will experience exceptions during startup.
使用此配置,Spring可以连接到正在运行的Postgres实例。
该示例中最后值得注意的信息是使用@ EnableR2dbcRepositories。 此注释指示Spring查找任何扩展Spring的存储库接口资料库接口。 这用作检测Spring Data存储库的基本接口。 在下一节中,我们将对此进行更仔细的研究。 从这里获取的主要信息是,您需要使用@ EnableR2dbcRepositories注释以充分利用Spring Data的功能。
Creating a Spring Data Repository
如上所述,在本节中,我们将介绍添加Spring Data Repository。 这些存储库是Spring Data的一个不错的功能,这意味着您无需写很多额外的代码即可简单地编写查询。 不幸的是,至少就目前而言,Spring R2DBC无法以与其他Spring Data模块当前相同的方式来推断查询(我肯定会在某个时候添加它)。 这意味着您将需要使用@查询注释并手动编写SQL。 让我们来看看:
@Repository
interface PersonRepository : R2dbcRepository<Person, Int> {
@Query("SELECT * FROM people WHERE name = $1")
fun findAllByName(name: String): Flux<Person>
@Query("SELECT * FROM people WHERE age = $1")
fun findAllByAge(age: Int): Flux<Person>
}
该接口扩展R2dbc存储库。 这反过来又扩展了ReactiveCrud储存库然后下降到资料库。ReactiveCrud储存库提供标准的CRUD功能,据我了解,R2dbc存储库不提供任何其他功能,而是为更好的情境命名而创建的接口。
R2dbc存储库接受两个通用参数,一个是它作为输入并产生作为输出的实体类。 第二个是主键的类型。 因此,在这种情况下,人该课程由人Repository(很有意义)以及其中的“主键”字段人是一个整数。
此类中函数的返回类型以及ReactiveCrud储存库是助焊剂和单声道 (not seen here). These是Project Reactor types that Spring makes use of as the default Reactive Stream types. 助焊剂表示多个元素的流,而a单声道是一个结果。
最后,正如我在示例之前提到的那样,每个函数都用@查询。 语法非常简单,SQL是注释中的字符串。 的$ 1(2元,三块钱,etc… for more inputs) represents the value input into the function. Once you have done this,Spring will handle the rest and pass the input(s) into their respective input parameter,gather the results and map it to the repository’s designated entity class.
A very quick look at the entity
在这里不多说,只是显示人所使用的类人Repository。
@Table("people")
data class Person(
@Id val id: Int? = null,
val name: String,
val age: Int
)
实际上,这里要讲一点。ID has been made 空值able and provIDed a default value of 空值 to allow Postgres to generate the next suitable value itself. If this is not 空值able and an ID value is provIDed, Spring will actually try to run an update instead of an insert upon saving. There are other ways around this, but I think this is good enough.
该实体将映射到人下表定义:
CREATE TABLE people (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
age INTEGER NOT NULL
);
Seeing it all in action
现在让我们看看它实际上在做什么。 下面是一些插入一些记录并以几种不同方式检索它们的代码:
@SpringBootApplication
class Application : CommandLineRunner {
@Autowired
private lateinit var personRepository: PersonRepository
override fun run(vararg args: String?) {
personRepository.saveAll(
listOf(
Person(name = "Dan Newton", age = 25),
Person(name = "Laura So", age = 23)
)
).log().subscribe()
personRepository.findAll().subscribe { log.info("findAll - $it") }
personRepository.findAllById(Mono.just(1)).subscribe { log.info("findAllById - $it") }
personRepository.findAllByName("Laura So").subscribe { log.info("findAllByName - $it") }
personRepository.findAllByAge(25).subscribe { log.info("findAllByAge - $it") }
}
}
关于此代码,我将提到一件事。 它很可能在没有实际插入或读取某些记录的情况下执行。 但是,当您考虑它时。 这说得通。 反应性应用程序旨在异步执行操作,因此该应用程序已开始处理不同线程中的函数调用。 如果不阻塞主线程,这些异步进程可能永远不会完全执行。 因此,有一些线程睡眠调用此代码中的内容,但我从示例中删除了它们,以使所有内容保持整洁。
运行上面的代码的输出如下所示:
2019-02-11 09:04:52.294 INFO 13226 --- [main] reactor.Flux.ConcatMap.1 : onSubscribe(FluxConcatMap.ConcatMapImmediate)
2019-02-11 09:04:52.295 INFO 13226 --- [main] reactor.Flux.ConcatMap.1 : request(unbounded)
2019-02-11 09:04:52.572 INFO 13226 --- [actor-tcp-nio-1] reactor.Flux.ConcatMap.1 : onNext(Person(id=35, name=Dan Newton, age=25))
2019-02-11 09:04:52.591 INFO 13226 --- [actor-tcp-nio-1] reactor.Flux.ConcatMap.1 : onNext(Person(id=36, name=Laura So, age=23))
2019-02-11 09:04:52.591 INFO 13226 --- [actor-tcp-nio-1] reactor.Flux.ConcatMap.1 : onComplete()
2019-02-11 09:04:54.472 INFO 13226 --- [actor-tcp-nio-2] com.lankydanblog.tutorial.Application : findAll - Person(id=35, name=Dan Newton, age=25)
2019-02-11 09:04:54.473 INFO 13226 --- [actor-tcp-nio-2] com.lankydanblog.tutorial.Application : findAll - Person(id=36, name=Laura So, age=23)
2019-02-11 09:04:54.512 INFO 13226 --- [actor-tcp-nio-4] com.lankydanblog.tutorial.Application : findAllByName - Person(id=36, name=Laura So, age=23)
2019-02-11 09:04:54.524 INFO 13226 --- [actor-tcp-nio-5] com.lankydanblog.tutorial.Application : findAllByAge - Person(id=35, name=Dan Newton, age=25)
这里有一些要注意的地方:
- onSubscribe和请求发生在主线程上助焊剂被叫。 只要保存全部输出此内容,因为它已包含日志 function. Adding this to the other calls would have lead to the same result of 日志ging to the main thread. The execution contained within the subscribe function和the internal steps of the 助焊剂在不同的线程上运行。
这与在实际应用程序中如何使用反应流的真实表示不尽相同,但希望可以演示如何使用它们,并对它们的执行方式提供一些见解。
Conclusion
总之,由于R2DBC驱动程序和Spring Data在顶部建立了一层,使所有内容变得更加整洁,因此Reactive Streams进入了某些RDBMS数据库。 通过使用Spring Data R2DBC,我们能够创建与数据库的连接并开始查询它,而无需太多代码。 尽管Spring已经为我们做了大量工作,但它可能会做更多的事情。 当前,它不具有Spring Boot自动配置支持。 这有点烦人。 但是,我相信有人很快就会做起来,并使所有事情变得比现在更好。
The code used in this post can be found on my GitHub.
If you found this post helpful, you can follow me on Twitter at @LankyDanDev to keep up with my new posts.