Skip to content

Commit f794bfc

Browse files
committed
#2 - Add R2dbcRepositoryFactory and simple query subsystem.
1 parent 28dee12 commit f794bfc

24 files changed

+1983
-42
lines changed

src/main/java/org/springframework/data/jdbc/core/function/MappingR2dbcConverter.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@
3737
*/
3838
public class MappingR2dbcConverter {
3939

40-
private final MappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext;
40+
private final MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext;
4141

42-
public MappingR2dbcConverter(MappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext) {
42+
public MappingR2dbcConverter(
43+
MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext) {
4344
this.mappingContext = mappingContext;
4445
}
4546

@@ -97,4 +98,8 @@ public <T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object) {
9798
return object;
9899
};
99100
}
101+
102+
public MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> getMappingContext() {
103+
return mappingContext;
104+
}
100105
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository;
17+
18+
import org.springframework.data.repository.NoRepositoryBean;
19+
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
20+
21+
/**
22+
* R2DBC specific {@link org.springframework.data.repository.Repository} interface with reactive support.
23+
*
24+
* @author Mark Paluch
25+
*/
26+
@NoRepositoryBean
27+
public interface R2dbcRepository<T, ID> extends ReactiveCrudRepository<T, ID> {}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import reactor.core.publisher.Flux;
19+
import reactor.core.publisher.Mono;
20+
21+
import org.reactivestreams.Publisher;
22+
import org.springframework.core.convert.converter.Converter;
23+
import org.springframework.data.convert.EntityInstantiators;
24+
import org.springframework.data.jdbc.core.function.DatabaseClient;
25+
import org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec;
26+
import org.springframework.data.jdbc.core.function.FetchSpec;
27+
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter;
28+
import org.springframework.data.jdbc.repository.query.R2dbcQueryExecution.ResultProcessingConverter;
29+
import org.springframework.data.jdbc.repository.query.R2dbcQueryExecution.ResultProcessingExecution;
30+
import org.springframework.data.repository.query.ParameterAccessor;
31+
import org.springframework.data.repository.query.RepositoryQuery;
32+
import org.springframework.data.repository.query.ResultProcessor;
33+
import org.springframework.data.repository.query.ReturnedType;
34+
import org.springframework.util.Assert;
35+
36+
/**
37+
* Base class for reactive {@link RepositoryQuery} implementations for R2DBC.
38+
*
39+
* @author Mark Paluch
40+
*/
41+
public abstract class AbstractR2dbcQuery implements RepositoryQuery {
42+
43+
private final R2dbcQueryMethod method;
44+
private final DatabaseClient databaseClient;
45+
private final MappingR2dbcConverter converter;
46+
private final EntityInstantiators instantiators;
47+
48+
/**
49+
* Creates a new {@link AbstractR2dbcQuery} from the given {@link R2dbcQueryMethod} and {@link DatabaseClient}.
50+
*
51+
* @param method must not be {@literal null}.
52+
* @param databaseClient must not be {@literal null}.
53+
* @param converter must not be {@literal null}.
54+
*/
55+
public AbstractR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, MappingR2dbcConverter converter) {
56+
57+
Assert.notNull(method, "R2dbcQueryMethod must not be null!");
58+
Assert.notNull(databaseClient, "DatabaseClient must not be null!");
59+
Assert.notNull(converter, "MappingR2dbcConverter must not be null!");
60+
61+
this.method = method;
62+
this.databaseClient = databaseClient;
63+
this.converter = converter;
64+
this.instantiators = new EntityInstantiators();
65+
}
66+
67+
/*
68+
* (non-Javadoc)
69+
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
70+
*/
71+
public R2dbcQueryMethod getQueryMethod() {
72+
return method;
73+
}
74+
75+
/*
76+
* (non-Javadoc)
77+
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
78+
*/
79+
public Object execute(Object[] parameters) {
80+
81+
return method.hasReactiveWrapperParameter() ? executeDeferred(parameters)
82+
: execute(new JdbcParametersParameterAccessor(method, parameters));
83+
}
84+
85+
@SuppressWarnings("unchecked")
86+
private Object executeDeferred(Object[] parameters) {
87+
88+
R2dbcParameterAccessor parameterAccessor = new R2dbcParameterAccessor(method, parameters);
89+
90+
if (getQueryMethod().isCollectionQuery()) {
91+
return Flux.defer(() -> (Publisher<Object>) execute(parameterAccessor));
92+
}
93+
94+
return Mono.defer(() -> (Mono<Object>) execute(parameterAccessor));
95+
}
96+
97+
private Object execute(JdbcParameterAccessor parameterAccessor) {
98+
99+
// TODO: ConvertingParameterAccessor
100+
BindableQuery query = createQuery(parameterAccessor);
101+
102+
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
103+
GenericExecuteSpec boundQuery = query.bind(databaseClient.execute().sql(query));
104+
FetchSpec<?> fetchSpec = boundQuery.as(resolveResultType(processor)).fetch();
105+
106+
String tableName = method.getEntityInformation().getTableName();
107+
108+
R2dbcQueryExecution execution = getExecution(
109+
new ResultProcessingConverter(processor, converter.getMappingContext(), instantiators));
110+
111+
return execution.execute(fetchSpec, processor.getReturnedType().getDomainType(), tableName);
112+
}
113+
114+
private Class<?> resolveResultType(ResultProcessor resultProcessor) {
115+
116+
ReturnedType returnedType = resultProcessor.getReturnedType();
117+
118+
return returnedType.isProjecting() ? returnedType.getDomainType() : returnedType.getReturnedType();
119+
}
120+
121+
/**
122+
* Returns the execution instance to use.
123+
*
124+
* @param resultProcessing must not be {@literal null}.
125+
* @return
126+
*/
127+
private R2dbcQueryExecution getExecution(Converter<Object, Object> resultProcessing) {
128+
return new ResultProcessingExecution(getExecutionToWrap(), resultProcessing);
129+
}
130+
131+
private R2dbcQueryExecution getExecutionToWrap() {
132+
133+
if (method.isCollectionQuery()) {
134+
return (q, t, c) -> q.all();
135+
}
136+
137+
return (q, t, c) -> q.one();
138+
}
139+
140+
/**
141+
* Creates a {@link BindableQuery} instance using the given {@link ParameterAccessor}
142+
*
143+
* @param accessor must not be {@literal null}.
144+
* @return
145+
*/
146+
protected abstract BindableQuery createQuery(JdbcParameterAccessor accessor);
147+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import java.util.function.Supplier;
19+
20+
import org.springframework.data.jdbc.core.function.DatabaseClient.BindSpec;
21+
22+
/**
23+
* Interface declaring a query that supplies SQL and can bind parameters to a {@link BindSpec}.
24+
*
25+
* @author Mark Paluch
26+
*/
27+
public interface BindableQuery extends Supplier<String> {
28+
29+
/**
30+
* Bind parameters to the {@link BindSpec query}.
31+
*
32+
* @param bindSpec must not be {@literal null}.
33+
* @return the bound query object.
34+
*/
35+
<T extends BindSpec<T>> T bind(T bindSpec);
36+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import org.springframework.core.convert.converter.Converter;
19+
import org.springframework.data.convert.EntityInstantiator;
20+
import org.springframework.data.convert.EntityInstantiators;
21+
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
22+
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty;
23+
import org.springframework.data.mapping.PersistentEntity;
24+
import org.springframework.data.mapping.PersistentProperty;
25+
import org.springframework.data.mapping.PersistentPropertyAccessor;
26+
import org.springframework.data.mapping.PreferredConstructor;
27+
import org.springframework.data.mapping.PreferredConstructor.Parameter;
28+
import org.springframework.data.mapping.SimplePropertyHandler;
29+
import org.springframework.data.mapping.context.MappingContext;
30+
import org.springframework.data.mapping.model.ParameterValueProvider;
31+
import org.springframework.util.Assert;
32+
33+
/**
34+
* {@link Converter} to instantiate DTOs from fully equipped domain objects.
35+
*
36+
* @author Mark Paluch
37+
*/
38+
class DtoInstantiatingConverter implements Converter<Object, Object> {
39+
40+
private final Class<?> targetType;
41+
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context;
42+
private final EntityInstantiator instantiator;
43+
44+
/**
45+
* Creates a new {@link Converter} to instantiate DTOs.
46+
*
47+
* @param dtoType must not be {@literal null}.
48+
* @param context must not be {@literal null}.
49+
* @param instantiators must not be {@literal null}.
50+
*/
51+
public DtoInstantiatingConverter(Class<?> dtoType,
52+
MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> context,
53+
EntityInstantiators instantiator) {
54+
55+
Assert.notNull(dtoType, "DTO type must not be null!");
56+
Assert.notNull(context, "MappingContext must not be null!");
57+
Assert.notNull(instantiator, "EntityInstantiators must not be null!");
58+
59+
this.targetType = dtoType;
60+
this.context = context;
61+
this.instantiator = instantiator.getInstantiatorFor(context.getRequiredPersistentEntity(dtoType));
62+
}
63+
64+
/*
65+
* (non-Javadoc)
66+
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
67+
*/
68+
@Override
69+
public Object convert(Object source) {
70+
71+
if (targetType.isInterface()) {
72+
return source;
73+
}
74+
75+
final PersistentEntity<?, ?> sourceEntity = context.getRequiredPersistentEntity(source.getClass());
76+
final PersistentPropertyAccessor sourceAccessor = sourceEntity.getPropertyAccessor(source);
77+
final PersistentEntity<?, ?> targetEntity = context.getRequiredPersistentEntity(targetType);
78+
final PreferredConstructor<?, ? extends PersistentProperty<?>> constructor = targetEntity
79+
.getPersistenceConstructor();
80+
81+
@SuppressWarnings({ "rawtypes", "unchecked" })
82+
Object dto = instantiator.createInstance(targetEntity, new ParameterValueProvider() {
83+
84+
@Override
85+
public Object getParameterValue(Parameter parameter) {
86+
return sourceAccessor.getProperty(sourceEntity.getPersistentProperty(parameter.getName()));
87+
}
88+
});
89+
90+
final PersistentPropertyAccessor dtoAccessor = targetEntity.getPropertyAccessor(dto);
91+
92+
targetEntity.doWithProperties(new SimplePropertyHandler() {
93+
94+
@Override
95+
public void doWithPersistentProperty(PersistentProperty<?> property) {
96+
97+
if (constructor.isConstructorParameter(property)) {
98+
return;
99+
}
100+
101+
dtoAccessor.setProperty(property,
102+
sourceAccessor.getProperty(sourceEntity.getPersistentProperty(property.getName())));
103+
}
104+
});
105+
106+
return dto;
107+
}
108+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import org.springframework.data.repository.core.EntityInformation;
19+
20+
/**
21+
* JDBC specific {@link EntityInformation}.
22+
*
23+
* @author Mark Paluch
24+
*/
25+
public interface JdbcEntityInformation<T, ID> extends EntityInformation<T, ID> {
26+
27+
/**
28+
* Returns the name of the table the entity shall be persisted to.
29+
*
30+
* @return
31+
*/
32+
String getTableName();
33+
}

0 commit comments

Comments
 (0)