24 changed files with 442 additions and 53 deletions
@ -0,0 +1,40 @@
@@ -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(); |
||||
} |
||||
@ -0,0 +1,59 @@
@@ -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); |
||||
} |
||||
} |
||||
@ -0,0 +1,50 @@
@@ -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(); |
||||
} |
||||
} |
||||
@ -0,0 +1,64 @@
@@ -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; |
||||
} |
||||
} |
||||
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
/* |
||||
* 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 java.util.List; |
||||
|
||||
import org.junit.ClassRule; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Import; |
||||
import org.springframework.data.annotation.Id; |
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; |
||||
import org.springframework.data.jdbc.testing.TestConfiguration; |
||||
import org.springframework.data.repository.CrudRepository; |
||||
import org.springframework.data.repository.query.Param; |
||||
import org.springframework.test.context.ContextConfiguration; |
||||
import org.springframework.test.context.junit4.rules.SpringClassRule; |
||||
import org.springframework.test.context.junit4.rules.SpringMethodRule; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
/** |
||||
* Tests the execution of queries from {@link Query} annotations on repository methods. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
@ContextConfiguration |
||||
@Transactional |
||||
public class QueryAnnotationHsqlIntegrationTests { |
||||
|
||||
@Autowired DummyEntityRepository repository; |
||||
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule(); |
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule(); |
||||
|
||||
@Test // DATAJDBC-164
|
||||
public void executeCustomQueryWithoutParameter() { |
||||
|
||||
repository.save(dummyEntity("Example")); |
||||
repository.save(dummyEntity("example")); |
||||
repository.save(dummyEntity("EXAMPLE")); |
||||
|
||||
List<DummyEntity> entities = repository.findByNameContainingCapitalLetter(); |
||||
|
||||
assertThat(entities) //
|
||||
.extracting(e -> e.name) //
|
||||
.containsExactlyInAnyOrder("Example", "EXAMPLE"); |
||||
|
||||
} |
||||
|
||||
@Test // DATAJDBC-164
|
||||
public void executeCustomQueryWithNamedParameters() { |
||||
|
||||
repository.save(dummyEntity("a")); |
||||
repository.save(dummyEntity("b")); |
||||
repository.save(dummyEntity("c")); |
||||
|
||||
List<DummyEntity> entities = repository.findByNamedRangeWithNamedParameter("a", "c"); |
||||
|
||||
assertThat(entities) //
|
||||
.extracting(e -> e.name) //
|
||||
.containsExactlyInAnyOrder("b"); |
||||
|
||||
} |
||||
|
||||
private DummyEntity dummyEntity(String name) { |
||||
|
||||
DummyEntity entity = new DummyEntity(); |
||||
entity.name = name; |
||||
return entity; |
||||
} |
||||
|
||||
@Configuration |
||||
@Import(TestConfiguration.class) |
||||
@EnableJdbcRepositories(considerNestedRepositories = true) |
||||
static class Config { |
||||
|
||||
@Bean |
||||
Class<?> testClass() { |
||||
return QueryAnnotationHsqlIntegrationTests.class; |
||||
} |
||||
} |
||||
|
||||
private static class DummyEntity { |
||||
|
||||
@Id Long id; |
||||
|
||||
String name; |
||||
} |
||||
|
||||
private interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> { |
||||
|
||||
@Query("SELECT * FROM DUMMYENTITY WHERE lower(name) <> name") |
||||
List<DummyEntity> findByNameContainingCapitalLetter(); |
||||
|
||||
|
||||
@Query("SELECT * FROM DUMMYENTITY WHERE name < :upper and name > :lower") |
||||
List<DummyEntity> findByNamedRangeWithNamedParameter(@Param("lower") String lower, @Param("upper") String upper); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue