Introduced queryLookupStrategy to EnableJdbcRepositories.

Added the missing functionality that is found in the documentation of spring-data-jdbc, but was not present in the code as functionality. Users can not choose between various QueryLookupStrategies.

Closes #1043
Original pull request #1187
This commit is contained in:
Diego Krupitza
2022-02-24 12:46:28 +01:00
committed by Jens Schauder
parent eb197ee064
commit 246343f477
16 changed files with 610 additions and 31 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2021 the original author or authors.
* Copyright 2017-2022 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.
@@ -27,8 +27,8 @@ import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Annotation to enable JDBC repositories. Will scan the package of the annotated configuration class for Spring Data
@@ -39,6 +39,7 @@ import org.springframework.transaction.PlatformTransactionManager;
* @author Mark Paluch
* @author Fei Dong
* @author Antoine Sauray
* @author Diego Krupitza
* @see AbstractJdbcConfiguration
*/
@Target(ElementType.TYPE)
@@ -124,11 +125,17 @@ public @interface EnableJdbcRepositories {
*/
String dataAccessStrategyRef() default "";
/**
/**
* Configures the name of the {@link DataSourceTransactionManager} bean definition to be used to create repositories
* discovered through this annotation. Defaults to {@code transactionManager}.
*
* @since 2.1
*/
String transactionManagerRef() default "transactionManager";
/**
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
* {@link QueryLookupStrategy.Key#CREATE_IF_NOT_FOUND}.
*/
QueryLookupStrategy.Key queryLookupStrategy() default QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 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.
@@ -21,7 +21,6 @@ import java.sql.SQLException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
@@ -41,7 +40,6 @@ import org.springframework.data.relational.core.mapping.event.AfterLoadCallback;
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.RowMapper;
@@ -51,7 +49,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link QueryLookupStrategy} for JDBC repositories.
* Abstract {@link QueryLookupStrategy} for JDBC repositories.
*
* @author Jens Schauder
* @author Kazuki Shimizu
@@ -60,8 +58,9 @@ import org.springframework.util.Assert;
* @author Maciej Walkowiak
* @author Moises Cisneros
* @author Hebert Coelho
* @author Diego Krupitza
*/
class JdbcQueryLookupStrategy implements QueryLookupStrategy {
abstract class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private static final Log LOG = LogFactory.getLog(JdbcQueryLookupStrategy.class);
@@ -72,12 +71,12 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private final Dialect dialect;
private final QueryMappingConfiguration queryMappingConfiguration;
private final NamedParameterJdbcOperations operations;
private final BeanFactory beanfactory;
@Nullable private final BeanFactory beanfactory;
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
BeanFactory beanfactory) {
@Nullable BeanFactory beanfactory) {
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
@@ -96,14 +95,52 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
this.beanfactory = beanfactory;
}
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
/**
* {@link QueryLookupStrategy} to create a query from the method name.
*
* @author Diego Krupitza
*/
static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy {
JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries,
context);
CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
@Nullable BeanFactory beanfactory) {
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory);
}
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
JdbcQueryMethod queryMethod = getJdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries);
return new PartTreeJdbcQuery(getContext(), queryMethod, getDialect(), getConverter(), getOperations(),
this::createMapper);
}
}
/**
* {@link QueryLookupStrategy} that tries to detect a declared query declared via
* {@link org.springframework.data.jdbc.repository.query.Query} annotation followed by a JPA named query lookup.
*
* @author Diego Krupitza
*/
static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy {
DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
@Nullable BeanFactory beanfactory) {
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory);
}
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
JdbcQueryMethod queryMethod = getJdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries);
try {
if (namedQueries.hasQuery(queryMethod.getNamedQueryName()) || queryMethod.hasAnnotatedQuery()) {
if (queryMethod.hasAnnotatedQuery() && queryMethod.hasAnnotatedQueryName()) {
@@ -111,24 +148,156 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
"Query method %s is annotated with both, a query and a query name. Using the declared query.", method));
}
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, this::createMapper, converter);
query.setBeanFactory(beanfactory);
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, getOperations(), this::createMapper,
getConverter());
query.setBeanFactory(getBeanfactory());
return query;
} else {
return new PartTreeJdbcQuery(context, queryMethod, dialect, converter, operations, this::createMapper);
}
} catch (Exception e) {
throw QueryCreationException.create(queryMethod, e);
throw new IllegalStateException(
String.format("Did neither find a NamedQuery nor an annotated query for method %s!", method));
}
}
/**
* {@link QueryLookupStrategy} to try to detect a declared query first (
* {@link org.springframework.data.jdbc.repository.query.Query}, JDBC named query). In case none is found we fall back
* on query creation.
* <p>
* Modified based on original source: {@link org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy}
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Diego Krupitza
*/
static class CreateIfNotFoundQueryLookupStrategy extends JdbcQueryLookupStrategy {
private final DeclaredQueryLookupStrategy lookupStrategy;
private final CreateQueryLookupStrategy createStrategy;
/**
* Creates a new {@link CreateIfNotFoundQueryLookupStrategy}.
*
* @param createStrategy must not be {@literal null}.
* @param lookupStrategy must not be {@literal null}.
*/
public CreateIfNotFoundQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
@Nullable BeanFactory beanfactory, CreateQueryLookupStrategy createStrategy,
DeclaredQueryLookupStrategy lookupStrategy) {
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory);
Assert.notNull(createStrategy, "CreateQueryLookupStrategy must not be null!");
Assert.notNull(lookupStrategy, "DeclaredQueryLookupStrategy must not be null!");
this.createStrategy = createStrategy;
this.lookupStrategy = lookupStrategy;
}
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
try {
return lookupStrategy.resolveQuery(method, repositoryMetadata, projectionFactory, namedQueries);
} catch (IllegalStateException e) {
return createStrategy.resolveQuery(method, repositoryMetadata, projectionFactory, namedQueries);
}
}
}
/**
* Creates a {@link JdbcQueryMethod} based on the parameters
*/
JdbcQueryMethod getJdbcQueryMethod(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
return new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries, context);
}
/**
* Creates a {@link QueryLookupStrategy} based on the provided
* {@link org.springframework.data.repository.query.QueryLookupStrategy.Key}.
*
* @param key the key that decides what {@link QueryLookupStrategy} shozld be used.
* @param publisher must not be {@literal null}
* @param callbacks may be {@literal null}
* @param context must not be {@literal null}
* @param converter must not be {@literal null}
* @param dialect must not be {@literal null}
* @param queryMappingConfiguration must not be {@literal null}
* @param operations must not be {@literal null}
* @param beanfactory may be {@literal null}
*/
public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPublisher publisher,
@Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
@Nullable BeanFactory beanfactory) {
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
Assert.notNull(converter, "JdbcConverter must not be null");
Assert.notNull(dialect, "Dialect must not be null");
Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, context,
converter, dialect, queryMappingConfiguration, operations, beanfactory);
DeclaredQueryLookupStrategy declaredQueryLookupStrategy = new DeclaredQueryLookupStrategy(publisher, callbacks,
context, converter, dialect, queryMappingConfiguration, operations, beanfactory);
Key cleanedKey = key != null ? key : Key.CREATE_IF_NOT_FOUND;
LOG.debug(String.format("Using the queryLookupStrategy %s", cleanedKey));
switch (cleanedKey) {
case CREATE:
return createQueryLookupStrategy;
case USE_DECLARED_QUERY:
return declaredQueryLookupStrategy;
case CREATE_IF_NOT_FOUND:
return new CreateIfNotFoundQueryLookupStrategy(publisher, callbacks, context, converter, dialect,
queryMappingConfiguration, operations, beanfactory, createQueryLookupStrategy, declaredQueryLookupStrategy);
default:
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
}
}
protected ApplicationEventPublisher getPublisher() {
return publisher;
}
protected RelationalMappingContext getContext() {
return context;
}
protected JdbcConverter getConverter() {
return converter;
}
protected Dialect getDialect() {
return dialect;
}
protected NamedParameterJdbcOperations getOperations() {
return operations;
}
@Nullable
protected BeanFactory getBeanfactory() {
return beanfactory;
}
@SuppressWarnings("unchecked")
private RowMapper<Object> createMapper(Class<?> returnedObjectType) {
RowMapper<Object> createMapper(Class<?> returnedObjectType) {
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(returnedObjectType);
if (persistentEntity == null) {
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType, converter.getConversionService());
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType,
converter.getConversionService());
}
return (RowMapper<Object>) determineDefaultMapper(returnedObjectType);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2021 the original author or authors.
* Copyright 2017-2022 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.
@@ -46,6 +46,7 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @author Mark Paluch
* @author Hebert Coelho
* @author Diego Krupitza
*/
public class JdbcRepositoryFactory extends RepositoryFactorySupport {
@@ -133,7 +134,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return Optional.of(new JdbcQueryLookupStrategy(publisher, entityCallbacks, context, converter, dialect,
return Optional.of(JdbcQueryLookupStrategy.create(key, publisher, entityCallbacks, context, converter, dialect,
queryMappingConfiguration, operations, beanFactory));
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2022 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.repository;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
/**
* Base class to test <code>@EnableJdbcRepositories(queryLookupStrategy = ...)</code> Tests based on logic from
* {@link org.springframework.data.jdbc.repository.support.JdbcQueryLookupStrategy}
*
* @author Diego Krupitza
*/
abstract class AbstractJdbcRepositoryLookUpStrategyIntegrationTests {
@Autowired protected OnesRepository onesRepository;
@Autowired NamedParameterJdbcTemplate template;
@Autowired RelationalMappingContext context;
protected void insertTestInstances() {
AggregateOne firstAggregate = new AggregateOne("Diego");
AggregateOne secondAggregate = new AggregateOne("Franz");
AggregateOne thirdAggregate = new AggregateOne("Daniela");
onesRepository.saveAll(Arrays.asList(firstAggregate, secondAggregate, thirdAggregate));
}
protected void callDeclaredQuery(String name, int expectedSize, String... expectedNames) {
insertTestInstances();
List<AggregateOne> likeNameD = onesRepository.findAllByName(name);
assertThat(likeNameD).hasSize(expectedSize);
assertThat(likeNameD.stream().map(item -> item.name).collect(Collectors.toList())) //
.contains(expectedNames);
}
protected void callDerivedQuery() {
insertTestInstances();
AggregateOne diego = onesRepository.findByName("Diego");
assertThat(diego).isNotNull();
assertThat(diego.id).isNotNull();
assertThat(diego.name).isEqualToIgnoringCase("Diego");
}
interface OnesRepository extends CrudRepository<AggregateOne, Long> {
// if derived is used it is just a basic findByName
// if declared is used it should be a like check
@Query("Select * from aggregate_one where NAME like concat('%', :name, '%') ")
List<AggregateOne> findAllByName(String name);
AggregateOne findByName(String name);
}
static class AggregateOne {
@Id Long id;
String name;
public AggregateOne(String name) {
this.name = name;
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2022 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.repository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;
/**
* Test to verify that
* <code>@EnableJdbcRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)</code> works as
* intended. Tests based on logic from
* {@link org.springframework.data.jdbc.repository.support.JdbcQueryLookupStrategy.CreateIfNotFoundQueryLookupStrategy}
*
* @author Diego Krupitza
*/
@ContextConfiguration
@Transactional
@ActiveProfiles("hsql")
@ExtendWith(SpringExtension.class)
class JdbcRepositoryCreateIfNotFoundLookUpStrategyIntegrationTests
extends AbstractJdbcRepositoryLookUpStrategyIntegrationTests {
@Test
void declaredQueryShouldWork() {
onesRepository.deleteAll();
callDeclaredQuery("D", 2, "Diego", "Daniela");
}
@Test
void derivedQueryShouldWork() {
onesRepository.deleteAll();
callDerivedQuery();
}
@Configuration
@Import(TestConfiguration.class)
@EnableJdbcRepositories(considerNestedRepositories = true,
queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND,
includeFilters = @ComponentScan.Filter(
value = AbstractJdbcRepositoryLookUpStrategyIntegrationTests.OnesRepository.class,
type = FilterType.ASSIGNABLE_TYPE))
static class Config {
@Autowired JdbcRepositoryFactory factory;
@Bean
Class<?> testClass() {
return AbstractJdbcRepositoryLookUpStrategyIntegrationTests.class;
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2022 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.repository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;
/**
* Test to verify that <code>@EnableJdbcRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE)</code> works
* as intended. Tests based on logic from
* {@link org.springframework.data.jdbc.repository.support.JdbcQueryLookupStrategy.CreateQueryLookupStrategy}
*
* @author Diego Krupitza
*/
@ContextConfiguration
@Transactional
@ActiveProfiles("hsql")
@ExtendWith(SpringExtension.class)
class JdbcRepositoryCreateLookUpStrategyIntegrationTests extends AbstractJdbcRepositoryLookUpStrategyIntegrationTests {
@Test
void declaredQueryShouldWork() {
onesRepository.deleteAll();
// here the declared query will use the dervice query which does something totally different
callDeclaredQuery("D", 0);
}
@Test
void derivedQueryShouldWork() {
onesRepository.deleteAll();
callDerivedQuery();
}
@Configuration
@Import(TestConfiguration.class)
@EnableJdbcRepositories(considerNestedRepositories = true, queryLookupStrategy = QueryLookupStrategy.Key.CREATE,
includeFilters = @ComponentScan.Filter(value = OnesRepository.class, type = FilterType.ASSIGNABLE_TYPE))
static class Config {
@Autowired JdbcRepositoryFactory factory;
@Bean
Class<?> testClass() {
return AbstractJdbcRepositoryLookUpStrategyIntegrationTests.class;
}
}
}

View File

@@ -0,0 +1,60 @@
package org.springframework.data.jdbc.repository;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.query.QueryLookupStrategy;
/**
* Test to verify that
* <code>@EnableJdbcRepositories(queryLookupStrategy = QueryLookupStrategy.Key.USE_DECLARED_QUERY)</code> works as
* intended. Tests based on logic from
* {@link org.springframework.data.jdbc.repository.support.JdbcQueryLookupStrategy.DeclaredQueryLookupStrategy}
*
* @author Diego Krupitza
*/
class JdbcRepositoryDeclaredLookUpStrategyIntegrationTests
extends AbstractJdbcRepositoryLookUpStrategyIntegrationTests {
@Test
void contextCannotByCreatedDueToFindByNameNotDeclaredQuery() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(JdbcRepositoryDeclaredLookUpStrategyIntegrationTests.Config.class);
assertThatThrownBy(() -> {
context.refresh();
context.getBean(OnesRepository.class);
}).hasMessageContaining("findByName");
}
}
@Configuration
@Import(TestConfiguration.class)
@EnableJdbcRepositories(considerNestedRepositories = true,
queryLookupStrategy = QueryLookupStrategy.Key.USE_DECLARED_QUERY,
includeFilters = @ComponentScan.Filter(
value = AbstractJdbcRepositoryLookUpStrategyIntegrationTests.OnesRepository.class,
type = FilterType.ASSIGNABLE_TYPE))
static class Config {
@Autowired JdbcRepositoryFactory factory;
@Bean
Class<?> testClass() {
return AbstractJdbcRepositoryLookUpStrategyIntegrationTests.class;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 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.
@@ -15,15 +15,20 @@
*/
package org.springframework.data.jdbc.repository.support;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
@@ -35,6 +40,7 @@ import org.springframework.data.relational.core.dialect.H2Dialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.jdbc.core.RowMapper;
@@ -52,6 +58,7 @@ import org.springframework.util.ReflectionUtils;
* @author Evgeni Dimitrov
* @author Mark Paluch
* @author Hebert Coelho
* @author Diego Krupitza
*/
class JdbcQueryLookupStrategyUnitTests {
@@ -81,7 +88,8 @@ class JdbcQueryLookupStrategyUnitTests {
QueryMappingConfiguration mappingConfiguration = new DefaultQueryMappingConfiguration()
.registerRowMapper(NumberFormat.class, numberFormatMapper);
RepositoryQuery repositoryQuery = getRepositoryQuery("returningNumberFormat", mappingConfiguration);
RepositoryQuery repositoryQuery = getRepositoryQuery(QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND,
"returningNumberFormat", mappingConfiguration);
repositoryQuery.execute(new Object[] {});
@@ -95,16 +103,55 @@ class JdbcQueryLookupStrategyUnitTests {
QueryMappingConfiguration mappingConfiguration = new DefaultQueryMappingConfiguration()
.registerRowMapper(NumberFormat.class, numberFormatMapper);
RepositoryQuery repositoryQuery = getRepositoryQuery("annotatedQueryWithQueryAndQueryName", mappingConfiguration);
RepositoryQuery repositoryQuery = getRepositoryQuery(QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND,
"annotatedQueryWithQueryAndQueryName", mappingConfiguration);
repositoryQuery.execute(new Object[] {});
verify(operations).queryForObject(eq("some SQL"), any(SqlParameterSource.class), any(RowMapper.class));
}
private RepositoryQuery getRepositoryQuery(String name, QueryMappingConfiguration mappingConfiguration) {
@Test
void shouldFailOnMissingDeclaredQuery() {
JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(publisher, callbacks, mappingContext,
RowMapper<? extends NumberFormat> numberFormatMapper = mock(RowMapper.class);
QueryMappingConfiguration mappingConfiguration = new DefaultQueryMappingConfiguration()
.registerRowMapper(NumberFormat.class, numberFormatMapper);
assertThatThrownBy(
() -> getRepositoryQuery(QueryLookupStrategy.Key.USE_DECLARED_QUERY, "findByName", mappingConfiguration))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Did neither find a NamedQuery nor an annotated query for method")
.hasMessageContaining("findByName");
}
@ParameterizedTest
@MethodSource("correctLookUpStrategyForKeySource")
void correctLookUpStrategyForKey(QueryLookupStrategy.Key key, Class expectedClass) {
RowMapper<? extends NumberFormat> numberFormatMapper = mock(RowMapper.class);
QueryMappingConfiguration mappingConfiguration = new DefaultQueryMappingConfiguration()
.registerRowMapper(NumberFormat.class, numberFormatMapper);
QueryLookupStrategy queryLookupStrategy = JdbcQueryLookupStrategy.create(key, publisher, callbacks, mappingContext,
converter, H2Dialect.INSTANCE, mappingConfiguration, operations, null);
assertThat(queryLookupStrategy).isInstanceOf(expectedClass);
}
private static Stream<Arguments> correctLookUpStrategyForKeySource() {
return Stream.of( //
Arguments.of(QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND,
JdbcQueryLookupStrategy.CreateIfNotFoundQueryLookupStrategy.class), //
Arguments.of(QueryLookupStrategy.Key.CREATE, JdbcQueryLookupStrategy.CreateQueryLookupStrategy.class), //
Arguments.of(QueryLookupStrategy.Key.USE_DECLARED_QUERY,
JdbcQueryLookupStrategy.DeclaredQueryLookupStrategy.class) //
);
}
private RepositoryQuery getRepositoryQuery(QueryLookupStrategy.Key key, String name,
QueryMappingConfiguration mappingConfiguration) {
QueryLookupStrategy queryLookupStrategy = JdbcQueryLookupStrategy.create(key, publisher, callbacks, mappingContext,
converter, H2Dialect.INSTANCE, mappingConfiguration, operations, null);
Method method = ReflectionUtils.findMethod(MyRepository.class, name);
@@ -119,5 +166,7 @@ class JdbcQueryLookupStrategyUnitTests {
@Query(value = "some SQL", name = "query-name")
void annotatedQueryWithQueryAndQueryName();
NumberFormat findByName();
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
CREATE TABLE aggregate_one
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(100)
);

View File

@@ -0,0 +1,7 @@
DROP TABLE IF EXISTS aggregate_one;
CREATE TABLE aggregate_one
(
id BIGINT IDENTITY PRIMARY KEY,
NAME VARCHAR(100)
);

View File

@@ -0,0 +1,5 @@
CREATE TABLE aggregate_one
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(100)
);

View File

@@ -0,0 +1,7 @@
DROP TABLE aggregate_one CASCADE CONSTRAINTS PURGE;
CREATE TABLE aggregate_one
(
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
NAME VARCHAR2(100)
);

View File

@@ -0,0 +1,6 @@
DROP TABLE aggregate_one;
CREATE TABLE aggregate_one
(
id SERIAL PRIMARY KEY,
NAME VARCHAR(100)
);