This project is merged as of version 3.0 in the Spring Data Relational repository.
Spring Data R2DBC is Open Source software released under the Apache 2.0 license.
Provide support to increase developer productivity in Java when using Reactive Relational Database Connectivity. Uses familiar Spring concepts such as a DatabaseClient for core API usage and lightweight repository style data access.
License: Apache License 2.0
This project is merged as of version 3.0 in the Spring Data Relational repository.
Spring Data R2DBC is Open Source software released under the Apache 2.0 license.
Take a look at this
databaseClient.execute()
.sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
.bind("$1", 42055)
.bind("$2", "Description")
.bindNull("$3", Integer.class)
.fetch()
The method name execute()
sounds & feels like we are executing something, not building a query for execution. For comparison, just try to understand what is fetch
& what is execute
in general. Both are ACTIONs, but the API expects us to treat them differently for no good reason. For a user who knows nothing about Spring Data R2DBC, execute
API is not at all intuitive
I saw the same flawed naming convention being followed in WebClient
api of webflux when it comes to building http requests, i.e inorder to build a GET
request, they expect us to write webClient.get()
, but this sounds & feels like we are making GET
call when we are writing it (as per intuition), but we with WebFlux
api we are not making the call with get
, rather we are informing the API to return a *Builder
/*Spec
that will eventually make a GET
request. Its a flawed API design as it breaks intuition
I have couple of suggestions about it
Either name methods that build something with op<Builder>()
(or) for<Op>
, i.e for building execute
statements use databaseClient.executeBuilder()
(or) databaseClient.forExecute()
The first one is more direct & does not leave any space for any confusion
See #7: Calling repository.saveAll(Arrays.asList(legoSet1, legoSet2, legoSet3, legoSet4))
does not retain the order of input elements and emits results in a different order.
We should add Dialect
support to use driver/database-specific bind-markers in generated SQL statements.
We should support custom converters to allow conversion for nested properties to allow the conversion of individual properties.
Remove common properties from index.adoc as they are now defined in Spring Data Build.
We should introduce a client abstraction for reactive relational database access (DatabaseClient
) that allows execution of arbitrary SQL, select, insert, … operations and provide a reactive repository implementation on top of this.
Right now, we require locally running databases to run our tests. We should allow for using TestContainers to reduce build requirements (Docker instead of multiple databases).
R2DBC uses native parameter bind markers ($1
, $2
for Postgres, @foo
, @bar
for Microsoft SQL Server). Native bind markers require a specific adaption within queries to use the appropriate bind marker and to properly bind parameters.
We should introduce a component to create bind markers (for dynamic SQL generation) and to bind parameters that both honor the native specifics.
Parameter binding is a pre-requisite to creating a translation layer which accepts standardized parameter declaration (e.g. named parameters such as in NamedParameterJdbcTemplate
)
Currently Spring data Jdbc/JPA can recognize spring.datasource
and build a DataSource
bean, if possible create a ConnectionFactory
bean from spring.datasource
automatically if it is configured in Spring Boot applications?
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class People {
@Id private String id;
private String name;
}
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface PeopleRepository extends ReactiveCrudRepository<People, String> {
}
@Autowired private PeopleRepository repository;
@Test
void test() {
// save it
People po = new People();
po.setId(UUID.randomUUID().toString());
po.setName("simter");
StepVerifier.create(repository.save(po))
.expectNext(po)
.verifyComplete();
// verify saved
StepVerifier.create(repository.findById(po.getId()))
.expectNext(po)
.verifyComplete();
}
I log out io.r2dbc.h2.client.SessionClient
as debug, request a update
sql not insert into
sql. Entity not saved and repository.findById
found nothing.
···
2019-01-11 11:37:00.404 DEBUG io.r2dbc.h2.client.SessionClient : Request: UPDATE people SET name = $2 WHERE id = $1 {1: '1d7d50d3-b1a3-4c27-8552-00d2bf9c5b08', 2: 'simter'}
···
I'm using spring-data-r2dbc-1.0.0.M1 with r2dbc-postgresql-1.0.0.M6. See below code, failed to select single field value.
Code :
public class Dream {
@Id private Integer id;
private String name;
...
}
public interface DreamRepository extends ReactiveCrudRepository<Dream, Integer> {
@Query("select name from dream")
Flux<String> findAllName();
}
Error message :
org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class java.lang.String!
at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:79)
at org.springframework.data.r2dbc.function.DefaultReactiveDataAccessStrategy.getRequiredPersistentEntity(DefaultReactiveDataAccessStrategy.java:242)
at org.springframework.data.r2dbc.function.DefaultReactiveDataAccessStrategy.getRowMapper(DefaultReactiveDataAccessStrategy.java:228)
at org.springframework.data.r2dbc.function.DefaultDatabaseClient$DefaultTypedExecuteSpec.<init>(DefaultDatabaseClient.java:476)
at org.springframework.data.r2dbc.function.DefaultDatabaseClient.createTypedExecuteSpec(DefaultDatabaseClient.java:223)
at org.springframework.data.r2dbc.function.DefaultDatabaseClient$DefaultGenericExecuteSpec.as(DefaultDatabaseClient.java:408)
at org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery.execute(AbstractR2dbcQuery.java:106)
at org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery.execute(AbstractR2dbcQuery.java:84)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605)
...
at com.sun.proxy.$Proxy38.findAllName(Unknown Source)
at tech.simter.start.r2dbc.spring.dao.FindAllNameMethodImplTest.test(FindAllNameMethodImplTest.java:40)
...
My unit test code is FindAllNameMethodImplTest.java
#10 gives me a regression on h2:
Syntax error in SQL statement "INSERT INTO NEWS_USER (FIRSTNAME,LASTNAME,USERNAME,PASSWORD) VALUES($1,$2,$3,$4) RETURNING[*] * "
I guess H2 does support only returning id.
Right now, we're using Spring Framework's JDBC module to apply exception translation. Spring JDBC is heavily based on JDBC API and it makes little sense to pull JDBC API into R2DBC, especially when running in modularized Java 9+ environments.
We should provide our own exception translation mechanism that is built along the lines of Spring JDBC's SQLErrorCodes
without using JDBC API.
We should explicitly call bind(int, …)
methods when binding parameters by their positional index.
We should leverage StatementBuilder
to create SQL statements instead of using string concatenation.
Currently, when I add this library as a dependency, it automatically pulls mssql-jdbc
driver. I am not sure if this intentional. Can someone please check if this is indeed intentional?
Ref:
Lines 241 to 245 in 3d1041c
No spring r2dbc for mariadb exists as of today. As said in https://r2dbc.io/ videos there is a support mentioned 2 months back in works. So please do help in providing initial repository (mariadb r2dbc) for community to test and give feedback.
We should provide an abstract R2dbcConfiguration
class that configures components which are required to spin up repositories. These are a ConnectionFactory
and DatabaseClient
We should provide a configuration infrastructure to configure the beans that are necessary to bootstrap R2DBC repositories.
The following code causes an IllegalStateException
:
client
.inTransaction(
dc ->
passwordHistoryRepository
.save(new PasswordHistory(1))
.then(Mono.error(new RuntimeException("6")))
.then(passwordHistoryRepository.save(new PasswordHistory(2))
)
Trace:
RuntimeException, trace=java.lang.RuntimeException: Async resource cleanup failed after onComplete
at reactor.core.publisher.FluxUsingWhen$CommitInner.onError(FluxUsingWhen.java:519)
...
Caused by: java.lang.IllegalStateException: Connection is closed!
at org.springframework.data.r2dbc.function.connectionfactory.SingletonConnectionFactory.create(SingletonConnectionFactory.java:54)
at org.springframework.data.r2dbc.function.DefaultTransactionalDatabaseClient.lambda$cleanup$7(DefaultTransactionalDatabaseClient.java:142)
Related ticket: #44.
Add docs/index.html
with a redirect to https://spring.io/projects/spring-data-r2dbc
We should add R2dbcCustomConversions
to distinguish between entity types and non-entity types.
Right now, String[]
is considered an entity as well as List
and Collection
. For Postgres, we would like to read and write array types.
We should consider adding a Query Pre-Processor API that allows generic pre-processing of queries. The first use-case of query-pre-processing is named parameter expansion (see #23). Another case can be generic query augmentation to add or remove parts of a query and to mutate bound parameters. Query pre-processors can be modeled as a filter function to compose a chain of functions.
Reading of Map entity property is intentionally blocked by above line.
How about allowing it and then delegate to the client's custom DBStoredValue-To-Map converter. Similar to:
static class Config extends AbstractR2dbcConfiguration {
@Override @Bean public R2dbcCustomConversions r2dbcCustomConversions() {
...
return new R2dbcCustomConversions(
storeConversions,
Arrays.asList(new JsonStringToMapConverter()));
}
class JsonStringToMapConverter implements Converter<String, Map<String, Object>> {
...
}
Hi, I want to ask when will you guys release the first GA version? would it be possible before April?
And also a road map will be helpful.
I really don't know if this is a bug of r2dbc but it happens with spring data when I try to save a very simple instance:
java.lang.AbstractMethodError: Receiver class io.r2dbc.h2.H2Statement does not define or inherit an implementation of the resolved method abstract bind(Ljava/lang/Integer;Ljava/lang/Object;)Lio/r2dbc/spi/Statement; of interface io.r2dbc.spi.Statement. at org.springframework.data.r2dbc.function.DefaultDatabaseClient$DefaultTypedInsertSpec.lambda$exchange$4(DefaultDatabaseClient.java:992) ~[spring-data-r2dbc-1.0.0.BUILD-20181030.065337-2.jar:1.0.0.BUILD-SNAPSHOT] at org.springframework.data.r2dbc.function.DefaultDatabaseClient$DefaultTypedInsertSpec.lambda$exchange$5(DefaultDatabaseClient.java:1002) ~[spring-data-r2dbc-1.0.0.BUILD-20181030.065337-2.jar:1.0.0.BUILD-SNAPSHOT] at org.springframework.data.r2dbc.function.DefaultSqlResult$1.apply(DefaultSqlResult.java:54) ~[spring-data-r2dbc-1.0.0.BUILD-20181030.065337-2.jar:1.0.0.BUILD-SNAPSHOT] at org.springframework.data.r2dbc.function.DefaultSqlResult$1.apply(DefaultSqlResult.java:51) ~[spring-data-r2dbc-1.0.0.BUILD-20181030.065337-2.jar:1.0.0.BUILD-SNAPSHOT] at org.springframework.data.r2dbc.function.DefaultDatabaseClient.doInConnectionMany(DefaultDatabaseClient.java:1016) ~[spring-data-r2dbc-1.0.0.BUILD-20181030.065337-2.jar:1.0.0.BUILD-SNAPSHOT] at org.springframework.data.r2dbc.function.DefaultDatabaseClient.lambda$inConnectionMany$2(DefaultDatabaseClient.java:160) ~[spring-data-r2dbc-1.0.0.BUILD-20181030.065337-2.jar:1.0.0.BUILD-SNAPSHOT]
is this a problem with the current snapshot releases?
Any hint is very welcome :)
We should provide reference docs for this project.
Oracle constantly keeps changing Java 8 download paths which breaks the build. We rather fall back to an older JDK instead of being affected by broken builds caused due to external infrastructure changes.
Postgres supports ARRAY columns that allow storing multiple values in a field. #22 allows reading these values. We need a possibility how to store these. The assumption for Collection
-typed fields is that these values are stored using relations and not within a single column.
when has r2dbc mysql driver implement?
I noticed today that calls to reactive repository save
method hang indefinitely when block
is called (for instance in tests, I'm using Postgre DB).
It worked few days ago, please see my example project and specifically try R2dbcTestApplicationTest.testSave
method: https://github.com/nbabic298/basic-spring-r2dbc-postgre
I tried to dig into the source code for save but unfortunately couldn't find my way through.
We should consider renaming GenericInsertSpec.value(…)
methods to bind
respective bindNull
for symmetry between BindSpec
and the terminology of R2DBC Statement
.
Hi guys,
I'm using compile group: 'org.springframework.data', name: 'spring-data-r2dbc', version: '1.0.0.M1'
What I done is:
@Override public Flux<T> findAll() { return databaseClient.select().from(entity.getJavaType()).fetch().all(); }
From ReactiveCrudRepository
Here is a trace:
2019-01-21 17:30:09.743 ERROR 2353 --- [-server-epoll-6] r.i.n.c.ChannelOperations :
java.lang.NoSuchMethodError: reactor.core.publisher.Flux.usingWhen(Lorg/reactivestreams/Publisher;Ljava/util/function/Function;Ljava/util/function/Function;Ljava/util/function/Function;Ljava/util/function/Function;)Lreactor/core/publisher/Flux;
at org.springframework.data.r2dbc.function.DefaultDatabaseClient.inConnectionMany(DefaultDatabaseClient.java:155) ~[spring-data-r2dbc-1.0.0.M1.jar:1.0.0.M1]
at org.springframework.data.r2dbc.function.DefaultFetchSpec.all(DefaultFetchSpec.java:75) ~[spring-data-r2dbc-1.0.0.M1.jar:1.0.0.M1]
at org.springframework.data.r2dbc.function.DefaultSqlResult.all(DefaultSqlResult.java:141) ~[spring-data-r2dbc-1.0.0.M1.jar:1.0.0.M1]
at org.springframework.data.r2dbc.repository.support.SimpleR2dbcRepository.findAll(SimpleR2dbcRepository.java:175) ~[spring-data-r2dbc-1.0.0.M1.jar:1.0.0.M1]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_201]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_201]
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:377) ~[spring-data-commons-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200) ~[spring-data-commons-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:629) ~[spring-data-commons-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:593) ~[spring-data-commons-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:578) ~[spring-data-commons-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) ~[spring-data-commons-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61) ~[spring-data-commons-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at com.sun.proxy.$Proxy83.findAll(Unknown Source) ~[?:?]
at com.arm.hma.simulator.controller.DeviceDirectoryController.getDevices(DeviceDirectoryController.java:29) ~[bin/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_201]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_201]
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:243) ~[spring-webflux-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$invoke$0(InvocableHandlerMethod.java:138) ~[spring-webflux-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:141) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:53) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:153) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:271) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:803) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:115) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:1640) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:156) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:1454) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:1328) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.Mono.subscribe(Mono.java:3080) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:418) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:210) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:140) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:64) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxConcatMap.subscribe(FluxConcatMap.java:121) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoNext.subscribe(MonoNext.java:40) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.Mono.subscribe(Mono.java:3080) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:70) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.ipc.netty.channel.ChannelOperations.applyHandler(ChannelOperations.java:380) ~[reactor-netty-0.7.8.RELEASE.jar:0.7.8.RELEASE]
at reactor.ipc.netty.http.server.HttpServerOperations.onHandlerStart(HttpServerOperations.java:398) ~[reactor-netty-0.7.8.RELEASE.jar:0.7.8.RELEASE]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163) [netty-common-4.1.27.Final.jar:4.1.27.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404) [netty-common-4.1.27.Final.jar:4.1.27.Final]
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:322) [netty-transport-native-epoll-4.1.27.Final-linux-x86_64.jar:4.1.27.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) [netty-common-4.1.27.Final.jar:4.1.27.Final]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_201]
As transaction doc said, we can use transaction with calling the APIs of TransactionalDatabaseClient
, but how can I do it if I want to use repository?
What a potential solution I thought to approach my goal (using transaction for multiple repositories' operators) is:
transactionalDatabaseClient.beginTransaction();
userRepository.save(user);
passwordHistoryRepository.save(user.getPassword());
transactionalDatabaseClient.commitTransaction();
But it won't work, right? because in the example, transaction is implemented by same dataClient
, but Repository
is using another client, How can I customize the dataClient
in Repository
?
And I checked the detail code, transaction is implemented based on Connection
, to approach my goal, how can I let dataClinet
and Repository
use same Connection
?
We should introduce named parameter support for DatabaseClient
and repositories using a uniform naming pattern to decouple from vendor-specific bind marker syntax.
Right now, we need to stick to vendor-specific bind markers if we want to bind parameters to a statement. This makes statements non-portable.
Like exchange()
provides a generic way to deal with requests in WebClient
even those with no body, I would expect DatabaseClient
variant to do the same. Maybe by providing in SqlResult
methods suitable to deal with request with no rows like DROP
or CREATE
ones.
I created a sample project to demo spring-data-r2dbc, check it from my github account.
https://github.com/hantsy/spring-reactive-sample/tree/master/data-r2dbc
For other Spring data projects, there is a @EnableXXX
annotation for this purpose, but I can not find such one in Spring Data R2dbc, my sample failed when it is started.
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'postController' defined in file [E:\hantsylabs\spring-reactive-sample\data-r2dbc\target\classes\com\example\demo\PostController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.PostRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
With the introduction of the QueryBuilder API and the renderer in Spring Data Relational we need to move a part of the dialect-specifics into Spring Data Relational (e.g. paging behavior queries).
R2DBC SPI removed executeReturningGeneratedKeys()
so we need to append RETURNING *
(for PostgreSQL) to return generated keys.
Currently, we have conversion code laid out in EntityRowMapper
(entity reading), DefaultReactiveDataAccessStrategy
(entity to SettableValue
conversion). We should pull methods into a single MappingR2dbcConverter
to keep conversion code closely together.
Currently, dependencies cause test failures on Java 9 and higher.
What are the guarantees in terms of insertion order for R2dbcRepository#saveAll
for new items? Some experiments I have been playing around with seem to show that the insertion order is not honored, which makes sense since SimpleR2dbcRepository#saveAll
is just a flatMap built on SimpleR2dbcRepository#save
, and the latter queues an asynchronous job on the TcpResources pool of Reactor-Netty.
While our-of-order updates is pretty much a non-issue in my experience, not honoring the insertion order is very confusing when the database is generating sequential primary keys.
For reference, #7 enhances the tests to show sporadic failures due to the insertion order not been deterministic.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.