diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java index c184c2d298..2ca7379351 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java @@ -31,6 +31,7 @@ * 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 @@ /** * 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 diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java index 2906dbf355..69410aa0aa 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java @@ -43,6 +43,7 @@ * @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); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java index 14fa0ea864..82feb1ee8d 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java @@ -27,24 +27,30 @@ 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 JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFac * * @return May be {@code null}. */ + @Nullable - String getAnnotatedQuery() { + public String getAnnotatedQuery() { + String annotatedValue = getQueryValue(); + return StringUtils.hasText(annotatedValue) ? annotatedValue : getNamedQuery(); + } + + /** + * 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 class to be used as {@link org.springframework.jdbc.core.RowMapper} + * 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 @@ Class getRowMapperClass() { } /** - * 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}. */ diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 64657011cf..6292cea80f 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -24,22 +24,32 @@ 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,13 +59,14 @@ @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() { return JdbcRepositoryIntegrationTests.class; @@ -64,15 +75,16 @@ Class testClass() { @Bean DummyEntityRepository dummyEntityRepository() { return factory.getRepository(DummyEntityRepository.class); - } - + } } - @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); - @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + @ClassRule + public static final SpringClassRule classRule = new SpringClassRule(); + @Rule + public SpringMethodRule methodRule = new SpringMethodRule(); - @Autowired NamedParameterJdbcTemplate template; - @Autowired DummyEntityRepository repository; + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; @Test // DATAJDBC-95 public void savesAnEntity() { @@ -244,20 +256,30 @@ public void findByIdReturnsEmptyWhenNoneFound() { assertThat(repository.findById(-1L)).isEmpty(); } - private static DummyEntity createDummyEntity() { + @Test // DATAJDBC-234 + public void findAllQueryName() { + + repository.save(createDummyEntity()); + assertThat(repository.findAllQueryName().size() > 0); + } + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); entity.setName("Entity Name"); return entity; } interface DummyEntityRepository extends CrudRepository { + + @Query(name = DUMMY_SELECT_NAME) + List findAllQueryName(); } @Data static class DummyEntity { - String name; - @Id private Long idProp; + @Id + private Long idProp; } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethodUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethodUnitTests.java index 0f7d77dd7c..ddbe4d6d42 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethodUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethodUnitTests.java @@ -21,11 +21,14 @@ 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 @@ * * @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 void returnsSqlStatement() throws NoSuchMethodException { 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 void returnsSpecifiedRowMapperClass() 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("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 { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/QueryNamedTestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/QueryNamedTestConfiguration.java new file mode 100644 index 0000000000..2f41077645 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/QueryNamedTestConfiguration.java @@ -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); + } +} + diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index 623ddb9ba2..1b9185c6f5 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -15,7 +15,9 @@ */ package org.springframework.data.jdbc.testing; +import java.io.IOException; import java.util.Optional; +import java.util.Properties; import javax.sql.DataSource; @@ -24,11 +26,13 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; +import org.springframework.core.io.ClassPathResource; import org.springframework.data.convert.CustomConversions; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; @@ -43,6 +47,8 @@ import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; @@ -63,14 +69,13 @@ public class TestConfiguration { @Autowired DataSource dataSource; @Autowired ApplicationEventPublisher publisher; @Autowired(required = false) SqlSessionFactory sqlSessionFactory; - + @Bean JdbcRepositoryFactory jdbcRepositoryFactory( @Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy, RelationalMappingContext context, - JdbcConverter converter) { + JdbcConverter converter) throws IOException { return new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher, namedParameterJdbcTemplate()); } - @Bean NamedParameterJdbcOperations namedParameterJdbcTemplate() { return new NamedParameterJdbcTemplate(dataSource); diff --git a/spring-data-jdbc/src/test/resources/META-INF/jdbc-named-queries.properties b/spring-data-jdbc/src/test/resources/META-INF/jdbc-named-queries.properties new file mode 100644 index 0000000000..e9cb683b2c --- /dev/null +++ b/spring-data-jdbc/src/test/resources/META-INF/jdbc-named-queries.properties @@ -0,0 +1 @@ +DUMMY.SELECT=SELECT * FROM DUMMY_ENTITY \ No newline at end of file