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:
committed by
Jens Schauder
parent
eb197ee064
commit
246343f477
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE aggregate_one
|
||||
(
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE aggregate_one
|
||||
(
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE aggregate_one
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
DROP TABLE IF EXISTS aggregate_one;
|
||||
|
||||
CREATE TABLE aggregate_one
|
||||
(
|
||||
id BIGINT IDENTITY PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE aggregate_one
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
@@ -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)
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
DROP TABLE aggregate_one;
|
||||
CREATE TABLE aggregate_one
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
Reference in New Issue
Block a user