DATAJDBC-164 - Add support for basic @Query annotation.

This commit is contained in:
Jens Schauder
2018-01-09 15:47:04 +01:00
committed by Greg Turnquist
parent 2163a26917
commit bc209b1114
24 changed files with 442 additions and 53 deletions

View File

@@ -24,7 +24,11 @@ import java.util.stream.StreamSupport;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.jdbc.mapping.model.*;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.PropertyPath;
@@ -278,7 +282,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
}
}
private <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
public <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context.getConversions(), context, accessStrategy);
}

View File

@@ -43,7 +43,7 @@ import org.springframework.jdbc.core.RowMapper;
* @author Oliver Gierke
* @since 2.0
*/
class EntityRowMapper<T> implements RowMapper<T> {
public class EntityRowMapper<T> implements RowMapper<T> {
private final JdbcPersistentEntity<T> entity;
private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();

View File

@@ -37,6 +37,7 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.TypeInformation;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
/**
* {@link MappingContext} implementation for JDBC.
@@ -53,20 +54,22 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
Temporal.class //
));
private final @Getter NamingStrategy namingStrategy;
@Getter private final NamingStrategy namingStrategy;
@Getter private final NamedParameterJdbcOperations template;
private GenericConversionService conversions = getDefaultConversionService();
public JdbcMappingContext(NamingStrategy namingStrategy, ConversionCustomizer customizer) {
public JdbcMappingContext(NamingStrategy namingStrategy, NamedParameterJdbcOperations template,
ConversionCustomizer customizer) {
this.namingStrategy = namingStrategy;
this.template = template;
customizer.customize(conversions);
setSimpleTypeHolder(new SimpleTypeHolder(CUSTOM_SIMPLE_TYPES, true));
}
public JdbcMappingContext() {
this(new DefaultNamingStrategy(), __ -> {});
public JdbcMappingContext(NamedParameterJdbcOperations template) {
this(new DefaultNamingStrategy(), template, __ -> {});
}
public List<PropertyPath> referencedEntities(Class<?> rootType, PropertyPath path) {
@@ -126,5 +129,4 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
return conversionService;
}
}

View File

@@ -23,6 +23,7 @@ import org.springframework.data.jdbc.mapping.model.ConversionCustomizer;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
/**
* Beans that must be registered for Spring Data JDBC to work.
@@ -33,11 +34,10 @@ import org.springframework.data.jdbc.mapping.model.NamingStrategy;
public class JdbcConfiguration {
@Bean
JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy,
JdbcMappingContext jdbcMappingContext(NamedParameterJdbcTemplate template, Optional<NamingStrategy> namingStrategy,
Optional<ConversionCustomizer> conversionCustomizer) {
return new JdbcMappingContext(
namingStrategy.orElse(new DefaultNamingStrategy()),
conversionCustomizer.orElse(conversionService -> {}));
namingStrategy.orElse(new DefaultNamingStrategy()), template, conversionCustomizer.orElse(conversionService -> {}));
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.annotation.QueryAnnotation;
/**
* Annotation to provide SQL statements that will get used for executing the method.
*
* The SQL statement may contain named parameters as supported by {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}.
* Those parameters will get bound to the arguments of the annotated method.
*
* @author Jens Schauder
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@QueryAnnotation
@Documented
public @interface Query {
String value();
}

View File

@@ -0,0 +1,59 @@
/*
* 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 java.lang.reflect.Method;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.EntityRowMapper;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.RowMapper;
/**
* {@link QueryLookupStrategy} for JDBC repositories. Currently only supports annotated queries.
*
* @author Jens Schauder
*/
class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private final JdbcMappingContext context;
private final DataAccessStrategy accessStrategy;
JdbcQueryLookupStrategy(EvaluationContextProvider evaluationContextProvider, JdbcMappingContext context,
DataAccessStrategy accessStrategy) {
this.context = context;
this.accessStrategy = accessStrategy;
}
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory);
Class<?> domainType = queryMethod.getReturnedObjectType();
RowMapper<?> rowMapper = new EntityRowMapper<>(context.getRequiredPersistentEntity(domainType),
context.getConversions(), context, accessStrategy);
return new JdbcRepositoryQuery(queryMethod, context, rowMapper);
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod;
/**
* {@link QueryMethod} implementation that implements a method by executing the query from a {@link Query} annotation on
* that method.
*
* Binds method arguments to named parameters in the SQL statement.
*
* @author Jens Schauder
*/
public class JdbcQueryMethod extends QueryMethod {
private final Method method;
public JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
super(method, metadata, factory);
this.method = method;
}
public String getAnnotatedQuery() {
Query queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
return queryAnnotation == null ? null : queryAnnotation.value();
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.jdbc.repository.support;
import java.util.Optional;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.JdbcEntityTemplate;
@@ -25,8 +27,12 @@ import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;
/**
* Creates repository implementation based on JDBC.
*
* @author Jens Schauder
* @author Greg Turnquist
* @since 2.0
@@ -67,4 +73,17 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
return SimpleJdbcRepository.class;
}
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key,
EvaluationContextProvider evaluationContextProvider) {
if (key != null //
&& key != QueryLookupStrategy.Key.USE_DECLARED_QUERY //
&& key != QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND //
) {
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
}
return Optional.of(new JdbcQueryLookupStrategy(evaluationContextProvider, context, accessStrategy));
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.mapping.model.JdbcMappingContext;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
/**
* A query to be executed based on a repository method, it's annotated SQL query and the arguments provided to the
* method.
*
* @author Jens Schauder
*/
class JdbcRepositoryQuery implements RepositoryQuery {
private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.";
private final JdbcQueryMethod queryMethod;
private final JdbcMappingContext context;
private final RowMapper<?> rowMapper;
JdbcRepositoryQuery(JdbcQueryMethod queryMethod, JdbcMappingContext context, RowMapper rowMapper) {
this.queryMethod = queryMethod;
this.context = context;
this.rowMapper = rowMapper;
}
@Override
public Object execute(Object[] objects) {
String query = queryMethod.getAnnotatedQuery();
MapSqlParameterSource parameters = new MapSqlParameterSource();
queryMethod.getParameters().getBindableParameters().forEach(p -> {
String parameterName = p.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
parameters.addValue(parameterName, objects[p.getIndex()]);
});
return context.getTemplate().query(query, parameters, rowMapper);
}
@Override
public JdbcQueryMethod getQueryMethod() {
return queryMethod;
}
}