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

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

View File

@@ -111,6 +111,19 @@ But the default mapping itself will stay limited.
If you want highly customizable mappings which support almost everything one can imagine you will probably be much happier with (Spring Data) JPA.
Which is a very powerful and mature technology.
=== 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<DummyEntity> 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.
@@ -254,9 +267,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
@@ -274,7 +290,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].

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.query;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.annotation.QueryAnnotation;
/**
* Annotation to provide SQL statements that will get used for executing the method.
*
* The SQL statement may contain named parameters as supported by {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}.
* Those parameters will get bound to the arguments of the annotated method.
*
* @author Jens Schauder
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@QueryAnnotation
@Documented
public @interface Query {
String value();
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.support;
import java.lang.reflect.Method;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.EntityRowMapper;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.RowMapper;
/**
* {@link QueryLookupStrategy} for JDBC repositories. Currently only supports annotated queries.
*
* @author Jens Schauder
*/
class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private final JdbcMappingContext context;
private final DataAccessStrategy accessStrategy;
JdbcQueryLookupStrategy(EvaluationContextProvider evaluationContextProvider, JdbcMappingContext context,
DataAccessStrategy accessStrategy) {
this.context = context;
this.accessStrategy = accessStrategy;
}
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory);
Class<?> domainType = queryMethod.getReturnedObjectType();
RowMapper<?> rowMapper = new EntityRowMapper<>(context.getRequiredPersistentEntity(domainType),
context.getConversions(), context, accessStrategy);
return new JdbcRepositoryQuery(queryMethod, context, rowMapper);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.support;
import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod;
/**
* {@link QueryMethod} implementation that implements a method by executing the query from a {@link Query} annotation on
* that method.
*
* Binds method arguments to named parameters in the SQL statement.
*
* @author Jens Schauder
*/
public class JdbcQueryMethod extends QueryMethod {
private final Method method;
public JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
super(method, metadata, factory);
this.method = method;
}
public String getAnnotatedQuery() {
Query queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
return queryAnnotation == null ? null : queryAnnotation.value();
}
}

View File

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

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.support;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
/**
* A query to be executed based on a repository method, it's annotated SQL query and the arguments provided to the
* method.
*
* @author Jens Schauder
*/
class JdbcRepositoryQuery implements RepositoryQuery {
private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.";
private final JdbcQueryMethod queryMethod;
private final JdbcMappingContext context;
private final RowMapper<?> rowMapper;
JdbcRepositoryQuery(JdbcQueryMethod queryMethod, JdbcMappingContext context, RowMapper rowMapper) {
this.queryMethod = queryMethod;
this.context = context;
this.rowMapper = rowMapper;
}
@Override
public Object execute(Object[] objects) {
String query = queryMethod.getAnnotatedQuery();
MapSqlParameterSource parameters = new MapSqlParameterSource();
queryMethod.getParameters().getBindableParameters().forEach(p -> {
String parameterName = p.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
parameters.addValue(parameterName, objects[p.getIndex()]);
});
return context.getTemplate().query(query, parameters, rowMapper);
}
@Override
public JdbcQueryMethod getQueryMethod() {
return queryMethod;
}
}

View File

@@ -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.NamedParameterJdbcOperations;
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<String, Object> additionalParameters = new HashMap<>();
ArgumentCaptor<SqlParameterSource> captor = ArgumentCaptor.forClass(SqlParameterSource.class);

View File

@@ -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.core.conversion.JdbcPropertyPath;
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);

View File

@@ -43,6 +43,7 @@ import org.springframework.data.convert.Jsr310Converters;
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 class EntityRowMapperUnitTests {
private <T> EntityRowMapper<T> createRowMapper(Class<T> 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.

View File

@@ -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.JdbcMappingContext;
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 @@ public class SqlGeneratorContextBasedNamingStrategyUnitTests {
*/
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));

View File

@@ -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.JdbcPersistentEntity;
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 class SqlGeneratorFixedNamingStrategyUnitTests {
*/
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));
}

View File

@@ -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.JdbcPersistentEntity;
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));
}

View File

@@ -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.annotation.Id;
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 @@ import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
@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() {

View File

@@ -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.Delete;
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 @@ import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
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() {

View File

@@ -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 @@ import org.springframework.lang.Nullable;
*/
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();

View File

@@ -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 java.util.Date;
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<JdbcPersistentProperty>) p -> {

View File

@@ -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, //

View File

@@ -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);
}
}

View File

@@ -30,7 +30,10 @@ import org.springframework.data.jdbc.core.DataAccessStrategy;
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 @@ public class TestConfiguration {
@Bean
DataAccessStrategy defaultDataAccessStrategy(JdbcMappingContext context,
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations operations) {
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations operations) {
DelegatingDataAccessStrategy accessStrategy = new DelegatingDataAccessStrategy();
@@ -98,11 +98,13 @@ public class TestConfiguration {
}
@Bean
JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy,
Optional<ConversionCustomizer> conversionCustomizer) {
JdbcMappingContext jdbcMappingContext(NamedParameterJdbcOperations template, Optional<NamingStrategy> namingStrategy,
Optional<ConversionCustomizer> conversionCustomizer) {
return new JdbcMappingContext(
namingStrategy.orElse(new DefaultNamingStrategy()),
conversionCustomizer.orElse(conversionService -> {}));
return new JdbcMappingContext( //
namingStrategy.orElse(new DefaultNamingStrategy()), //
template, //
conversionCustomizer.orElse(conversionService -> {}) //
);
}
}

View File

@@ -0,0 +1 @@
CREATE TABLE dummyentity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100))