24 changed files with 1983 additions and 42 deletions
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository; |
||||
|
||||
import org.springframework.data.repository.NoRepositoryBean; |
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository; |
||||
|
||||
/** |
||||
* R2DBC specific {@link org.springframework.data.repository.Repository} interface with reactive support. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
@NoRepositoryBean |
||||
public interface R2dbcRepository<T, ID> extends ReactiveCrudRepository<T, ID> {} |
||||
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.data.convert.EntityInstantiators; |
||||
import org.springframework.data.jdbc.core.function.DatabaseClient; |
||||
import org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec; |
||||
import org.springframework.data.jdbc.core.function.FetchSpec; |
||||
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter; |
||||
import org.springframework.data.jdbc.repository.query.R2dbcQueryExecution.ResultProcessingConverter; |
||||
import org.springframework.data.jdbc.repository.query.R2dbcQueryExecution.ResultProcessingExecution; |
||||
import org.springframework.data.repository.query.ParameterAccessor; |
||||
import org.springframework.data.repository.query.RepositoryQuery; |
||||
import org.springframework.data.repository.query.ResultProcessor; |
||||
import org.springframework.data.repository.query.ReturnedType; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Base class for reactive {@link RepositoryQuery} implementations for R2DBC. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public abstract class AbstractR2dbcQuery implements RepositoryQuery { |
||||
|
||||
private final R2dbcQueryMethod method; |
||||
private final DatabaseClient databaseClient; |
||||
private final MappingR2dbcConverter converter; |
||||
private final EntityInstantiators instantiators; |
||||
|
||||
/** |
||||
* Creates a new {@link AbstractR2dbcQuery} from the given {@link R2dbcQueryMethod} and {@link DatabaseClient}. |
||||
* |
||||
* @param method must not be {@literal null}. |
||||
* @param databaseClient must not be {@literal null}. |
||||
* @param converter must not be {@literal null}. |
||||
*/ |
||||
public AbstractR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, MappingR2dbcConverter converter) { |
||||
|
||||
Assert.notNull(method, "R2dbcQueryMethod must not be null!"); |
||||
Assert.notNull(databaseClient, "DatabaseClient must not be null!"); |
||||
Assert.notNull(converter, "MappingR2dbcConverter must not be null!"); |
||||
|
||||
this.method = method; |
||||
this.databaseClient = databaseClient; |
||||
this.converter = converter; |
||||
this.instantiators = new EntityInstantiators(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() |
||||
*/ |
||||
public R2dbcQueryMethod getQueryMethod() { |
||||
return method; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) |
||||
*/ |
||||
public Object execute(Object[] parameters) { |
||||
|
||||
return method.hasReactiveWrapperParameter() ? executeDeferred(parameters) |
||||
: execute(new JdbcParametersParameterAccessor(method, parameters)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private Object executeDeferred(Object[] parameters) { |
||||
|
||||
R2dbcParameterAccessor parameterAccessor = new R2dbcParameterAccessor(method, parameters); |
||||
|
||||
if (getQueryMethod().isCollectionQuery()) { |
||||
return Flux.defer(() -> (Publisher<Object>) execute(parameterAccessor)); |
||||
} |
||||
|
||||
return Mono.defer(() -> (Mono<Object>) execute(parameterAccessor)); |
||||
} |
||||
|
||||
private Object execute(JdbcParameterAccessor parameterAccessor) { |
||||
|
||||
// TODO: ConvertingParameterAccessor
|
||||
BindableQuery query = createQuery(parameterAccessor); |
||||
|
||||
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); |
||||
GenericExecuteSpec boundQuery = query.bind(databaseClient.execute().sql(query)); |
||||
FetchSpec<?> fetchSpec = boundQuery.as(resolveResultType(processor)).fetch(); |
||||
|
||||
String tableName = method.getEntityInformation().getTableName(); |
||||
|
||||
R2dbcQueryExecution execution = getExecution( |
||||
new ResultProcessingConverter(processor, converter.getMappingContext(), instantiators)); |
||||
|
||||
return execution.execute(fetchSpec, processor.getReturnedType().getDomainType(), tableName); |
||||
} |
||||
|
||||
private Class<?> resolveResultType(ResultProcessor resultProcessor) { |
||||
|
||||
ReturnedType returnedType = resultProcessor.getReturnedType(); |
||||
|
||||
return returnedType.isProjecting() ? returnedType.getDomainType() : returnedType.getReturnedType(); |
||||
} |
||||
|
||||
/** |
||||
* Returns the execution instance to use. |
||||
* |
||||
* @param resultProcessing must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
private R2dbcQueryExecution getExecution(Converter<Object, Object> resultProcessing) { |
||||
return new ResultProcessingExecution(getExecutionToWrap(), resultProcessing); |
||||
} |
||||
|
||||
private R2dbcQueryExecution getExecutionToWrap() { |
||||
|
||||
if (method.isCollectionQuery()) { |
||||
return (q, t, c) -> q.all(); |
||||
} |
||||
|
||||
return (q, t, c) -> q.one(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link BindableQuery} instance using the given {@link ParameterAccessor} |
||||
* |
||||
* @param accessor must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
protected abstract BindableQuery createQuery(JdbcParameterAccessor accessor); |
||||
} |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import java.util.function.Supplier; |
||||
|
||||
import org.springframework.data.jdbc.core.function.DatabaseClient.BindSpec; |
||||
|
||||
/** |
||||
* Interface declaring a query that supplies SQL and can bind parameters to a {@link BindSpec}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public interface BindableQuery extends Supplier<String> { |
||||
|
||||
/** |
||||
* Bind parameters to the {@link BindSpec query}. |
||||
* |
||||
* @param bindSpec must not be {@literal null}. |
||||
* @return the bound query object. |
||||
*/ |
||||
<T extends BindSpec<T>> T bind(T bindSpec); |
||||
} |
||||
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.data.convert.EntityInstantiator; |
||||
import org.springframework.data.convert.EntityInstantiators; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty; |
||||
import org.springframework.data.mapping.PersistentEntity; |
||||
import org.springframework.data.mapping.PersistentProperty; |
||||
import org.springframework.data.mapping.PersistentPropertyAccessor; |
||||
import org.springframework.data.mapping.PreferredConstructor; |
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter; |
||||
import org.springframework.data.mapping.SimplePropertyHandler; |
||||
import org.springframework.data.mapping.context.MappingContext; |
||||
import org.springframework.data.mapping.model.ParameterValueProvider; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* {@link Converter} to instantiate DTOs from fully equipped domain objects. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class DtoInstantiatingConverter implements Converter<Object, Object> { |
||||
|
||||
private final Class<?> targetType; |
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context; |
||||
private final EntityInstantiator instantiator; |
||||
|
||||
/** |
||||
* Creates a new {@link Converter} to instantiate DTOs. |
||||
* |
||||
* @param dtoType must not be {@literal null}. |
||||
* @param context must not be {@literal null}. |
||||
* @param instantiators must not be {@literal null}. |
||||
*/ |
||||
public DtoInstantiatingConverter(Class<?> dtoType, |
||||
MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> context, |
||||
EntityInstantiators instantiator) { |
||||
|
||||
Assert.notNull(dtoType, "DTO type must not be null!"); |
||||
Assert.notNull(context, "MappingContext must not be null!"); |
||||
Assert.notNull(instantiator, "EntityInstantiators must not be null!"); |
||||
|
||||
this.targetType = dtoType; |
||||
this.context = context; |
||||
this.instantiator = instantiator.getInstantiatorFor(context.getRequiredPersistentEntity(dtoType)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) |
||||
*/ |
||||
@Override |
||||
public Object convert(Object source) { |
||||
|
||||
if (targetType.isInterface()) { |
||||
return source; |
||||
} |
||||
|
||||
final PersistentEntity<?, ?> sourceEntity = context.getRequiredPersistentEntity(source.getClass()); |
||||
final PersistentPropertyAccessor sourceAccessor = sourceEntity.getPropertyAccessor(source); |
||||
final PersistentEntity<?, ?> targetEntity = context.getRequiredPersistentEntity(targetType); |
||||
final PreferredConstructor<?, ? extends PersistentProperty<?>> constructor = targetEntity |
||||
.getPersistenceConstructor(); |
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" }) |
||||
Object dto = instantiator.createInstance(targetEntity, new ParameterValueProvider() { |
||||
|
||||
@Override |
||||
public Object getParameterValue(Parameter parameter) { |
||||
return sourceAccessor.getProperty(sourceEntity.getPersistentProperty(parameter.getName())); |
||||
} |
||||
}); |
||||
|
||||
final PersistentPropertyAccessor dtoAccessor = targetEntity.getPropertyAccessor(dto); |
||||
|
||||
targetEntity.doWithProperties(new SimplePropertyHandler() { |
||||
|
||||
@Override |
||||
public void doWithPersistentProperty(PersistentProperty<?> property) { |
||||
|
||||
if (constructor.isConstructorParameter(property)) { |
||||
return; |
||||
} |
||||
|
||||
dtoAccessor.setProperty(property, |
||||
sourceAccessor.getProperty(sourceEntity.getPersistentProperty(property.getName()))); |
||||
} |
||||
}); |
||||
|
||||
return dto; |
||||
} |
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import org.springframework.data.repository.core.EntityInformation; |
||||
|
||||
/** |
||||
* JDBC specific {@link EntityInformation}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public interface JdbcEntityInformation<T, ID> extends EntityInformation<T, ID> { |
||||
|
||||
/** |
||||
* Returns the name of the table the entity shall be persisted to. |
||||
* |
||||
* @return |
||||
*/ |
||||
String getTableName(); |
||||
} |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; |
||||
import org.springframework.data.repository.core.EntityMetadata; |
||||
|
||||
/** |
||||
* Extension of {@link EntityMetadata} to additionally expose the collection name an entity shall be persisted to. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public interface JdbcEntityMetadata<T> extends EntityMetadata<T> { |
||||
|
||||
/** |
||||
* Returns the name of the table the entity shall be persisted to. |
||||
* |
||||
* @return |
||||
*/ |
||||
String getTableName(); |
||||
|
||||
/** |
||||
* Returns the {@link JdbcPersistentEntity} that supposed to determine the table to be queried. |
||||
* |
||||
* @return |
||||
*/ |
||||
JdbcPersistentEntity<?> getTableEntity(); |
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import org.springframework.data.repository.query.ParameterAccessor; |
||||
|
||||
/** |
||||
* JDBC-specific {@link ParameterAccessor}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public interface JdbcParameterAccessor extends ParameterAccessor { |
||||
|
||||
/** |
||||
* Returns the raw parameter values of the underlying query method. |
||||
*/ |
||||
Object[] getValues(); |
||||
} |
||||
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.data.jdbc.repository.query.JdbcParameters.JdbcParameter; |
||||
import org.springframework.data.repository.query.Parameter; |
||||
import org.springframework.data.repository.query.Parameters; |
||||
|
||||
/** |
||||
* Custom extension of {@link Parameters}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class JdbcParameters extends Parameters<JdbcParameters, JdbcParameter> { |
||||
|
||||
/** |
||||
* Creates a new {@link JdbcParameters} instance from the given {@link Method}. |
||||
* |
||||
* @param method must not be {@literal null}. |
||||
*/ |
||||
public JdbcParameters(Method method) { |
||||
super(method); |
||||
} |
||||
|
||||
private JdbcParameters(List<JdbcParameter> parameters) { |
||||
super(parameters); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.Parameters#createParameter(org.springframework.core.MethodParameter) |
||||
*/ |
||||
@Override |
||||
protected JdbcParameter createParameter(MethodParameter parameter) { |
||||
return new JdbcParameter(parameter); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.Parameters#createFrom(java.util.List) |
||||
*/ |
||||
@Override |
||||
protected JdbcParameters createFrom(List<JdbcParameter> parameters) { |
||||
return new JdbcParameters(parameters); |
||||
} |
||||
|
||||
/** |
||||
* Custom {@link Parameter} implementation. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class JdbcParameter extends Parameter { |
||||
|
||||
/** |
||||
* Creates a new {@link JdbcParameter}. |
||||
* |
||||
* @param parameter must not be {@literal null}. |
||||
*/ |
||||
JdbcParameter(MethodParameter parameter) { |
||||
super(parameter); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor; |
||||
import org.springframework.data.repository.query.QueryMethod; |
||||
|
||||
/** |
||||
* JDBC-specific {@link ParametersParameterAccessor}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class JdbcParametersParameterAccessor extends ParametersParameterAccessor implements JdbcParameterAccessor { |
||||
|
||||
private final List<Object> values; |
||||
|
||||
/** |
||||
* Creates a new {@link JdbcParametersParameterAccessor}. |
||||
* |
||||
* @param method must not be {@literal null}. |
||||
* @param values must not be {@literal null}. |
||||
*/ |
||||
public JdbcParametersParameterAccessor(QueryMethod method, Object[] values) { |
||||
|
||||
super(method.getParameters(), values); |
||||
this.values = Arrays.asList(values); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.jdbc.repository.query.JdbcParameterAccessor#getValues() |
||||
*/ |
||||
@Override |
||||
public Object[] getValues() { |
||||
return values.toArray(); |
||||
} |
||||
} |
||||
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
import reactor.core.publisher.MonoProcessor; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters; |
||||
import org.springframework.data.repository.util.ReactiveWrappers; |
||||
|
||||
/** |
||||
* Reactive {@link org.springframework.data.repository.query.ParametersParameterAccessor} implementation that subscribes |
||||
* to reactive parameter wrapper types upon creation. This class performs synchronization when accessing parameters. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class R2dbcParameterAccessor extends JdbcParametersParameterAccessor { |
||||
|
||||
private final Object[] values; |
||||
private final List<MonoProcessor<?>> subscriptions; |
||||
|
||||
/** |
||||
* Creates a new {@link R2dbcParameterAccessor}. |
||||
*/ |
||||
public R2dbcParameterAccessor(R2dbcQueryMethod method, Object... values) { |
||||
|
||||
super(method, values); |
||||
|
||||
this.values = values; |
||||
this.subscriptions = new ArrayList<>(values.length); |
||||
|
||||
for (int i = 0; i < values.length; i++) { |
||||
|
||||
Object value = values[i]; |
||||
|
||||
if (value == null || !ReactiveWrappers.supports(value.getClass())) { |
||||
subscriptions.add(null); |
||||
continue; |
||||
} |
||||
|
||||
if (ReactiveWrappers.isSingleValueType(value.getClass())) { |
||||
subscriptions.add(ReactiveWrapperConverters.toWrapper(value, Mono.class).toProcessor()); |
||||
} else { |
||||
subscriptions.add(ReactiveWrapperConverters.toWrapper(value, Flux.class).collectList().toProcessor()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.ParametersParameterAccessor#getValue(int) |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
protected <T> T getValue(int index) { |
||||
|
||||
if (subscriptions.get(index) != null) { |
||||
return (T) subscriptions.get(index).block(); |
||||
} |
||||
|
||||
return super.getValue(index); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.jdbc.repository.query.JdbcParametersParameterAccessor#getValues() |
||||
*/ |
||||
@Override |
||||
public Object[] getValues() { |
||||
|
||||
Object[] result = new Object[values.length]; |
||||
for (int i = 0; i < result.length; i++) { |
||||
result[i] = getValue(i); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.ParametersParameterAccessor#getBindableValue(int) |
||||
*/ |
||||
public Object getBindableValue(int index) { |
||||
return getValue(getParameters().getBindableParameter(index).getIndex()); |
||||
} |
||||
} |
||||
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import lombok.NonNull; |
||||
import lombok.RequiredArgsConstructor; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.data.convert.EntityInstantiators; |
||||
import org.springframework.data.jdbc.core.function.FetchSpec; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty; |
||||
import org.springframework.data.mapping.context.MappingContext; |
||||
import org.springframework.data.repository.query.ResultProcessor; |
||||
import org.springframework.data.repository.query.ReturnedType; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* Set of classes to contain query execution strategies. Depending (mostly) on the return type of a |
||||
* {@link org.springframework.data.repository.query.QueryMethod}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
interface R2dbcQueryExecution { |
||||
|
||||
Object execute(FetchSpec<?> query, Class<?> type, String tableName); |
||||
|
||||
/** |
||||
* An {@link R2dbcQueryExecution} that wraps the results of the given delegate with the given result processing. |
||||
*/ |
||||
@RequiredArgsConstructor |
||||
final class ResultProcessingExecution implements R2dbcQueryExecution { |
||||
|
||||
private final @NonNull R2dbcQueryExecution delegate; |
||||
private final @NonNull Converter<Object, Object> converter; |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.jdbc.repository.query.R2dbcQueryExecution#execute(org.springframework.data.jdbc.core.function.FetchSpec, java.lang.Class, java.lang.String) |
||||
*/ |
||||
@Override |
||||
public Object execute(FetchSpec<?> query, Class<?> type, String tableName) { |
||||
return converter.convert(delegate.execute(query, type, tableName)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}. |
||||
*/ |
||||
@RequiredArgsConstructor |
||||
final class ResultProcessingConverter implements Converter<Object, Object> { |
||||
|
||||
private final @NonNull ResultProcessor processor; |
||||
private final @NonNull MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext; |
||||
private final @NonNull EntityInstantiators instantiators; |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) |
||||
*/ |
||||
@Override |
||||
public Object convert(Object source) { |
||||
|
||||
ReturnedType returnedType = processor.getReturnedType(); |
||||
|
||||
if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) { |
||||
return source; |
||||
} |
||||
|
||||
Converter<Object, Object> converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), |
||||
mappingContext, instantiators); |
||||
|
||||
return processor.processResult(source, converter); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,227 @@
@@ -0,0 +1,227 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import static org.springframework.data.repository.util.ClassUtils.*; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils; |
||||
import org.springframework.dao.InvalidDataAccessApiUsageException; |
||||
import org.springframework.data.domain.Page; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Slice; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty; |
||||
import org.springframework.data.jdbc.repository.query.JdbcParameters.JdbcParameter; |
||||
import org.springframework.data.mapping.context.MappingContext; |
||||
import org.springframework.data.projection.ProjectionFactory; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.query.QueryMethod; |
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters; |
||||
import org.springframework.data.repository.util.ReactiveWrappers; |
||||
import org.springframework.data.util.ClassTypeInformation; |
||||
import org.springframework.data.util.TypeInformation; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* Reactive specific implementation of {@link QueryMethod}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class R2dbcQueryMethod extends QueryMethod { |
||||
|
||||
private static final ClassTypeInformation<Page> PAGE_TYPE = ClassTypeInformation.from(Page.class); |
||||
private static final ClassTypeInformation<Slice> SLICE_TYPE = ClassTypeInformation.from(Slice.class); |
||||
|
||||
private final Method method; |
||||
private final MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext; |
||||
private final Optional<Query> query; |
||||
|
||||
private @Nullable JdbcEntityMetadata<?> metadata; |
||||
|
||||
/** |
||||
* Creates a new {@link R2dbcQueryMethod} from the given {@link Method}. |
||||
* |
||||
* @param method must not be {@literal null}. |
||||
* @param metadata must not be {@literal null}. |
||||
* @param projectionFactory must not be {@literal null}. |
||||
* @param mappingContext must not be {@literal null}. |
||||
*/ |
||||
public R2dbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory projectionFactory, |
||||
MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext) { |
||||
|
||||
super(method, metadata, projectionFactory); |
||||
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null!"); |
||||
|
||||
this.mappingContext = mappingContext; |
||||
|
||||
if (hasParameterOfType(method, Pageable.class)) { |
||||
|
||||
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method); |
||||
|
||||
boolean multiWrapper = ReactiveWrappers.isMultiValueType(returnType.getType()); |
||||
boolean singleWrapperWithWrappedPageableResult = ReactiveWrappers.isSingleValueType(returnType.getType()) |
||||
&& (PAGE_TYPE.isAssignableFrom(returnType.getRequiredComponentType()) |
||||
|| SLICE_TYPE.isAssignableFrom(returnType.getRequiredComponentType())); |
||||
|
||||
if (singleWrapperWithWrappedPageableResult) { |
||||
throw new InvalidDataAccessApiUsageException( |
||||
String.format("'%s.%s' must not use sliced or paged execution. Please use Flux.buffer(size, skip).", |
||||
ClassUtils.getShortName(method.getDeclaringClass()), method.getName())); |
||||
} |
||||
|
||||
if (!multiWrapper) { |
||||
throw new IllegalStateException(String.format( |
||||
"Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: %s", |
||||
method.toString())); |
||||
} |
||||
|
||||
if (hasParameterOfType(method, Sort.class)) { |
||||
throw new IllegalStateException(String.format("Method must not have Pageable *and* Sort parameter. " |
||||
+ "Use sorting capabilities on Pageble instead! Offending method: %s", method.toString())); |
||||
} |
||||
} |
||||
|
||||
this.method = method; |
||||
this.query = Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Query.class)); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.QueryMethod#createParameters(java.lang.reflect.Method) |
||||
*/ |
||||
@Override |
||||
protected JdbcParameters createParameters(Method method) { |
||||
return new JdbcParameters(method); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.QueryMethod#isCollectionQuery() |
||||
*/ |
||||
@Override |
||||
public boolean isCollectionQuery() { |
||||
return !(isPageQuery() || isSliceQuery()) && ReactiveWrappers.isMultiValueType(method.getReturnType()); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.QueryMethod#isModifyingQuery() |
||||
*/ |
||||
@Override |
||||
public boolean isModifyingQuery() { |
||||
return super.isModifyingQuery(); |
||||
} |
||||
|
||||
/* |
||||
* All reactive query methods are streaming queries. |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.QueryMethod#isStreamQuery() |
||||
*/ |
||||
@Override |
||||
public boolean isStreamQuery() { |
||||
return true; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.QueryMethod#getEntityInformation() |
||||
*/ |
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public JdbcEntityMetadata<?> getEntityInformation() { |
||||
|
||||
if (metadata == null) { |
||||
|
||||
Class<?> returnedObjectType = getReturnedObjectType(); |
||||
Class<?> domainClass = getDomainClass(); |
||||
|
||||
if (ClassUtils.isPrimitiveOrWrapper(returnedObjectType)) { |
||||
|
||||
this.metadata = new SimpleJdbcEntityMetadata<>((Class<Object>) domainClass, |
||||
mappingContext.getRequiredPersistentEntity(domainClass)); |
||||
|
||||
} else { |
||||
|
||||
JdbcPersistentEntity<?> returnedEntity = mappingContext.getPersistentEntity(returnedObjectType); |
||||
JdbcPersistentEntity<?> managedEntity = mappingContext.getRequiredPersistentEntity(domainClass); |
||||
returnedEntity = returnedEntity == null || returnedEntity.getType().isInterface() ? managedEntity |
||||
: returnedEntity; |
||||
JdbcPersistentEntity<?> tableEntity = domainClass.isAssignableFrom(returnedObjectType) ? returnedEntity |
||||
: managedEntity; |
||||
|
||||
this.metadata = new SimpleJdbcEntityMetadata<>((Class<Object>) returnedEntity.getType(), tableEntity); |
||||
} |
||||
} |
||||
|
||||
return this.metadata; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.QueryMethod#getParameters() |
||||
*/ |
||||
@Override |
||||
public JdbcParameters getParameters() { |
||||
return (JdbcParameters) super.getParameters(); |
||||
} |
||||
|
||||
/** |
||||
* Check if the given {@link org.springframework.data.repository.query.QueryMethod} receives a reactive parameter |
||||
* wrapper as one of its parameters. |
||||
* |
||||
* @return {@literal true} if the given {@link org.springframework.data.repository.query.QueryMethod} receives a |
||||
* reactive parameter wrapper as one of its parameters. |
||||
*/ |
||||
public boolean hasReactiveWrapperParameter() { |
||||
|
||||
for (JdbcParameter parameter : getParameters()) { |
||||
if (ReactiveWrapperConverters.supports(parameter.getType())) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Returns the required query string declared in a {@link Query} annotation or throws {@link IllegalStateException} if |
||||
* neither the annotation found nor the attribute was specified. |
||||
* |
||||
* @return the query string. |
||||
* @throws IllegalStateException in case query method has no annotated query. |
||||
*/ |
||||
public String getRequiredAnnotatedQuery() { |
||||
return this.query.map(Query::value) |
||||
.orElseThrow(() -> new IllegalStateException("Query method " + this + " has no annotated query")); |
||||
} |
||||
|
||||
/** |
||||
* Returns the {@link Query} annotation that is applied to the method or {@literal null} if none available. |
||||
* |
||||
* @return the optional query annotation. |
||||
*/ |
||||
Optional<Query> getQueryAnnotation() { |
||||
return this.query; |
||||
} |
||||
|
||||
/** |
||||
* @return {@literal true} if the {@link Method} is annotated with {@link Query}. |
||||
*/ |
||||
public boolean hasAnnotatedQuery() { |
||||
return getQueryAnnotation().isPresent(); |
||||
} |
||||
} |
||||
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import lombok.Getter; |
||||
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Default implementation of {@link JdbcEntityMetadata}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
class SimpleJdbcEntityMetadata<T> implements JdbcEntityMetadata<T> { |
||||
|
||||
private final Class<T> type; |
||||
private final @Getter JdbcPersistentEntity<?> tableEntity; |
||||
|
||||
/** |
||||
* Creates a new {@link SimpleJdbcEntityMetadata} using the given type and {@link JdbcPersistentEntity} to use for |
||||
* table lookups. |
||||
* |
||||
* @param type must not be {@literal null}. |
||||
* @param tableEntity must not be {@literal null}. |
||||
*/ |
||||
SimpleJdbcEntityMetadata(Class<T> type, JdbcPersistentEntity<?> tableEntity) { |
||||
|
||||
Assert.notNull(type, "Type must not be null!"); |
||||
Assert.notNull(tableEntity, "Table entity must not be null!"); |
||||
|
||||
this.type = type; |
||||
this.tableEntity = tableEntity; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.core.EntityMetadata#getJavaType() |
||||
*/ |
||||
public Class<T> getJavaType() { |
||||
return type; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.jdbc.repository.query.JdbcEntityMetadata#getTableName() |
||||
*/ |
||||
public String getTableName() { |
||||
return tableEntity.getTableName(); |
||||
} |
||||
} |
||||
@ -0,0 +1,110 @@
@@ -0,0 +1,110 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import org.springframework.data.jdbc.core.function.DatabaseClient; |
||||
import org.springframework.data.jdbc.core.function.DatabaseClient.BindSpec; |
||||
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter; |
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; |
||||
import org.springframework.expression.spel.standard.SpelExpressionParser; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* String-based {@link StringBasedR2dbcQuery} implementation. |
||||
* <p> |
||||
* A {@link StringBasedR2dbcQuery} expects a query method to be annotated with {@link Query} with a SQL query. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { |
||||
|
||||
private final String sql; |
||||
|
||||
/** |
||||
* Creates a new {@link StringBasedR2dbcQuery} for the given {@link StringBasedR2dbcQuery}, {@link DatabaseClient}, |
||||
* {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}. |
||||
* |
||||
* @param queryMethod must not be {@literal null}. |
||||
* @param databaseClient must not be {@literal null}. |
||||
* @param converter must not be {@literal null}. |
||||
* @param expressionParser must not be {@literal null}. |
||||
* @param evaluationContextProvider must not be {@literal null}. |
||||
*/ |
||||
public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, DatabaseClient databaseClient, |
||||
MappingR2dbcConverter converter, SpelExpressionParser expressionParser, |
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) { |
||||
|
||||
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, databaseClient, converter, expressionParser, |
||||
evaluationContextProvider); |
||||
} |
||||
|
||||
/** |
||||
* Create a new {@link StringBasedR2dbcQuery} for the given {@code query}, {@link R2dbcQueryMethod}, |
||||
* {@link DatabaseClient}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}. |
||||
* |
||||
* @param method must not be {@literal null}. |
||||
* @param databaseClient must not be {@literal null}. |
||||
* @param converter must not be {@literal null}. |
||||
* @param expressionParser must not be {@literal null}. |
||||
* @param evaluationContextProvider must not be {@literal null}. |
||||
*/ |
||||
public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, DatabaseClient databaseClient, |
||||
MappingR2dbcConverter converter, SpelExpressionParser expressionParser, |
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) { |
||||
|
||||
super(method, databaseClient, converter); |
||||
|
||||
Assert.hasText(query, "Query must not be empty"); |
||||
|
||||
this.sql = query; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.jdbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.jdbc.repository.query.JdbcParameterAccessor) |
||||
*/ |
||||
@Override |
||||
protected BindableQuery createQuery(JdbcParameterAccessor accessor) { |
||||
|
||||
return new BindableQuery() { |
||||
|
||||
@Override |
||||
public <T extends BindSpec<T>> T bind(T bindSpec) { |
||||
|
||||
T bindSpecToUse = bindSpec; |
||||
|
||||
// TODO: Encapsulate PostgreSQL-specific bindings
|
||||
int index = 1; |
||||
for (Object value : accessor.getValues()) { |
||||
|
||||
if (value == null) { |
||||
if (accessor.hasBindableNullValue()) { |
||||
bindSpecToUse = bindSpecToUse.bindNull("$" + (index++)); |
||||
} |
||||
} else { |
||||
bindSpecToUse = bindSpecToUse.bind("$" + (index++), value); |
||||
} |
||||
} |
||||
|
||||
return bindSpecToUse; |
||||
} |
||||
|
||||
@Override |
||||
public String get() { |
||||
return sql; |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.support; |
||||
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; |
||||
import org.springframework.data.jdbc.repository.query.JdbcEntityInformation; |
||||
import org.springframework.data.repository.core.support.PersistentEntityInformation; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
import com.sun.corba.se.spi.ior.ObjectId; |
||||
|
||||
/** |
||||
* {@link JdbcEntityInformation} implementation using a {@link JdbcPersistentEntity} instance to lookup the necessary |
||||
* information. Can be configured with a custom table name. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class MappingJdbcEntityInformation<T, ID> extends PersistentEntityInformation<T, ID> |
||||
implements JdbcEntityInformation<T, ID> { |
||||
|
||||
private final JdbcPersistentEntity<T> entityMetadata; |
||||
private final @Nullable String customTableName; |
||||
private final Class<ID> fallbackIdType; |
||||
|
||||
/** |
||||
* Creates a new {@link MappingJdbcEntityInformation} for the given {@link JdbcPersistentEntity}. |
||||
* |
||||
* @param entity must not be {@literal null}. |
||||
*/ |
||||
public MappingJdbcEntityInformation(JdbcPersistentEntity<T> entity) { |
||||
this(entity, null, null); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@link MappingJdbcEntityInformation} for the given {@link JdbcPersistentEntity} and fallback |
||||
* identifier type. |
||||
* |
||||
* @param entity must not be {@literal null}. |
||||
* @param fallbackIdType can be {@literal null}. |
||||
*/ |
||||
public MappingJdbcEntityInformation(JdbcPersistentEntity<T> entity, @Nullable Class<ID> fallbackIdType) { |
||||
this(entity, null, fallbackIdType); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@link MappingJdbcEntityInformation} for the given {@link JdbcPersistentEntity} and custom table |
||||
* name. |
||||
* |
||||
* @param entity must not be {@literal null}. |
||||
* @param customTableName can be {@literal null}. |
||||
*/ |
||||
public MappingJdbcEntityInformation(JdbcPersistentEntity<T> entity, String customTableName) { |
||||
this(entity, customTableName, null); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@link MappingJdbcEntityInformation} for the given {@link JdbcPersistentEntity}, collection name and |
||||
* identifier type. |
||||
* |
||||
* @param entity must not be {@literal null}. |
||||
* @param customTableName can be {@literal null}. |
||||
* @param idType can be {@literal null}. |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
private MappingJdbcEntityInformation(JdbcPersistentEntity<T> entity, @Nullable String customTableName, |
||||
@Nullable Class<ID> idType) { |
||||
|
||||
super(entity); |
||||
|
||||
this.entityMetadata = entity; |
||||
this.customTableName = customTableName; |
||||
this.fallbackIdType = idType != null ? idType : (Class<ID>) ObjectId.class; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.jdbc.repository.query.JdbcEntityInformation#getTableName() |
||||
*/ |
||||
public String getTableName() { |
||||
return customTableName == null ? entityMetadata.getTableName() : customTableName; |
||||
} |
||||
|
||||
public String getIdAttribute() { |
||||
return entityMetadata.getRequiredIdProperty().getName(); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.repository.core.support.PersistentEntityInformation#getIdType() |
||||
*/ |
||||
@Override |
||||
public Class<ID> getIdType() { |
||||
|
||||
if (this.entityMetadata.hasIdProperty()) { |
||||
return super.getIdType(); |
||||
} |
||||
|
||||
return fallbackIdType; |
||||
} |
||||
} |
||||
@ -0,0 +1,159 @@
@@ -0,0 +1,159 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.support; |
||||
|
||||
import lombok.AccessLevel; |
||||
import lombok.RequiredArgsConstructor; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.data.jdbc.core.function.DatabaseClient; |
||||
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty; |
||||
import org.springframework.data.jdbc.repository.query.JdbcEntityInformation; |
||||
import org.springframework.data.jdbc.repository.query.R2dbcQueryMethod; |
||||
import org.springframework.data.jdbc.repository.query.StringBasedR2dbcQuery; |
||||
import org.springframework.data.mapping.context.MappingContext; |
||||
import org.springframework.data.projection.ProjectionFactory; |
||||
import org.springframework.data.repository.core.NamedQueries; |
||||
import org.springframework.data.repository.core.RepositoryInformation; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; |
||||
import org.springframework.data.repository.query.QueryLookupStrategy; |
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key; |
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; |
||||
import org.springframework.data.repository.query.RepositoryQuery; |
||||
import org.springframework.expression.spel.standard.SpelExpressionParser; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Factory to create {@link org.springframework.data.jdbc.repository.R2dbcRepository} instances. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { |
||||
|
||||
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); |
||||
|
||||
private final DatabaseClient databaseClient; |
||||
private final MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext; |
||||
private final MappingR2dbcConverter converter; |
||||
|
||||
/** |
||||
* Creates a new {@link R2dbcRepositoryFactory} given {@link DatabaseClient} and {@link MappingContext}. |
||||
* |
||||
* @param databaseClient must not be {@literal null}. |
||||
* @param mappingContext must not be {@literal null}. |
||||
*/ |
||||
public R2dbcRepositoryFactory(DatabaseClient databaseClient, |
||||
MappingContext<? extends JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext) { |
||||
|
||||
Assert.notNull(databaseClient, "DatabaseClient must not be null!"); |
||||
Assert.notNull(mappingContext, "MappingContext must not be null!"); |
||||
|
||||
this.databaseClient = databaseClient; |
||||
this.mappingContext = mappingContext; |
||||
this.converter = new MappingR2dbcConverter(mappingContext); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata) |
||||
*/ |
||||
@Override |
||||
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { |
||||
return SimpleR2dbcRepository.class; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryInformation) |
||||
*/ |
||||
@Override |
||||
protected Object getTargetRepository(RepositoryInformation information) { |
||||
|
||||
JdbcEntityInformation<?, ?> entityInformation = getEntityInformation(information.getDomainType(), information); |
||||
|
||||
return getTargetRepositoryViaReflection(information, entityInformation, databaseClient, converter); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider) |
||||
*/ |
||||
@Override |
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key, |
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) { |
||||
return Optional.of(new R2dbcQueryLookupStrategy(databaseClient, evaluationContextProvider, converter)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class) |
||||
*/ |
||||
public <T, ID> JdbcEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) { |
||||
return getEntityInformation(domainClass, null); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private <T, ID> JdbcEntityInformation<T, ID> getEntityInformation(Class<T> domainClass, |
||||
@Nullable RepositoryInformation information) { |
||||
|
||||
JdbcPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(domainClass); |
||||
|
||||
return new MappingJdbcEntityInformation<>((JdbcPersistentEntity<T>) entity); |
||||
} |
||||
|
||||
/** |
||||
* {@link QueryLookupStrategy} to create R2DBC queries.. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE) |
||||
private static class R2dbcQueryLookupStrategy implements QueryLookupStrategy { |
||||
|
||||
private final DatabaseClient databaseClient; |
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider; |
||||
private final MappingR2dbcConverter converter; |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries) |
||||
*/ |
||||
@Override |
||||
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, |
||||
NamedQueries namedQueries) { |
||||
|
||||
R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory, converter.getMappingContext()); |
||||
String namedQueryName = queryMethod.getNamedQueryName(); |
||||
|
||||
if (namedQueries.hasQuery(namedQueryName)) { |
||||
String namedQuery = namedQueries.getQuery(namedQueryName); |
||||
return new StringBasedR2dbcQuery(namedQuery, queryMethod, databaseClient, converter, EXPRESSION_PARSER, |
||||
evaluationContextProvider); |
||||
} else if (queryMethod.hasAnnotatedQuery()) { |
||||
return new StringBasedR2dbcQuery(queryMethod, databaseClient, converter, EXPRESSION_PARSER, |
||||
evaluationContextProvider); |
||||
} |
||||
|
||||
throw new UnsupportedOperationException("Query derivation not yet supported!"); |
||||
|
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
/* |
||||
* Copyright 2017-2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.degraph; |
||||
|
||||
import static de.schauderhaft.degraph.check.JCheck.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import de.schauderhaft.degraph.check.JCheck; |
||||
import scala.runtime.AbstractFunction1; |
||||
|
||||
import org.junit.Ignore; |
||||
import org.junit.Test; |
||||
|
||||
/** |
||||
* Test package dependencies for violations. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class DependencyTests { |
||||
|
||||
@Test // DATAJDBC-114
|
||||
public void cycleFree() { |
||||
|
||||
assertThat( //
|
||||
classpath() //
|
||||
.noJars() //
|
||||
.including("org.springframework.data.jdbc.**") //
|
||||
.filterClasspath("*target/classes") // exclude test code
|
||||
.printOnFailure("degraph.graphml"), |
||||
JCheck.violationFree()); |
||||
} |
||||
|
||||
@Test // DATAJDBC-220
|
||||
@Ignore("I don't understand why this fails after adding reactive repos - mp911de") |
||||
public void acrossModules() { |
||||
|
||||
assertThat( //
|
||||
classpath() //
|
||||
// include only Spring Data related classes (for example no JDK code)
|
||||
.including("org.springframework.data.**") //
|
||||
.filterClasspath(new AbstractFunction1<String, Object>() { |
||||
@Override |
||||
public Object apply(String s) { //
|
||||
// only the current module + commons
|
||||
return s.endsWith("target/classes") || s.contains("spring-data-commons"); |
||||
} |
||||
}) // exclude test code
|
||||
.withSlicing("sub-modules", // sub-modules are defined by any of the following pattern.
|
||||
"org.springframework.data.jdbc.(**).*", //
|
||||
"org.springframework.data.(**).*") //
|
||||
.printTo("degraph-across-modules.graphml"), // writes a graphml to this location
|
||||
JCheck.violationFree()); |
||||
} |
||||
} |
||||
@ -0,0 +1,157 @@
@@ -0,0 +1,157 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import io.r2dbc.spi.ConnectionFactory; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Hooks; |
||||
import reactor.core.publisher.Mono; |
||||
import reactor.test.StepVerifier; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.data.annotation.Id; |
||||
import org.springframework.data.convert.EntityInstantiators; |
||||
import org.springframework.data.jdbc.core.function.DatabaseClient; |
||||
import org.springframework.data.jdbc.core.function.DefaultReactiveDataAccessStrategy; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; |
||||
import org.springframework.data.jdbc.core.mapping.Table; |
||||
import org.springframework.data.jdbc.repository.query.Query; |
||||
import org.springframework.data.jdbc.repository.support.R2dbcRepositoryFactory; |
||||
import org.springframework.data.jdbc.testing.R2dbcIntegrationTestSupport; |
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository; |
||||
import org.springframework.jdbc.core.JdbcTemplate; |
||||
|
||||
/** |
||||
* Integration tests for {@link LegoSetRepository} using {@link R2dbcRepositoryFactory}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class R2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestSupport { |
||||
|
||||
private static JdbcMappingContext mappingContext = new JdbcMappingContext(); |
||||
|
||||
private ConnectionFactory connectionFactory; |
||||
private DatabaseClient databaseClient; |
||||
private LegoSetRepository repository; |
||||
private JdbcTemplate jdbc; |
||||
|
||||
@Before |
||||
public void before() { |
||||
|
||||
Hooks.onOperatorDebug(); |
||||
|
||||
this.connectionFactory = createConnectionFactory(); |
||||
this.databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory) |
||||
.dataAccessStrategy(new DefaultReactiveDataAccessStrategy(mappingContext, new EntityInstantiators())).build(); |
||||
|
||||
this.jdbc = createJdbcTemplate(createDataSource()); |
||||
|
||||
String tableToCreate = "CREATE TABLE IF NOT EXISTS repo_legoset (\n" + " id SERIAL PRIMARY KEY,\n" |
||||
+ " name varchar(255) NOT NULL,\n" + " manual integer NULL\n" + ");"; |
||||
|
||||
this.jdbc.execute("DROP TABLE IF EXISTS repo_legoset"); |
||||
this.jdbc.execute(tableToCreate); |
||||
|
||||
this.repository = new R2dbcRepositoryFactory(databaseClient, mappingContext).getRepository(LegoSetRepository.class); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldInsertNewItems() { |
||||
|
||||
LegoSet legoSet1 = new LegoSet(null, "SCHAUFELRADBAGGER", 12); |
||||
LegoSet legoSet2 = new LegoSet(null, "FORSCHUNGSSCHIFF", 13); |
||||
|
||||
repository.saveAll(Arrays.asList(legoSet1, legoSet2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFindItemsByManual() { |
||||
|
||||
shouldInsertNewItems(); |
||||
|
||||
repository.findByManual(13) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> { |
||||
assertThat(actual.getName()).isEqualTo("FORSCHUNGSSCHIFF"); |
||||
}) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFindItemsByNameLike() { |
||||
|
||||
shouldInsertNewItems(); |
||||
|
||||
repository.findByNameContains("%F%") //
|
||||
.map(LegoSet::getName) //
|
||||
.collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> { |
||||
assertThat(actual).contains("SCHAUFELRADBAGGER", "FORSCHUNGSSCHIFF"); |
||||
}).verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFindApplyingProjection() { |
||||
|
||||
shouldInsertNewItems(); |
||||
|
||||
repository.findAsProjection() //
|
||||
.map(Named::getName) //
|
||||
.collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> { |
||||
assertThat(actual).contains("SCHAUFELRADBAGGER", "FORSCHUNGSSCHIFF"); |
||||
}).verifyComplete(); |
||||
} |
||||
|
||||
interface LegoSetRepository extends ReactiveCrudRepository<LegoSet, Integer> { |
||||
|
||||
@Query("SELECT * FROM repo_legoset WHERE name like $1") |
||||
Flux<LegoSet> findByNameContains(String name); |
||||
|
||||
@Query("SELECT * FROM repo_legoset") |
||||
Flux<Named> findAsProjection(); |
||||
|
||||
@Query("SELECT * FROM repo_legoset WHERE manual = $1") |
||||
Mono<LegoSet> findByManual(int manual); |
||||
} |
||||
|
||||
@Data |
||||
@Table("repo_legoset") |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
static class LegoSet { |
||||
@Id Integer id; |
||||
String name; |
||||
Integer manual; |
||||
} |
||||
|
||||
interface Named { |
||||
String getName(); |
||||
} |
||||
} |
||||
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.util.List; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.dao.InvalidDataAccessApiUsageException; |
||||
import org.springframework.data.domain.Page; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Slice; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; |
||||
import org.springframework.data.projection.ProjectionFactory; |
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory; |
||||
import org.springframework.data.repository.Repository; |
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; |
||||
|
||||
/** |
||||
* Unit test for {@link R2dbcQueryMethod}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class R2dbcQueryMethodUnitTests { |
||||
|
||||
JdbcMappingContext context; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
context = new JdbcMappingContext(); |
||||
} |
||||
|
||||
@Test |
||||
public void detectsCollectionFromReturnTypeIfReturnTypeAssignable() throws Exception { |
||||
|
||||
R2dbcQueryMethod queryMethod = queryMethod(SampleRepository.class, "method"); |
||||
JdbcEntityMetadata<?> metadata = queryMethod.getEntityInformation(); |
||||
|
||||
assertThat(metadata.getJavaType()).isAssignableFrom(Contact.class); |
||||
assertThat(metadata.getTableName()).isEqualTo("contact"); |
||||
} |
||||
|
||||
@Test |
||||
public void detectsTableNameFromRepoTypeIfReturnTypeNotAssignable() throws Exception { |
||||
|
||||
R2dbcQueryMethod queryMethod = queryMethod(SampleRepository.class, "differentTable"); |
||||
JdbcEntityMetadata<?> metadata = queryMethod.getEntityInformation(); |
||||
|
||||
assertThat(metadata.getJavaType()).isAssignableFrom(Address.class); |
||||
assertThat(metadata.getTableName()).isEqualTo("contact"); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsNullMappingContext() throws Exception { |
||||
|
||||
Method method = PersonRepository.class.getMethod("findMonoByLastname", String.class, Pageable.class); |
||||
|
||||
new R2dbcQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), |
||||
new SpelAwareProxyProjectionFactory(), null); |
||||
} |
||||
|
||||
@Test(expected = IllegalStateException.class) |
||||
public void rejectsMonoPageableResult() throws Exception { |
||||
queryMethod(PersonRepository.class, "findMonoByLastname", String.class, Pageable.class); |
||||
} |
||||
|
||||
@Test |
||||
public void createsQueryMethodObjectForMethodReturningAnInterface() throws Exception { |
||||
queryMethod(SampleRepository.class, "methodReturningAnInterface"); |
||||
} |
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class) |
||||
public void throwsExceptionOnWrappedPage() throws Exception { |
||||
queryMethod(PersonRepository.class, "findMonoPageByLastname", String.class, Pageable.class); |
||||
} |
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class) |
||||
public void throwsExceptionOnWrappedSlice() throws Exception { |
||||
queryMethod(PersonRepository.class, "findMonoSliceByLastname", String.class, Pageable.class); |
||||
} |
||||
|
||||
@Test |
||||
public void fallsBackToRepositoryDomainTypeIfMethodDoesNotReturnADomainType() throws Exception { |
||||
|
||||
R2dbcQueryMethod method = queryMethod(PersonRepository.class, "deleteByUserName", String.class); |
||||
|
||||
assertThat(method.getEntityInformation().getJavaType()).isAssignableFrom(Contact.class); |
||||
} |
||||
|
||||
private R2dbcQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters) throws Exception { |
||||
|
||||
Method method = repository.getMethod(name, parameters); |
||||
ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); |
||||
return new R2dbcQueryMethod(method, new DefaultRepositoryMetadata(repository), factory, context); |
||||
} |
||||
|
||||
interface PersonRepository extends Repository<Contact, Long> { |
||||
|
||||
Mono<Contact> findMonoByLastname(String lastname, Pageable pageRequest); |
||||
|
||||
Mono<Page<Contact>> findMonoPageByLastname(String lastname, Pageable pageRequest); |
||||
|
||||
Mono<Slice<Contact>> findMonoSliceByLastname(String lastname, Pageable pageRequest); |
||||
|
||||
void deleteByUserName(String userName); |
||||
} |
||||
|
||||
interface SampleRepository extends Repository<Contact, Long> { |
||||
|
||||
List<Contact> method(); |
||||
|
||||
List<Address> differentTable(); |
||||
|
||||
Customer methodReturningAnInterface(); |
||||
} |
||||
|
||||
interface Customer {} |
||||
|
||||
static class Contact {} |
||||
|
||||
static class Address {} |
||||
} |
||||
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.query; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
import org.springframework.data.jdbc.core.function.DatabaseClient; |
||||
import org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec; |
||||
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; |
||||
import org.springframework.data.projection.ProjectionFactory; |
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory; |
||||
import org.springframework.data.repository.Repository; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; |
||||
import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider; |
||||
import org.springframework.expression.spel.standard.SpelExpressionParser; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* Unit tests for {@link StringBasedR2dbcQuery}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class StringBasedR2dbcQueryUnitTests { |
||||
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser(); |
||||
|
||||
@Mock private DatabaseClient databaseClient; |
||||
@Mock private GenericExecuteSpec bindSpec; |
||||
|
||||
private JdbcMappingContext mappingContext; |
||||
private MappingR2dbcConverter converter; |
||||
private ProjectionFactory factory; |
||||
private RepositoryMetadata metadata; |
||||
|
||||
@Before |
||||
@SuppressWarnings("unchecked") |
||||
public void setUp() { |
||||
|
||||
this.mappingContext = new JdbcMappingContext(); |
||||
this.converter = new MappingR2dbcConverter(this.mappingContext); |
||||
this.metadata = AbstractRepositoryMetadata.getMetadata(SampleRepository.class); |
||||
this.factory = new SpelAwareProxyProjectionFactory(); |
||||
|
||||
when(bindSpec.bind(anyString(), any())).thenReturn(bindSpec); |
||||
} |
||||
|
||||
@Test |
||||
public void bindsSimplePropertyCorrectly() { |
||||
|
||||
StringBasedR2dbcQuery query = getQueryMethod("findByLastname", String.class); |
||||
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); |
||||
|
||||
BindableQuery stringQuery = query.createQuery(accessor); |
||||
|
||||
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); |
||||
assertThat(stringQuery.bind(bindSpec)).isNotNull(); |
||||
|
||||
verify(bindSpec).bind("$1", "White"); |
||||
} |
||||
|
||||
private StringBasedR2dbcQuery getQueryMethod(String name, Class<?>... args) { |
||||
|
||||
Method method = ReflectionUtils.findMethod(SampleRepository.class, name, args); |
||||
|
||||
R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory, converter.getMappingContext()); |
||||
|
||||
return new StringBasedR2dbcQuery(queryMethod, databaseClient, converter, PARSER, |
||||
ExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
private interface SampleRepository extends Repository<Person, String> { |
||||
|
||||
@Query("SELECT * FROM person WHERE lastname = $1") |
||||
Person findByLastname(String lastname); |
||||
} |
||||
|
||||
static class Person { |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.repository.support; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
import org.springframework.data.jdbc.core.function.DatabaseClient; |
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; |
||||
import org.springframework.data.jdbc.repository.query.JdbcEntityInformation; |
||||
import org.springframework.data.mapping.context.MappingContext; |
||||
import org.springframework.data.repository.Repository; |
||||
|
||||
/** |
||||
* Unit test for {@link R2dbcRepositoryFactory}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class R2dbcRepositoryFactoryUnitTests { |
||||
|
||||
@Mock DatabaseClient databaseClient; |
||||
@Mock @SuppressWarnings("rawtypes") MappingContext mappingContext; |
||||
@Mock @SuppressWarnings("rawtypes") JdbcPersistentEntity entity; |
||||
|
||||
@Before |
||||
@SuppressWarnings("unchecked") |
||||
public void before() { |
||||
when(mappingContext.getRequiredPersistentEntity(Person.class)).thenReturn(entity); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("unchecked") |
||||
public void usesMappingJdbcEntityInformationIfMappingContextSet() { |
||||
|
||||
R2dbcRepositoryFactory factory = new R2dbcRepositoryFactory(databaseClient, mappingContext); |
||||
JdbcEntityInformation<Person, Long> entityInformation = factory.getEntityInformation(Person.class); |
||||
|
||||
assertThat(entityInformation).isInstanceOf(MappingJdbcEntityInformation.class); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("unchecked") |
||||
public void createsRepositoryWithIdTypeLong() { |
||||
|
||||
R2dbcRepositoryFactory factory = new R2dbcRepositoryFactory(databaseClient, mappingContext); |
||||
MyPersonRepository repository = factory.getRepository(MyPersonRepository.class); |
||||
|
||||
assertThat(repository).isNotNull(); |
||||
} |
||||
|
||||
interface MyPersonRepository extends Repository<Person, Long> {} |
||||
|
||||
static class Person {} |
||||
} |
||||
Loading…
Reference in new issue