diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java index ef6844ad2..19f903a66 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java @@ -24,6 +24,8 @@ import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.jdbc.core.convert.DataAccessStrategy; +import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.relational.core.query.Query; import org.springframework.lang.Nullable; @@ -37,6 +39,7 @@ import org.springframework.lang.Nullable; * @author Diego Krupitza * @author Myeonghyeon Lee * @author Sergey Korotaev + * @author Tomohiko Ozawa */ public interface JdbcAggregateOperations { @@ -324,4 +327,18 @@ public interface JdbcAggregateOperations { * @param the type of the aggregate roots. */ void deleteAll(Iterable aggregateRoots); + + /** + * Returns the {@link JdbcConverter}. + * + * @return the {@link JdbcConverter}. + */ + JdbcConverter getConverter(); + + /** + * Return the {@link DataAccessStrategy} + * + * @return the {@link DataAccessStrategy} + */ + DataAccessStrategy getDataAccessStrategy(); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java index 2db2a3f18..0819562f8 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java @@ -70,6 +70,7 @@ import org.springframework.util.ClassUtils; * @author Diego Krupitza * @author Sergey Korotaev * @author Mikhail Polivakha + * @author Tomohiko Ozawa */ public class JdbcAggregateTemplate implements JdbcAggregateOperations { @@ -84,6 +85,57 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { private EntityCallbacks entityCallbacks = EntityCallbacks.create(); + /** + * Creates a new {@link JdbcAggregateTemplate} given {@link ApplicationContext}, {@link RelationalMappingContext} and + * {@link DataAccessStrategy}. + * + * @param publisher must not be {@literal null}. + * @param dataAccessStrategy must not be {@literal null}. + * @since 3.3 + */ + public JdbcAggregateTemplate(ApplicationContext publisher, JdbcConverter converter, + DataAccessStrategy dataAccessStrategy) { + + Assert.notNull(publisher, "ApplicationContext must not be null"); + Assert.notNull(converter, "RelationalConverter must not be null"); + Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null"); + + this.eventDelegate.setPublisher(publisher); + this.converter = converter; + this.accessStrategy = dataAccessStrategy; + this.context = converter.getMappingContext(); + + this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context); + + this.executor = new AggregateChangeExecutor(converter, accessStrategy); + + setEntityCallbacks(EntityCallbacks.create(publisher)); + } + + /** + * Creates a new {@link JdbcAggregateTemplate} given {@link ApplicationEventPublisher}, + * {@link RelationalMappingContext} and {@link DataAccessStrategy}. + * + * @param publisher must not be {@literal null}. + * @param dataAccessStrategy must not be {@literal null}. + * @since 3.3 + */ + public JdbcAggregateTemplate(ApplicationEventPublisher publisher, JdbcConverter converter, + DataAccessStrategy dataAccessStrategy) { + + Assert.notNull(publisher, "ApplicationEventPublisher must not be null"); + Assert.notNull(converter, "RelationalConverter must not be null"); + Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null"); + + this.eventDelegate.setPublisher(publisher); + this.converter = converter; + this.accessStrategy = dataAccessStrategy; + this.context = converter.getMappingContext(); + + this.jdbcEntityDeleteWriter = new RelationalEntityDeleteWriter(context); + this.executor = new AggregateChangeExecutor(converter, accessStrategy); + } + /** * Creates a new {@link JdbcAggregateTemplate} given {@link ApplicationContext}, {@link RelationalMappingContext} and * {@link DataAccessStrategy}. @@ -92,7 +144,10 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * @param context must not be {@literal null}. * @param dataAccessStrategy must not be {@literal null}. * @since 1.1 + * @deprecated since 3.3, use {@link JdbcAggregateTemplate(ApplicationContext, JdbcConverter, DataAccessStrategy)} + * instead. */ + @Deprecated(since = "3.3") public JdbcAggregateTemplate(ApplicationContext publisher, RelationalMappingContext context, JdbcConverter converter, DataAccessStrategy dataAccessStrategy) { @@ -120,7 +175,10 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { * @param publisher must not be {@literal null}. * @param context must not be {@literal null}. * @param dataAccessStrategy must not be {@literal null}. + * @deprecated since 3.3, use {@link JdbcAggregateTemplate(ApplicationEventPublisher, JdbcConverter, + * DataAccessStrategy)} instead. */ + @Deprecated(since = "3.3") public JdbcAggregateTemplate(ApplicationEventPublisher publisher, RelationalMappingContext context, JdbcConverter converter, DataAccessStrategy dataAccessStrategy) { @@ -705,4 +763,14 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations { private interface AggregateChangeCreator { RootAggregateChange createAggregateChange(T instance); } + + @Override + public DataAccessStrategy getDataAccessStrategy() { + return accessStrategy; + } + + @Override + public JdbcConverter getConverter() { + return converter; + } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/dialect/DialectResolver.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/dialect/DialectResolver.java new file mode 100644 index 000000000..9121b12c9 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/dialect/DialectResolver.java @@ -0,0 +1,105 @@ +/* + * Copyright 2020-2025 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.dialect; + +import java.sql.Connection; +import java.util.Optional; + +import javax.sql.DataSource; + +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.data.jdbc.core.dialect.JdbcDialect; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.jdbc.core.JdbcOperations; + +/** + * Resolves a {@link Dialect}. Resolution typically uses {@link JdbcOperations} to obtain and inspect a + * {@link Connection}. Dialect resolution uses Spring's {@link SpringFactoriesLoader spring.factories} to determine + * available {@link JdbcDialectProvider extensions}. + * + * @author Jens Schauder + * @author Mikhail Polivakha + * @since 2.0 + * @see Dialect + * @see SpringFactoriesLoader + * @deprecated since 3.5, replacement {@link org.springframework.data.jdbc.core.dialect.DialectResolver} was moved to + * the {@link org.springframework.data.jdbc.core.dialect} package. + */ +@Deprecated(since = "3.5", forRemoval = true) +public class DialectResolver { + + // utility constructor. + private DialectResolver() {} + + /** + * Retrieve a {@link Dialect} by inspecting a {@link Connection}. + * + * @param operations must not be {@literal null}. + * @return the resolved {@link Dialect} {@link NoDialectException} if the database type cannot be determined from + * {@link DataSource}. + * @throws NoDialectException if no {@link Dialect} can be found. + */ + public static JdbcDialect getDialect(JdbcOperations operations) { + return org.springframework.data.jdbc.core.dialect.DialectResolver.getDialect(operations); + } + + /** + * SPI to extend Spring's default JDBC Dialect discovery mechanism. Implementations of this interface are discovered + * through Spring's {@link SpringFactoriesLoader} mechanism. + * + * @author Jens Schauder + * @see org.springframework.core.io.support.SpringFactoriesLoader + * @deprecated since 3.5, replacement {@link org.springframework.data.jdbc.core.dialect.DialectResolver} was moved to + * the {@link org.springframework.data.jdbc.core.dialect} package. + */ + @Deprecated(since = "3.5", forRemoval = true) + public interface JdbcDialectProvider + extends org.springframework.data.jdbc.core.dialect.DialectResolver.JdbcDialectProvider { + + /** + * Returns a {@link Dialect} for a {@link DataSource}. + * + * @param operations the {@link JdbcOperations} to be used with the {@link Dialect}. + * @return {@link Optional} containing the {@link Dialect} if the {@link JdbcDialectProvider} can provide a dialect + * object, otherwise {@link Optional#empty()}. + */ + Optional getDialect(JdbcOperations operations); + } + + @Deprecated(since = "3.5", forRemoval = true) + static public class DefaultDialectProvider extends + org.springframework.data.jdbc.core.dialect.DialectResolver.DefaultDialectProvider implements JdbcDialectProvider { + + } + + /** + * Exception thrown when {@link DialectResolver} cannot resolve a {@link Dialect}. + */ + @Deprecated(since = "3.5", forRemoval = true) + public static class NoDialectException + extends org.springframework.data.jdbc.core.dialect.DialectResolver.NoDialectException { + + /** + * Constructor for NoDialectFoundException. + * + * @param msg the detail message + */ + NoDialectException(String msg) { + super(msg); + } + } + +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java index 17824fe6f..ac7ef8075 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java @@ -41,6 +41,7 @@ import org.springframework.data.jdbc.core.dialect.JdbcArrayColumns; import org.springframework.data.jdbc.core.dialect.JdbcDialect; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; +import org.springframework.data.jdbc.dialect.DialectResolver; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.RelationalManagedTypes; import org.springframework.data.relational.core.conversion.RelationalConverter; @@ -65,6 +66,7 @@ import org.springframework.util.StringUtils; * @author Myeonghyeon Lee * @author Chirag Tailor * @author Mikhail Polivakha + * @author Tomohiko Ozawa * @since 1.1 */ @Configuration(proxyBeanMethods = false) @@ -221,7 +223,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationContext applicationContext, JdbcMappingContext mappingContext, JdbcConverter converter, DataAccessStrategy dataAccessStrategy) { - return new JdbcAggregateTemplate(applicationContext, mappingContext, converter, dataAccessStrategy); + return new JdbcAggregateTemplate(applicationContext, converter, dataAccessStrategy); } /** @@ -249,8 +251,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { * @param operations the {@link NamedParameterJdbcOperations} allowing access to a {@link java.sql.Connection}. * @return the {@link Dialect} to be used. * @since 2.0 - * @throws org.springframework.data.jdbc.repository.config.DialectResolver.NoDialectException if the {@link Dialect} - * cannot be determined. + * @throws DialectResolver.NoDialectException if the {@link Dialect} cannot be determined. */ @Bean public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java index e42547c54..82dfd9997 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java @@ -131,7 +131,9 @@ public @interface EnableJdbcRepositories { /** * Configures the name of the {@link org.springframework.data.jdbc.core.convert.DataAccessStrategy} bean definition to * be used to create repositories discovered through this annotation. Defaults to {@code defaultDataAccessStrategy}. + * @deprecated since 3.3 use {@link #jdbcAggregateOperationsRef()} instead */ + @Deprecated(since = "3.3") String dataAccessStrategyRef() default ""; /** @@ -142,6 +144,12 @@ public @interface EnableJdbcRepositories { */ String transactionManagerRef() default "transactionManager"; + /** + * Configure the name of the {@link org.springframework.data.jdbc.core.JdbcAggregateOperations} bean definition to be + * used to create repositories discovered through this annotation. + */ + String jdbcAggregateOperationsRef() default ""; + /** * 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}. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java index 64e05f0fb..9e098a46e 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java @@ -40,6 +40,7 @@ import org.springframework.util.StringUtils; * @author Fei Dong * @author Mark Paluch * @author Antoine Sauray + * @author Tomohiko Ozawa */ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport { @@ -79,9 +80,15 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens Optional transactionManagerRef = source.getAttribute("transactionManagerRef"); builder.addPropertyValue("transactionManager", transactionManagerRef.orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)); - builder.addPropertyValue("mappingContext", new RuntimeBeanReference(JdbcMappingContext.class)); - builder.addPropertyValue("dialect", new RuntimeBeanReference(Dialect.class)); - builder.addPropertyValue("converter", new RuntimeBeanReference(JdbcConverter.class)); + Optional jdbcAggregateOperationsRef = source.getAttribute("jdbcAggregateOperationsRef"); + + if (jdbcAggregateOperationsRef.isPresent()) { + builder.addPropertyReference("jdbcAggregateOperations", jdbcAggregateOperationsRef.get()); + } else { + builder.addPropertyValue("mappingContext", new RuntimeBeanReference(JdbcMappingContext.class)); + builder.addPropertyValue("dialect", new RuntimeBeanReference(Dialect.class)); + builder.addPropertyValue("converter", new RuntimeBeanReference(JdbcConverter.class)); + } } /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index c9b54ac8b..0ff4f00a2 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -19,9 +19,11 @@ import java.util.Optional; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.jdbc.core.JdbcAggregateOperations; import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.JdbcConverter; +import org.springframework.data.jdbc.dialect.DialectResolver; import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.relational.core.dialect.Dialect; @@ -50,6 +52,7 @@ import org.springframework.util.Assert; * @author Diego Krupitza * @author Christopher Klein * @author Marcin Grzejszczak + * @author Tomohiko Ozawa */ public class JdbcRepositoryFactory extends RepositoryFactorySupport { @@ -64,6 +67,20 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY; private EntityCallbacks entityCallbacks; + public JdbcRepositoryFactory(ApplicationEventPublisher publisher, JdbcAggregateOperations jdbcAggregateOperations, + NamedParameterJdbcOperations operations) { + Assert.notNull(publisher, "ApplicationEventPublisher must not be null"); + Assert.notNull(jdbcAggregateOperations, "JdbcAggregateOperations must not be null"); + Assert.notNull(operations, "NamedParameterJdbcOperations must not be null"); + + this.converter = jdbcAggregateOperations.getConverter(); + this.accessStrategy = jdbcAggregateOperations.getDataAccessStrategy(); + this.context = jdbcAggregateOperations.getConverter().getMappingContext(); + this.dialect = DialectResolver.getDialect(operations.getJdbcOperations()); + this.operations = operations; + this.publisher = publisher; + } + /** * Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy}, * {@link RelationalMappingContext} and {@link ApplicationEventPublisher}. @@ -115,7 +132,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { @Override protected Object getTargetRepository(RepositoryInformation repositoryInformation) { - JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, context, converter, accessStrategy); + JdbcAggregateTemplate template = new JdbcAggregateTemplate(publisher, converter, accessStrategy); if (entityCallbacks != null) { template.setEntityCallbacks(entityCallbacks); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 0d56c6b15..fa7d39089 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -22,6 +22,8 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.data.jdbc.core.JdbcAggregateOperations; +import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.DataAccessStrategyFactory; import org.springframework.data.jdbc.core.convert.InsertStrategyFactory; @@ -64,6 +66,7 @@ import org.springframework.util.Assert; * @author Chirag Tailor * @author Mikhail Polivakha * @author Sergey Korotaev + * @author Tomohiko Ozawa */ public class JdbcRepositoryFactoryBean, S, ID extends Serializable> extends TransactionalRepositoryFactoryBeanSupport implements ApplicationEventPublisherAware { @@ -75,6 +78,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend private @Nullable DataAccessStrategy dataAccessStrategy; private @Nullable QueryMappingConfiguration queryMappingConfiguration; private @Nullable NamedParameterJdbcOperations operations; + private JdbcAggregateOperations aggregateOperations; private EntityCallbacks entityCallbacks = EntityCallbacks.create(); private @Nullable Dialect dialect; @@ -109,8 +113,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend Assert.state(this.operations != null, "NamedParameterJdbcOperations is required and must not be null"); Assert.state(this.queryMappingConfiguration != null, "RelationalConverter is required and must not be null"); - JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(dataAccessStrategy, mappingContext, - converter, dialect, publisher, operations); + JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(publisher, aggregateOperations, operations); jdbcRepositoryFactory.setQueryMappingConfiguration(queryMappingConfiguration); jdbcRepositoryFactory.setEntityCallbacks(entityCallbacks); jdbcRepositoryFactory.setBeanFactory(beanFactory); @@ -161,6 +164,10 @@ public class JdbcRepositoryFactoryBean, S, ID extend this.operations = operations; } + public void setJdbcAggregateOperations(JdbcAggregateOperations jdbcAggregateOperations) { + this.aggregateOperations = jdbcAggregateOperations; + } + public void setConverter(JdbcConverter converter) { Assert.notNull(converter, "JdbcConverter must not be null"); @@ -192,6 +199,9 @@ public class JdbcRepositoryFactoryBean, S, ID extend this.operations = this.beanFactory.getBean(NamedParameterJdbcOperations.class); } + aggregateOperations = new JdbcAggregateTemplate(publisher, converter, dataAccessStrategy); + } + if (this.queryMappingConfiguration == null) { if (this.beanFactory == null) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java index 28f7f1ed2..0f865fe4b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java @@ -46,8 +46,6 @@ import org.springframework.data.annotation.Version; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Persistable; import org.springframework.data.domain.Sort; -import org.springframework.data.jdbc.core.convert.DataAccessStrategy; -import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.testing.EnabledOnFeature; import org.springframework.data.jdbc.testing.IntegrationTest; import org.springframework.data.jdbc.testing.TestClass; @@ -1384,7 +1382,8 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests { void mapWithEnumKey() { EnumMapOwner enumMapOwner = template - .save(new EnumMapOwner(null, "OwnerName", Map.of(Color.BLUE, new MapElement("Element")))); + .save( + new EnumMapOwner(null, "OwnerName", Map.of(Color.BLUE, new MapElement("Element")))); Iterable enumMapOwners = template.findAll(EnumMapOwner.class); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java index e9a4a7ee1..a8a86523e 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java @@ -23,18 +23,14 @@ import java.util.Objects; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; 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.core.convert.DataAccessStrategy; -import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.testing.DatabaseType; import org.springframework.data.jdbc.testing.EnabledOnDatabase; import org.springframework.data.jdbc.testing.IntegrationTest; import org.springframework.data.jdbc.testing.TestConfiguration; -import org.springframework.data.relational.core.mapping.RelationalMappingContext; /** * Integration tests for {@link JdbcAggregateTemplate} and it's handling of immutable entities. @@ -602,11 +598,5 @@ public class ImmutableAggregateTemplateHsqlIntegrationTests { Class testClass() { return ImmutableAggregateTemplateHsqlIntegrationTests.class; } - - @Bean - JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context, - DataAccessStrategy dataAccessStrategy, JdbcConverter converter) { - return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); - } } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java index b10493553..0bbceebfb 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java @@ -16,22 +16,16 @@ package org.springframework.data.jdbc.core; import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; 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.core.convert.DataAccessStrategy; -import org.springframework.data.jdbc.core.convert.JdbcConverter; -import org.springframework.data.jdbc.testing.EnabledOnFeature; import org.springframework.data.jdbc.testing.IntegrationTest; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.mapping.NamingStrategy; -import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** @@ -86,12 +80,6 @@ public class JdbcAggregateTemplateSchemaIntegrationTests { return JdbcAggregateTemplateSchemaIntegrationTests.class; } - @Bean - JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context, - DataAccessStrategy dataAccessStrategy, JdbcConverter converter) { - return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); - } - @Bean NamingStrategy namingStrategy() { return new NamingStrategy() { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java index f5af0c6ba..f5b015708 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java @@ -77,7 +77,7 @@ public class JdbcAggregateTemplateUnitTests { RelationalMappingContext mappingContext = new RelationalMappingContext(); JdbcConverter converter = new MappingJdbcConverter(mappingContext, relationResolver); - template = new JdbcAggregateTemplate(eventPublisher, mappingContext, converter, dataAccessStrategy); + template = new JdbcAggregateTemplate(eventPublisher, converter, dataAccessStrategy); template.setEntityCallbacks(callbacks); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java index 90a454498..abe29bf0d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java @@ -27,12 +27,14 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; 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.annotation.Id; +import org.springframework.data.jdbc.core.JdbcAggregateOperations; import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.DataAccessStrategyFactory; @@ -41,6 +43,8 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration; import org.springframework.data.jdbc.core.convert.SqlGeneratorSource; import org.springframework.data.jdbc.core.convert.SqlParametersFactory; +import org.springframework.data.jdbc.dialect.DialectResolver; +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean; import org.springframework.data.jdbc.testing.IntegrationTest; import org.springframework.data.jdbc.testing.TestConfiguration; @@ -72,6 +76,8 @@ public class EnableJdbcRepositoriesIntegrationTests { static final Field OPERATIONS = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "operations"); static final Field DATA_ACCESS_STRATEGY = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "dataAccessStrategy"); + static final Field AGGREGATE_OPERATIONS = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, + "aggregateOperations"); public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class); public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class); @@ -82,9 +88,15 @@ public class EnableJdbcRepositoriesIntegrationTests { @Autowired @Qualifier("defaultDataAccessStrategy") DataAccessStrategy defaultDataAccessStrategy; @Autowired + @Qualifier("jdbcAggregateOperations") + JdbcAggregateOperations defaultJdbcAggregateOperations; + @Autowired @Qualifier("qualifierJdbcOperations") NamedParameterJdbcOperations qualifierJdbcOperations; @Autowired @Qualifier("qualifierDataAccessStrategy") DataAccessStrategy qualifierDataAccessStrategy; + @Autowired + @Qualifier("qualifierJdbcAggregateOperations") + JdbcAggregateOperations qualifierJdbcAggregateOperations; @BeforeAll public static void setup() { @@ -92,6 +104,7 @@ public class EnableJdbcRepositoriesIntegrationTests { MAPPER_MAP.setAccessible(true); OPERATIONS.setAccessible(true); DATA_ACCESS_STRATEGY.setAccessible(true); + AGGREGATE_OPERATIONS.setAccessible(true); } @Test // DATAJDBC-100 @@ -126,6 +139,19 @@ public class EnableJdbcRepositoriesIntegrationTests { assertThat(dataAccessStrategy).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierDataAccessStrategy); } + @Test // GH-1704 + public void jdbcAggregateOperationsRef() { + + JdbcAggregateOperations aggregateOperations = (JdbcAggregateOperations) ReflectionUtils.getField( + AGGREGATE_OPERATIONS, factoryBean); + assertThat(aggregateOperations).isNotSameAs(defaultJdbcAggregateOperations) + .isSameAs(qualifierJdbcAggregateOperations); + + DataAccessStrategy dataAccessStrategy = (DataAccessStrategy) ReflectionUtils.getField(DATA_ACCESS_STRATEGY, + factoryBean); + assertThat(dataAccessStrategy).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierDataAccessStrategy); + } + interface DummyRepository extends CrudRepository { } @@ -146,6 +172,7 @@ public class EnableJdbcRepositoriesIntegrationTests { @EnableJdbcRepositories(considerNestedRepositories = true, includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DummyRepository.class), jdbcOperationsRef = "qualifierJdbcOperations", dataAccessStrategyRef = "qualifierDataAccessStrategy", + jdbcAggregateOperationsRef = "qualifierJdbcAggregateOperations", repositoryBaseClass = DummyRepositoryBaseClass.class) @Import(TestConfiguration.class) static class Config { @@ -176,6 +203,12 @@ public class EnableJdbcRepositoriesIntegrationTests { Dialect jdbcDialect(@Qualifier("qualifierJdbcOperations") NamedParameterJdbcOperations operations) { return DialectResolver.getDialect(operations.getJdbcOperations()); } + + @Bean("qualifierJdbcAggregateOperations") + JdbcAggregateOperations jdbcAggregateOperations(ApplicationContext publisher, JdbcConverter converter, + @Qualifier("qualifierDataAccessStrategy") DataAccessStrategy dataAccessStrategy) { + return new JdbcAggregateTemplate(publisher, converter, dataAccessStrategy); + } } private static class DummyRepositoryBaseClass implements CrudRepository { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java index 2e3bbd8e3..dcd5a70b9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java @@ -40,8 +40,11 @@ import org.springframework.data.jdbc.core.convert.MappingJdbcConverter; import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.H2Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.test.util.ReflectionTestUtils; @@ -66,7 +69,12 @@ class JdbcRepositoryFactoryBeanUnitTests { @Mock(answer = Answers.RETURNS_DEEP_STUBS) ListableBeanFactory beanFactory; @Mock Dialect dialect; - private RelationalMappingContext mappingContext; + @Mock + JdbcOperations operations; + @Mock + NamedParameterJdbcOperations namedParameterJdbcOperations; + + RelationalMappingContext mappingContext; @BeforeEach void setUp() { @@ -76,7 +84,9 @@ class JdbcRepositoryFactoryBeanUnitTests { // Setup standard configuration factoryBean = new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class); - when(beanFactory.getBean(NamedParameterJdbcOperations.class)).thenReturn(mock(NamedParameterJdbcOperations.class)); + when(operations.execute(any(ConnectionCallback.class))).thenReturn(H2Dialect.INSTANCE); + when(namedParameterJdbcOperations.getJdbcOperations()).thenReturn(operations); + when(beanFactory.getBean(NamedParameterJdbcOperations.class)).thenReturn(namedParameterJdbcOperations); ObjectProvider dataAccessStrategyObjectProvider = mock(ObjectProvider.class); ObjectProvider queryMappingConfigurationObjectProvider = mock(ObjectProvider.class); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index ebc73a205..bde5a6f48 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -27,6 +27,7 @@ import org.mockito.Mockito; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -35,12 +36,14 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; import org.springframework.data.convert.CustomConversions; +import org.springframework.data.jdbc.core.JdbcAggregateOperations; +import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.convert.*; import org.springframework.data.jdbc.core.dialect.JdbcArrayColumns; import org.springframework.data.jdbc.core.dialect.JdbcDialect; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; -import org.springframework.data.jdbc.repository.config.DialectResolver; +import org.springframework.data.jdbc.dialect.DialectResolver; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.mapping.callback.EntityCallback; import org.springframework.data.mapping.callback.EntityCallbacks; @@ -194,6 +197,12 @@ public class TestConfiguration { return DialectResolver.getDialect(operations.getJdbcOperations()); } + @Bean + JdbcAggregateOperations jdbcAggregateOperations(ApplicationContext publisher, JdbcConverter converter, + @Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy) { + return new JdbcAggregateTemplate(publisher, converter, dataAccessStrategy); + } + @Lazy @Bean TestDatabaseFeatures features(NamedParameterJdbcOperations operations) {