DATAJDBC-234 - Adds support for named queries stored in property files.

Queries are expected in a properties file on the classpath `META-INF/jdbc-named-queries.properties`.
The name of the query is by default `<simple class name of the domain type>.<methodName>`.
Alternatively it can be set using the `@Query` annotations `name` attribute.

Original pull request: #180.
This commit is contained in:
mcisnerosb57
2019-11-19 12:17:24 +01:00
committed by Jens Schauder
parent a30fbf6246
commit 93272c8461
7 changed files with 239 additions and 27 deletions

View File

@@ -31,6 +31,7 @@ import org.springframework.jdbc.core.RowMapper;
* parameters will get bound to the arguments of the annotated method.
*
* @author Jens Schauder
* @author Moises Cisneros
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@@ -41,7 +42,13 @@ public @interface Query {
/**
* The SQL statement to execute when the annotated method gets invoked.
*/
String value();
String value() default "";
/**
* The named query to be used. If not defined, the name of
* {@code $ domainClass}.${queryMethodName}} will be used.
*/
String name() default "";
/**
* Optional {@link RowMapper} to use to convert the result of the query to domain class instances. Cannot be used

View File

@@ -43,6 +43,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
* @author Oliver Gierke
* @author Mark Paluch
* @author Maciej Walkowiak
* @author Moises Cisneros
*/
@RequiredArgsConstructor
class JdbcQueryLookupStrategy implements QueryLookupStrategy {
@@ -53,6 +54,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private final JdbcConverter converter;
private final QueryMappingConfiguration queryMappingConfiguration;
private final NamedParameterJdbcOperations operations;
/*
* (non-Javadoc)
@@ -62,7 +64,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory);
JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory,namedQueries);
RowMapper<?> mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod);

View File

@@ -27,24 +27,30 @@ import org.springframework.data.repository.query.QueryMethod;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.data.repository.core.NamedQueries;
/**
* {@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.
* {@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
* @author Kazuki Shimizu
* @author Moises Cisneros
* @deprecated Visibility of this class will be reduced to package private.
*/
@Deprecated
public class JdbcQueryMethod extends QueryMethod {
private final Method method;
private final NamedQueries namedQueries;
public JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
public JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries) {
super(method, metadata, factory);
this.namedQueries = namedQueries;
this.method = method;
}
@@ -53,13 +59,49 @@ public class JdbcQueryMethod extends QueryMethod {
*
* @return May be {@code null}.
*/
@Nullable
String getAnnotatedQuery() {
return getMergedAnnotationAttribute("value");
public String getAnnotatedQuery() {
String annotatedValue = getQueryValue();
return StringUtils.hasText(annotatedValue) ? annotatedValue : getNamedQuery();
}
/**
* Returns the class to be used as {@link org.springframework.jdbc.core.RowMapper}
* Returns the annotated query with key value if it exists.
*
* @return May be {@code null}.
*/
@Nullable
String getQueryValue() {
return getMergedAnnotationAttribute("value");
}
/**
* Returns the annotated query name.
*
* @return May be {@code null}.
*/
@Nullable
String getQueryName() {
return getMergedAnnotationAttribute("name");
}
/**
* Returns the annotated query with key name if it exists.
*
* @return May be {@code null}.
*/
@Nullable
String getNamedQuery() {
String annotatedName = getMergedAnnotationAttribute("name");
return (StringUtils.hasText(annotatedName) && this.namedQueries.hasQuery(annotatedName))
? this.namedQueries.getQuery(annotatedName)
: this.namedQueries.getQuery(super.getName());
}
/*
* Returns the class to be used as {@link
* org.springframework.jdbc.core.RowMapper}
*
* @return May be {@code null}.
*/
@@ -69,7 +111,8 @@ public class JdbcQueryMethod extends QueryMethod {
}
/**
* Returns the class to be used as {@link org.springframework.jdbc.core.ResultSetExtractor}
* Returns the class to be used as
* {@link org.springframework.jdbc.core.ResultSetExtractor}
*
* @return May be {@code null}.
*/

View File

@@ -24,22 +24,32 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.jdbc.testing.QueryNamedTestConfiguration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
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.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.io.IOException;
import java.util.List;
import javax.annotation.PostConstruct;
;
/**
* Very simple use cases for creation and usage of JdbcRepositories.
@@ -49,12 +59,14 @@ import java.util.HashMap;
@ContextConfiguration
@Transactional
public class JdbcRepositoryIntegrationTests {
public static final String DUMMY_SELECT_NAME = "DUMMY.SELECT";
@Configuration
@Import(TestConfiguration.class)
@Import(QueryNamedTestConfiguration.class)
static class Config {
@Autowired JdbcRepositoryFactory factory;
@Autowired
JdbcRepositoryFactory factory;
@Bean
Class<?> testClass() {
@@ -64,8 +76,7 @@ public class JdbcRepositoryIntegrationTests {
@Bean
DummyEntityRepository dummyEntityRepository() {
return factory.getRepository(DummyEntityRepository.class);
}
}
}
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
@@ -244,6 +255,13 @@ public class JdbcRepositoryIntegrationTests {
assertThat(repository.findById(-1L)).isEmpty();
}
@Test // DATAJDBC-234
public void findAllQueryName() {
repository.save(createDummyEntity());
assertThat(repository.findAllQueryName().size() > 0);
}
private static DummyEntity createDummyEntity() {
DummyEntity entity = new DummyEntity();
@@ -252,11 +270,13 @@ public class JdbcRepositoryIntegrationTests {
}
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
@Query(name = DUMMY_SELECT_NAME)
List<DummyEntity> findAllQueryName();
}
@Data
static class DummyEntity {
String name;
@Id private Long idProp;
}

View File

@@ -21,11 +21,14 @@ import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.util.Properties;
import org.junit.Test;
import org.springframework.data.jdbc.repository.query.Query;
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.core.support.PropertiesBasedNamedQueries;
import org.springframework.jdbc.core.RowMapper;
/**
@@ -33,10 +36,14 @@ import org.springframework.jdbc.core.RowMapper;
*
* @author Jens Schauder
* @author Oliver Gierke
* @author Moises Cisneros
*/
public class JdbcQueryMethodUnitTests {
public static final String DUMMY_SELECT = "SELECT something";
public static final String DUMMY_SELECT_VALUE = "SELECT something";
public static final String DUMMY_SELECT_NAME = "DUMMY.SELECT";
public static final String DUMMY_SELECT_METHOD = "queryWhitoutQueryAnnotation";
public static final String DUMMY_SELECT_NAME_VALUE= "SELECT something NAME AND VALUE";
@Test // DATAJDBC-165
public void returnsSqlStatement() throws NoSuchMethodException {
@@ -44,11 +51,13 @@ public class JdbcQueryMethodUnitTests {
RepositoryMetadata metadata = mock(RepositoryMetadata.class);
doReturn(String.class).when(metadata).getReturnedDomainClass(any(Method.class));
JdbcQueryMethod queryMethod = new JdbcQueryMethod(JdbcQueryMethodUnitTests.class.getDeclaredMethod("queryMethod"),
metadata, mock(ProjectionFactory.class));
assertThat(queryMethod.getAnnotatedQuery()).isEqualTo(DUMMY_SELECT);
Properties properties = new Properties();
properties.setProperty(DUMMY_SELECT_NAME, DUMMY_SELECT_VALUE);
NamedQueries nameQueries = new PropertiesBasedNamedQueries(properties);
JdbcQueryMethod queryMethod = new JdbcQueryMethod(
JdbcQueryMethodUnitTests.class.getDeclaredMethod("queryMethod"), metadata,
mock(ProjectionFactory.class), nameQueries);
assertThat(queryMethod.getAnnotatedQuery()).isEqualTo(DUMMY_SELECT_VALUE);
}
@Test // DATAJDBC-165
@@ -57,15 +66,91 @@ public class JdbcQueryMethodUnitTests {
RepositoryMetadata metadata = mock(RepositoryMetadata.class);
doReturn(String.class).when(metadata).getReturnedDomainClass(any(Method.class));
Properties properties = new Properties();
properties.setProperty(DUMMY_SELECT_NAME, DUMMY_SELECT_VALUE);
NamedQueries nameQueries = new PropertiesBasedNamedQueries(properties);
JdbcQueryMethod queryMethod = new JdbcQueryMethod(JdbcQueryMethodUnitTests.class.getDeclaredMethod("queryMethod"),
metadata, mock(ProjectionFactory.class));
JdbcQueryMethod queryMethod = new JdbcQueryMethod(
JdbcQueryMethodUnitTests.class.getDeclaredMethod("queryMethod"), metadata,
mock(ProjectionFactory.class), nameQueries);
assertThat(queryMethod.getRowMapperClass()).isEqualTo(CustomRowMapper.class);
}
@Query(value = DUMMY_SELECT, rowMapperClass = CustomRowMapper.class)
private void queryMethod() {}
@Test // DATAJDBC-234
public void returnsSqlStatementName() throws NoSuchMethodException {
RepositoryMetadata metadata = mock(RepositoryMetadata.class);
doReturn(String.class).when(metadata).getReturnedDomainClass(any(Method.class));
Properties properties = new Properties();
properties.setProperty(DUMMY_SELECT_NAME, DUMMY_SELECT_VALUE);
NamedQueries nameQueries = new PropertiesBasedNamedQueries(properties);
JdbcQueryMethod queryMethod = new JdbcQueryMethod(
JdbcQueryMethodUnitTests.class.getDeclaredMethod("queryMethodName"), metadata,
mock(ProjectionFactory.class), nameQueries);
assertThat(queryMethod.getAnnotatedQuery()).isEqualTo(DUMMY_SELECT_VALUE);
}
@Test // DATAJDBC-234
public void returnsSqlStatementNameAndValue() throws NoSuchMethodException {
RepositoryMetadata metadata = mock(RepositoryMetadata.class);
doReturn(String.class).when(metadata).getReturnedDomainClass(any(Method.class));
Properties properties = new Properties();
properties.setProperty(DUMMY_SELECT_NAME, DUMMY_SELECT_VALUE);
NamedQueries nameQueries = new PropertiesBasedNamedQueries(properties);
JdbcQueryMethod queryMethod = new JdbcQueryMethod(
JdbcQueryMethodUnitTests.class.getDeclaredMethod("queryMethodNameAndValue"), metadata,
mock(ProjectionFactory.class), nameQueries);
assertThat(queryMethod.getAnnotatedQuery()).isEqualTo(DUMMY_SELECT_NAME_VALUE);
}
@Test // DATAJDBC-234
public void returnsNullNoSqlQuery() throws NoSuchMethodException {
RepositoryMetadata metadata = mock(RepositoryMetadata.class);
Properties properties = new Properties();
properties.setProperty(DUMMY_SELECT_METHOD, DUMMY_SELECT_VALUE);
NamedQueries nameQueries = new PropertiesBasedNamedQueries(properties);
doReturn(String.class).when(metadata).getReturnedDomainClass(any(Method.class));
JdbcQueryMethod queryMethod = new JdbcQueryMethod(
JdbcQueryMethodUnitTests.class.getDeclaredMethod("queryWhitoutQueryAnnotation"), metadata,
mock(ProjectionFactory.class), nameQueries);
assertThat(queryMethod.getAnnotatedQuery()).isEqualTo(DUMMY_SELECT_VALUE);
}
@Query(value = DUMMY_SELECT_VALUE, rowMapperClass = CustomRowMapper.class)
private void queryMethod() {
}
@Query(name = DUMMY_SELECT_NAME)
private void queryMethodName() {
}
@Query(value = DUMMY_SELECT_NAME_VALUE, name = DUMMY_SELECT_NAME)
private void queryMethodNameAndValue() {
}
private void queryWhitoutQueryAnnotation() {
}
private class CustomRowMapper implements RowMapper<Object> {

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2017-2019 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
*
* https://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.testing;
import java.io.IOException;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
/**
* Infrastructure configuration for integration tests.
*
* @author Moises Cisneros
*/
@Configuration
@ComponentScan // To pick up configuration classes (per activated profile)
@Import(TestConfiguration.class)
public class QueryNamedTestConfiguration {
@Autowired JdbcRepositoryFactory factory;
@PostConstruct()
public void factory() throws IOException {
PropertiesFactoryBean properties = new PropertiesFactoryBean();
properties.setLocation(new ClassPathResource("META-INF/jdbc-named-queries.properties"));
NamedQueries namedQueries = null;
properties.afterPropertiesSet();
namedQueries = new PropertiesBasedNamedQueries(properties.getObject());
factory.setNamedQueries(namedQueries);
}
}

View File

@@ -0,0 +1 @@
DUMMY.SELECT=SELECT * FROM DUMMY_ENTITY