From 2c2c6975b6bc525e75258461ddb57c9b6ef8d2a8 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 12 Jan 2018 12:34:39 +0100 Subject: [PATCH 1/2] DATAJDBC-164 - Prepare branch --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2d68144a47..1a039fe685 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jdbc - 1.0.0.BUILD-SNAPSHOT + 1.0.0.DATAJDBC-164-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. From 7debbb891517571052ded2ec07970c8c2f83d6be Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 9 Jan 2018 15:47:04 +0100 Subject: [PATCH 2/2] DATAJDBC-164 - Added support of basic @Query annotation. --- README.adoc | 22 +++- .../jdbc/core/DefaultDataAccessStrategy.java | 8 +- .../data/jdbc/core/EntityRowMapper.java | 2 +- .../mapping/model/JdbcMappingContext.java | 14 ++- .../repository/config/JdbcConfiguration.java | 6 +- .../data/jdbc/repository/query/Query.java | 40 ++++++ .../support/JdbcQueryLookupStrategy.java | 59 +++++++++ .../repository/support/JdbcQueryMethod.java | 50 ++++++++ .../support/JdbcRepositoryFactory.java | 19 +++ .../support/JdbcRepositoryQuery.java | 64 ++++++++++ .../DefaultDataAccessStrategyUnitTests.java | 12 +- .../core/DefaultJdbcInterpreterUnitTests.java | 10 +- .../jdbc/core/EntityRowMapperUnitTests.java | 3 +- ...orContextBasedNamingStrategyUnitTests.java | 4 +- ...GeneratorFixedNamingStrategyUnitTests.java | 4 +- .../data/jdbc/core/SqlGeneratorUnitTests.java | 9 +- .../JdbcEntityDeleteWriterUnitTests.java | 5 +- .../conversion/JdbcEntityWriterUnitTests.java | 4 +- ...cPersistentEntityInformationUnitTests.java | 6 +- .../BasicJdbcPersistentPropertyUnitTests.java | 4 +- .../SimpleJdbcRepositoryEventsUnitTests.java | 2 +- .../QueryAnnotationHsqlIntegrationTests.java | 117 ++++++++++++++++++ .../data/jdbc/testing/TestConfiguration.java | 30 ++--- ...eryAnnotationHsqlIntegrationTests-hsql.sql | 1 + 24 files changed, 442 insertions(+), 53 deletions(-) create mode 100644 src/main/java/org/springframework/data/jdbc/repository/query/Query.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java create mode 100644 src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java create mode 100644 src/test/resources/org.springframework.data.jdbc.repository.query/QueryAnnotationHsqlIntegrationTests-hsql.sql diff --git a/README.adoc b/README.adoc index 8a5d46118b..ab6e107607 100644 --- a/README.adoc +++ b/README.adoc @@ -57,6 +57,19 @@ public void someMethod() { } ---- +=== Query annotation + +You can annotate a query method with `@Query` to specify a SQL statement to be used for that method. +You can bind method arguments using named parameters in the SQL statement like in the following example: + +[source,java] +---- +@Query("SELECT * FROM DUMMYENTITY WHERE name < :upper and name > :lower") +List findByNameRange(@Param("lower") String lower, @Param("upper") String upper); +---- + +If you compile your sources with the `-parameters` compiler flag you can omit the `@Param` annotations. + === Id generation Spring Data JDBC uses the id to identify entities but also to determine if an entity is new or already existing in the database. @@ -200,9 +213,12 @@ Note that the type used for prefixing the statement name is the name of the aggr == Features planned for the not to far future -=== Query annotation +=== Advance query annotation support -Just annotate a method with a SQL query to use this whenever the method gets called. +* customizable `RowMapper` +* projections +* modifying queries +* SpEL expressions === MyBatis per method support @@ -218,7 +234,7 @@ Currently you will need to build it locally. == Getting Help Right now the best source of information is the source code in this repository. -Especially the integration tests (type `t` and then `IntegrationTests.java`) +Especially the integration tests (When you are reading this on github type `t` and then `IntegrationTests.java`) We are keeping an eye on the (soon to be created) https://stackoverflow.com/questions/tagged/spring-data-jdbc[spring-data-jdbc tag on stackoverflow]. diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java index fec3216191..82b690fa1f 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java @@ -24,7 +24,11 @@ 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 @@ private Optional getIdFromHolder(KeyHolder holder, JdbcPersistentEnt } } - private EntityRowMapper getEntityRowMapper(Class domainType) { + public EntityRowMapper getEntityRowMapper(Class domainType) { return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context.getConversions(), context, accessStrategy); } diff --git a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java index ea8629931d..24f60df49d 100644 --- a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java @@ -43,7 +43,7 @@ * @author Oliver Gierke * @since 2.0 */ -class EntityRowMapper implements RowMapper { +public class EntityRowMapper implements RowMapper { private final JdbcPersistentEntity entity; private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator(); diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java index 381f9ab689..570d4a8f92 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java @@ -37,6 +37,7 @@ 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 {}); + public JdbcMappingContext(NamedParameterJdbcOperations template) { + this(new DefaultNamingStrategy(), template, __ -> {}); } public List referencedEntities(Class rootType, PropertyPath path) { @@ -126,5 +129,4 @@ private static GenericConversionService getDefaultConversionService() { return conversionService; } - } diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java index 8ecd1f5b02..4dafd53aae 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java +++ b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java @@ -23,6 +23,7 @@ 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 @@ public class JdbcConfiguration { @Bean - JdbcMappingContext jdbcMappingContext(Optional namingStrategy, + JdbcMappingContext jdbcMappingContext(NamedParameterJdbcTemplate template, Optional namingStrategy, Optional conversionCustomizer) { return new JdbcMappingContext( - namingStrategy.orElse(new DefaultNamingStrategy()), - conversionCustomizer.orElse(conversionService -> {})); + namingStrategy.orElse(new DefaultNamingStrategy()), template, conversionCustomizer.orElse(conversionService -> {})); } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/query/Query.java b/src/main/java/org/springframework/data/jdbc/repository/query/Query.java new file mode 100644 index 0000000000..bf7037c8ff --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/query/Query.java @@ -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(); +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java new file mode 100644 index 0000000000..ec097d6a9f --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java @@ -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); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java new file mode 100644 index 0000000000..f1552190b8 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java @@ -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(); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index fc37dfcb69..49281dc738 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -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.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 @@ protected Class getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) return SimpleJdbcRepository.class; } + @Override + protected Optional 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)); + } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java new file mode 100644 index 0000000000..07b46e26eb --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java @@ -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; + } +} diff --git a/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java index d22eda11c0..6ffbe1403e 100644 --- a/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java @@ -15,12 +15,15 @@ */ package org.springframework.data.jdbc.core; -import java.util.HashMap; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import lombok.RequiredArgsConstructor; + +import java.util.HashMap; + import org.junit.Test; import org.mockito.ArgumentCaptor; - import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; @@ -28,9 +31,6 @@ import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.support.KeyHolder; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - /** * @author Jens Schauder */ @@ -39,8 +39,8 @@ public class DefaultDataAccessStrategyUnitTests { public static final long ID_FROM_ADDITIONAL_VALUES = 23L; public static final long ORIGINAL_ID = 4711L; - JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy(), __ -> {}); NamedParameterJdbcOperations jdbcOperations = mock(NamedParameterJdbcOperations.class); + JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy(), jdbcOperations, __ -> {}); HashMap additionalParameters = new HashMap<>(); ArgumentCaptor captor = ArgumentCaptor.forClass(SqlParameterSource.class); diff --git a/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java index b16635e890..134c826cb1 100644 --- a/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java @@ -15,12 +15,14 @@ */ package org.springframework.data.jdbc.core; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + import java.util.AbstractMap.SimpleEntry; import java.util.Map; import org.junit.Test; import org.mockito.ArgumentCaptor; - import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.conversion.DbAction; import org.springframework.data.jdbc.core.conversion.DbAction.Insert; @@ -28,9 +30,7 @@ import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * Unit tests for {@link DefaultJdbcInterpreter} @@ -47,7 +47,7 @@ public class DefaultJdbcInterpreterUnitTests { public String getReverseColumnName(JdbcPersistentProperty property) { return BACK_REFERENCE; } - }, __ -> {}); + }, mock(NamedParameterJdbcOperations.class), __ -> {}); DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class); DefaultJdbcInterpreter interpreter = new DefaultJdbcInterpreter(context, dataAccessStrategy); diff --git a/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java index b4c3645ac1..9c55fdc521 100644 --- a/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java @@ -43,6 +43,7 @@ import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.util.Assert; /** @@ -117,7 +118,7 @@ public void mapReferenceGetsLoadedWithAdditionalSelect() throws SQLException { private EntityRowMapper createRowMapper(Class type) { - JdbcMappingContext context = new JdbcMappingContext(); + JdbcMappingContext context = new JdbcMappingContext(mock(NamedParameterJdbcOperations.class)); DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class); // the ID of the entity is used to determin what kind of resultset is needed for subsequent selects. diff --git a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java index 1305727e11..88c48f15d7 100644 --- a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.core; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -29,6 +30,7 @@ import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.NamingStrategy; import org.springframework.data.mapping.PropertyPath; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * Unit tests to verify a contextual {@link NamingStrategy} implementation that customizes using a user-centric {@link ThreadLocal}. @@ -185,7 +187,7 @@ private void threadedTest(String user, CountDownLatch latch, Consumer te */ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { - JdbcMappingContext context = new JdbcMappingContext(namingStrategy, __ -> {}); + JdbcMappingContext context = new JdbcMappingContext(namingStrategy, mock(NamedParameterJdbcOperations.class), __ -> {}); JdbcPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); diff --git a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java index 495f7ba348..0fe50121b1 100644 --- a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.core; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import org.assertj.core.api.SoftAssertions; import org.junit.Test; @@ -26,6 +27,7 @@ import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.jdbc.mapping.model.NamingStrategy; import org.springframework.data.mapping.PropertyPath; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * Unit tests the {@link SqlGenerator} with a fixed {@link NamingStrategy} implementation containing a hard wired @@ -180,7 +182,7 @@ public void deleteByList() { */ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { - JdbcMappingContext context = new JdbcMappingContext(namingStrategy, __ -> {}); + JdbcMappingContext context = new JdbcMappingContext(namingStrategy, mock(NamedParameterJdbcOperations.class), __ -> {}); JdbcPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); } diff --git a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java index b32583a7b5..41db7987b7 100644 --- a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java @@ -15,13 +15,15 @@ */ package org.springframework.data.jdbc.core; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + import java.util.Map; import java.util.Set; import org.assertj.core.api.SoftAssertions; import org.junit.Before; import org.junit.Test; - import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; @@ -29,8 +31,7 @@ import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.jdbc.mapping.model.NamingStrategy; import org.springframework.data.mapping.PropertyPath; - -import static org.assertj.core.api.Assertions.*; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * Unit tests for the {@link SqlGenerator}. @@ -46,7 +47,7 @@ public class SqlGeneratorUnitTests { public void setUp() { NamingStrategy namingStrategy = new PrefixingNamingStrategy(); - JdbcMappingContext context = new JdbcMappingContext(namingStrategy, __ -> {}); + JdbcMappingContext context = new JdbcMappingContext(namingStrategy, mock(NamedParameterJdbcOperations.class), __ -> {}); JdbcPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); this.sqlGenerator = new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); } diff --git a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriterUnitTests.java index 3a95f9f9cc..d26b40b1b3 100644 --- a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriterUnitTests.java @@ -15,6 +15,8 @@ */ package org.springframework.data.jdbc.core.conversion; +import static org.mockito.Mockito.*; + import lombok.Data; import org.assertj.core.api.Assertions; @@ -26,6 +28,7 @@ import org.springframework.data.jdbc.core.conversion.AggregateChange.Kind; import org.springframework.data.jdbc.core.conversion.DbAction.Delete; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * Unit tests for the {@link JdbcEntityDeleteWriter} @@ -35,7 +38,7 @@ @RunWith(MockitoJUnitRunner.class) public class JdbcEntityDeleteWriterUnitTests { - JdbcEntityDeleteWriter converter = new JdbcEntityDeleteWriter(new JdbcMappingContext()); + JdbcEntityDeleteWriter converter = new JdbcEntityDeleteWriter(new JdbcMappingContext(mock(NamedParameterJdbcOperations.class))); @Test public void deleteDeletesTheEntityAndReferencedEntities() { diff --git a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java index 4d7babd037..0b45c71def 100644 --- a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.core.conversion; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import lombok.RequiredArgsConstructor; @@ -33,6 +34,7 @@ import org.springframework.data.jdbc.core.conversion.DbAction.Insert; import org.springframework.data.jdbc.core.conversion.DbAction.Update; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * Unit tests for the {@link JdbcEntityWriter} @@ -43,7 +45,7 @@ public class JdbcEntityWriterUnitTests { public static final long SOME_ENTITY_ID = 23L; - JdbcEntityWriter converter = new JdbcEntityWriter(new JdbcMappingContext()); + JdbcEntityWriter converter = new JdbcEntityWriter(new JdbcMappingContext(mock(NamedParameterJdbcOperations.class))); @Test // DATAJDBC-112 public void newEntityGetsConvertedToOneInsert() { diff --git a/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentEntityInformationUnitTests.java b/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentEntityInformationUnitTests.java index 1f0082e064..13f3ae9d87 100644 --- a/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentEntityInformationUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentEntityInformationUnitTests.java @@ -15,11 +15,13 @@ */ package org.springframework.data.jdbc.mapping.model; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Java6Assertions.*; +import static org.mockito.Mockito.*; import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.domain.Persistable; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.lang.Nullable; /** @@ -27,7 +29,7 @@ */ public class BasicJdbcPersistentEntityInformationUnitTests { - JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy(), cs -> {}); + JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy(), mock(NamedParameterJdbcOperations.class), cs -> {}); private DummyEntity dummyEntity = new DummyEntity(); private PersistableDummyEntity persistableDummyEntity = new PersistableDummyEntity(); diff --git a/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentPropertyUnitTests.java index aae27ddd13..98f8095104 100644 --- a/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentPropertyUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.mapping.model; import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.mockito.Mockito.*; import lombok.Data; @@ -26,6 +27,7 @@ import org.assertj.core.api.Assertions; import org.junit.Test; import org.springframework.data.mapping.PropertyHandler; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * Unit tests for the {@link BasicJdbcPersistentProperty}. @@ -37,7 +39,7 @@ public class BasicJdbcPersistentPropertyUnitTests { @Test // DATAJDBC-104 public void enumGetsStoredAsString() { - JdbcPersistentEntity persistentEntity = new JdbcMappingContext() + JdbcPersistentEntity persistentEntity = new JdbcMappingContext(mock(NamedParameterJdbcOperations.class)) .getRequiredPersistentEntity(DummyEntity.class); persistentEntity.doWithProperties((PropertyHandler) p -> { diff --git a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index ef6b08798f..a5b8c5a772 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -46,7 +46,7 @@ public class SimpleJdbcRepositoryEventsUnitTests { @Before public void before() { - final JdbcMappingContext context = new JdbcMappingContext(); + final JdbcMappingContext context = new JdbcMappingContext(mock(NamedParameterJdbcOperations.class)); JdbcRepositoryFactory factory = new JdbcRepositoryFactory( // publisher, // context, // diff --git a/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java new file mode 100644 index 0000000000..cde1c740dd --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java @@ -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 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 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 { + + @Query("SELECT * FROM DUMMYENTITY WHERE lower(name) <> name") + List findByNameContainingCapitalLetter(); + + + @Query("SELECT * FROM DUMMYENTITY WHERE name < :upper and name > :lower") + List findByNamedRangeWithNamedParameter(@Param("lower") String lower, @Param("upper") String upper); + } +} diff --git a/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index 4bb1a3a33e..50889396da 100644 --- a/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -30,7 +30,10 @@ import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; -import org.springframework.data.jdbc.mapping.model.*; +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.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -54,19 +57,16 @@ public class TestConfiguration { @Bean JdbcRepositoryFactory jdbcRepositoryFactory() { - final JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy() { - @Override - public String getColumnName(JdbcPersistentProperty property) { - return super.getColumnName(property); - } - }, __ -> {}); + NamedParameterJdbcTemplate jdbcTemplate = namedParameterJdbcTemplate(); + + final JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy(), jdbcTemplate, __ -> {}); return new JdbcRepositoryFactory( // publisher, // context, // new DefaultDataAccessStrategy( // new SqlGeneratorSource(context), // - namedParameterJdbcTemplate(), // + jdbcTemplate, // context) // ); } @@ -83,7 +83,7 @@ PlatformTransactionManager transactionManager() { @Bean DataAccessStrategy defaultDataAccessStrategy(JdbcMappingContext context, - @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations operations) { + @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations operations) { DelegatingDataAccessStrategy accessStrategy = new DelegatingDataAccessStrategy(); @@ -98,11 +98,13 @@ DataAccessStrategy defaultDataAccessStrategy(JdbcMappingContext context, } @Bean - JdbcMappingContext jdbcMappingContext(Optional namingStrategy, - Optional conversionCustomizer) { + JdbcMappingContext jdbcMappingContext(NamedParameterJdbcOperations template, Optional namingStrategy, + Optional conversionCustomizer) { - return new JdbcMappingContext( - namingStrategy.orElse(new DefaultNamingStrategy()), - conversionCustomizer.orElse(conversionService -> {})); + return new JdbcMappingContext( // + namingStrategy.orElse(new DefaultNamingStrategy()), // + template, // + conversionCustomizer.orElse(conversionService -> {}) // + ); } } diff --git a/src/test/resources/org.springframework.data.jdbc.repository.query/QueryAnnotationHsqlIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository.query/QueryAnnotationHsqlIntegrationTests-hsql.sql new file mode 100644 index 0000000000..23bf117b32 --- /dev/null +++ b/src/test/resources/org.springframework.data.jdbc.repository.query/QueryAnnotationHsqlIntegrationTests-hsql.sql @@ -0,0 +1 @@ +CREATE TABLE dummyentity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100))