Skip to content

Commit 0bd6f8a

Browse files
committed
#406 - Allow configuring R2dbcEntityOperations bean reference in EnableR2dbcRepositories.
We now accept a bean reference to R2dbcEntityOperations so that a single application can scan for repositories that use different dialects/database systems.
1 parent b605daa commit 0bd6f8a

File tree

11 files changed

+292
-31
lines changed

11 files changed

+292
-31
lines changed

src/main/asciidoc/reference/r2dbc-repositories.adoc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,42 @@ include::../{spring-data-commons-docs}/repository-projections.adoc[leveloffset=+
339339
include::../{spring-data-commons-docs}/entity-callbacks.adoc[leveloffset=+1]
340340
include::./r2dbc-entity-callbacks.adoc[leveloffset=+2]
341341

342+
[[r2dbc.multiple-databases]]
343+
=== Working with multiple Databases
344+
345+
When working with multiple, potentially different databases, your application will require a different approach to configuration.
346+
The provided `AbstractR2dbcConfiguration` support class assumes a single `ConnectionFactory` from which the `Dialect` gets derived.
347+
That being said, you need to define a few beans yourself to configure Spring Data R2DBC to work with multiple databases.
348+
349+
R2DBC repositories require either a `DatabaseClient` and `ReactiveDataAccessStrategy` or `R2dbcEntityOperations` to implement repositories.
350+
A simple configuration to scan for repositories without using `AbstractR2dbcConfiguration` looks like:
351+
352+
[source,java]
353+
----
354+
@Configuration
355+
@EnableR2dbcRepositories(basePackages = "com.acme.mysql", entityOperationsRef = "mysqlR2dbcEntityOperations")
356+
static class MySQLConfiguration {
357+
358+
@Bean
359+
@Qualifier("mysql")
360+
public ConnectionFactory mysqlConnectionFactory() {
361+
return …;
362+
}
363+
364+
@Bean
365+
public R2dbcEntityOperations mysqlR2dbcEntityOperations(@Qualifier("mysql") ConnectionFactory connectionFactory) {
366+
367+
DefaultReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(MySqlDialect.INSTANCE);
368+
DatabaseClient databaseClient = DatabaseClient.builder()
369+
.connectionFactory(connectionFactory)
370+
.dataAccessStrategy(strategy)
371+
.build();
372+
373+
return new R2dbcEntityTemplate(databaseClient, strategy);
374+
}
375+
}
376+
----
377+
378+
Note that `@EnableR2dbcRepositories` allows configuration either through `databaseClientRef` or `entityOperationsRef`.
379+
Using various `DatabaseClient` beans is useful when connecting to multiple databases of the same type.
380+
When using different database systems that differ in their dialect, use `@EnableR2dbcRepositories`(entityOperationsRef = …)` instead.

src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
4242
*/
4343
DatabaseClient getDatabaseClient();
4444

45+
/**
46+
* Expose the underlying {@link ReactiveDataAccessStrategy} encapsulating dialect specifics.
47+
*
48+
* @return the underlying {@link ReactiveDataAccessStrategy}.
49+
* @see ReactiveDataAccessStrategy
50+
* @since 1.1.3
51+
*/
52+
ReactiveDataAccessStrategy getDataAccessStrategy();
53+
4554
// -------------------------------------------------------------------------
4655
// Methods dealing with org.springframework.data.r2dbc.query.Query
4756
// -------------------------------------------------------------------------

src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ public DatabaseClient getDatabaseClient() {
125125
return this.databaseClient;
126126
}
127127

128+
/*
129+
* (non-Javadoc)
130+
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#getDataAccessStrategy()
131+
*/
132+
@Override
133+
public ReactiveDataAccessStrategy getDataAccessStrategy() {
134+
return this.dataAccessStrategy;
135+
}
136+
128137
/*
129138
* (non-Javadoc)
130139
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)

src/main/java/org/springframework/data/r2dbc/repository/config/EnableR2dbcRepositories.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,21 @@
121121
* repositories detected.
122122
*
123123
* @return
124+
* @see #entityOperationsRef()
124125
*/
125126
String databaseClientRef() default "r2dbcDatabaseClient";
126127

128+
/**
129+
* Configures the name of the {@link org.springframework.data.r2dbc.core.R2dbcEntityOperations} bean to be used with
130+
* the repositories detected. Used as alternative to {@link #databaseClientRef()} to configure an access strategy when
131+
* using repositories with different database systems/dialects. If this attribute is set, then
132+
* {@link #databaseClientRef()} is ignored.
133+
*
134+
* @return
135+
* @since 1.1.3
136+
*/
137+
String entityOperationsRef() default "";
138+
127139
/**
128140
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
129141
* repositories infrastructure.

src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoryConfigurationExtension.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
3030
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
3131
import org.springframework.data.repository.core.RepositoryMetadata;
32+
import org.springframework.util.StringUtils;
3233

3334
/**
3435
* Reactive {@link RepositoryConfigurationExtension} for R2DBC.
@@ -97,8 +98,13 @@ public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfi
9798

9899
AnnotationAttributes attributes = config.getAttributes();
99100

100-
builder.addPropertyReference("databaseClient", attributes.getString("databaseClientRef"));
101-
builder.addPropertyReference("dataAccessStrategy", "reactiveDataAccessStrategy");
101+
String entityOperationsRef = attributes.getString("entityOperationsRef");
102+
if (StringUtils.hasText(entityOperationsRef)) {
103+
builder.addPropertyReference("entityOperations", entityOperationsRef);
104+
} else {
105+
builder.addPropertyReference("databaseClient", attributes.getString("databaseClientRef"));
106+
builder.addPropertyReference("dataAccessStrategy", "reactiveDataAccessStrategy");
107+
}
102108
}
103109

104110
/*

src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.data.projection.ProjectionFactory;
2323
import org.springframework.data.r2dbc.convert.R2dbcConverter;
2424
import org.springframework.data.r2dbc.core.DatabaseClient;
25+
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
2526
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
2627
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
2728
import org.springframework.data.r2dbc.repository.R2dbcRepository;
@@ -54,9 +55,9 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
5455
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
5556

5657
private final DatabaseClient databaseClient;
58+
private final ReactiveDataAccessStrategy dataAccessStrategy;
5759
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext;
5860
private final R2dbcConverter converter;
59-
private final ReactiveDataAccessStrategy dataAccessStrategy;
6061

6162
/**
6263
* Creates a new {@link R2dbcRepositoryFactory} given {@link DatabaseClient} and {@link MappingContext}.
@@ -70,9 +71,25 @@ public R2dbcRepositoryFactory(DatabaseClient databaseClient, ReactiveDataAccessS
7071
Assert.notNull(dataAccessStrategy, "ReactiveDataAccessStrategy must not be null!");
7172

7273
this.databaseClient = databaseClient;
74+
this.dataAccessStrategy = dataAccessStrategy;
75+
this.converter = dataAccessStrategy.getConverter();
76+
this.mappingContext = this.converter.getMappingContext();
77+
}
78+
79+
/**
80+
* Creates a new {@link R2dbcRepositoryFactory} given {@link R2dbcEntityOperations}.
81+
*
82+
* @param operations must not be {@literal null}.
83+
* @since 1.1.3
84+
*/
85+
public R2dbcRepositoryFactory(R2dbcEntityOperations operations) {
86+
87+
Assert.notNull(operations, "R2dbcEntityOperations must not be null!");
88+
89+
this.databaseClient = operations.getDatabaseClient();
90+
this.dataAccessStrategy = operations.getDataAccessStrategy();
7391
this.converter = dataAccessStrategy.getConverter();
7492
this.mappingContext = this.converter.getMappingContext();
75-
this.dataAccessStrategy = dataAccessStrategy;
7693
}
7794

7895
/*

src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.springframework.data.mapping.context.MappingContext;
2121
import org.springframework.data.r2dbc.core.DatabaseClient;
22+
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
2223
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
2324
import org.springframework.data.repository.Repository;
2425
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
@@ -28,7 +29,8 @@
2829

2930
/**
3031
* {@link org.springframework.beans.factory.FactoryBean} to create
31-
* {@link org.springframework.data.r2dbc.repository.R2dbcRepository} instances.
32+
* {@link org.springframework.data.r2dbc.repository.R2dbcRepository} instances. Can be either configured with
33+
* {@link R2dbcEntityOperations} or {@link DatabaseClient} with {@link ReactiveDataAccessStrategy}.
3234
*
3335
* @author Mark Paluch
3436
* @author Christoph Strobl
@@ -39,6 +41,7 @@ public class R2dbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID exten
3941

4042
private @Nullable DatabaseClient client;
4143
private @Nullable ReactiveDataAccessStrategy dataAccessStrategy;
44+
private @Nullable R2dbcEntityOperations operations;
4245

4346
private boolean mappingContextConfigured = false;
4447

@@ -56,26 +59,31 @@ public R2dbcRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
5659
*
5760
* @param client the client to set
5861
*/
59-
public void setDatabaseClient(@Nullable DatabaseClient client) {
62+
public void setDatabaseClient(DatabaseClient client) {
6063
this.client = client;
6164
}
6265

66+
public void setDataAccessStrategy(ReactiveDataAccessStrategy dataAccessStrategy) {
67+
this.dataAccessStrategy = dataAccessStrategy;
68+
}
69+
70+
/**
71+
* @param operations
72+
* @since 1.1.3
73+
*/
74+
public void setEntityOperations(R2dbcEntityOperations operations) {
75+
this.operations = operations;
76+
}
77+
6378
/*
6479
* (non-Javadoc)
6580
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#setMappingContext(org.springframework.data.mapping.context.MappingContext)
6681
*/
6782
@Override
68-
protected void setMappingContext(@Nullable MappingContext<?, ?> mappingContext) {
83+
protected void setMappingContext(MappingContext<?, ?> mappingContext) {
6984

85+
this.mappingContextConfigured = true;
7086
super.setMappingContext(mappingContext);
71-
72-
if (mappingContext != null) {
73-
this.mappingContextConfigured = true;
74-
}
75-
}
76-
77-
public void setDataAccessStrategy(@Nullable ReactiveDataAccessStrategy dataAccessStrategy) {
78-
this.dataAccessStrategy = dataAccessStrategy;
7987
}
8088

8189
/*
@@ -84,7 +92,9 @@ public void setDataAccessStrategy(@Nullable ReactiveDataAccessStrategy dataAcces
8492
*/
8593
@Override
8694
protected final RepositoryFactorySupport createRepositoryFactory() {
87-
return getFactoryInstance(client, dataAccessStrategy);
95+
96+
return this.operations != null ? getFactoryInstance(this.operations)
97+
: getFactoryInstance(this.client, this.dataAccessStrategy);
8898
}
8999

90100
/**
@@ -99,15 +109,32 @@ protected RepositoryFactorySupport getFactoryInstance(DatabaseClient client,
99109
return new R2dbcRepositoryFactory(client, dataAccessStrategy);
100110
}
101111

112+
/**
113+
* Creates and initializes a {@link RepositoryFactorySupport} instance.
114+
*
115+
* @param operations must not be {@literal null}.
116+
* @return new instance of {@link RepositoryFactorySupport}.
117+
* @since 1.1.3
118+
*/
119+
protected RepositoryFactorySupport getFactoryInstance(R2dbcEntityOperations operations) {
120+
return new R2dbcRepositoryFactory(operations);
121+
}
122+
102123
/*
103124
* (non-Javadoc)
104125
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
105126
*/
106127
@Override
107128
public void afterPropertiesSet() {
108129

109-
Assert.state(client != null, "DatabaseClient must not be null!");
110-
Assert.state(dataAccessStrategy != null, "ReactiveDataAccessStrategy must not be null!");
130+
if (operations == null) {
131+
132+
Assert.state(client != null, "DatabaseClient must not be null when R2dbcEntityOperations is not configured!");
133+
Assert.state(dataAccessStrategy != null,
134+
"ReactiveDataAccessStrategy must not be null when R2dbcEntityOperations is not configured!");
135+
} else {
136+
dataAccessStrategy = operations.getDataAccessStrategy();
137+
}
111138

112139
if (!mappingContextConfigured) {
113140
setMappingContext(dataAccessStrategy.getConverter().getMappingContext());

src/test/java/org/springframework/data/r2dbc/repository/config/Person.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
/**
1919
* @author Mark Paluch
2020
*/
21-
class Person {}
21+
public class Person {}

0 commit comments

Comments
 (0)