From 4b38241ef1b2688e6ad650ee91ac2fe703b8a113 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 14 Jul 2025 11:59:13 +0200 Subject: [PATCH] Polishing. Apply fallbacks requiring fewer dependencies and without requiring BeanFactory for all fallback variants. See #2070 --- .../support/JdbcRepositoryFactoryBean.java | 101 +++++++++++------- .../JdbcRepositoryFactoryBeanUnitTests.java | 22 ++-- 2 files changed, 74 insertions(+), 49 deletions(-) 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 ae0683e75..0d56c6b15 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 @@ -16,6 +16,9 @@ package org.springframework.data.jdbc.repository.support; import java.io.Serializable; + +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -38,6 +41,19 @@ import org.springframework.util.Assert; /** * Special adapter for Springs {@link org.springframework.beans.factory.FactoryBean} interface to allow easy setup of * repository factories via Spring configuration. + *

+ * A partially populated factory bean can use {@link BeanFactory} to resolve missing dependencies, specifically: + *

* * @author Jens Schauder * @author Greg Turnquist @@ -52,15 +68,15 @@ import org.springframework.util.Assert; public class JdbcRepositoryFactoryBean, S, ID extends Serializable> extends TransactionalRepositoryFactoryBeanSupport implements ApplicationEventPublisherAware { - private ApplicationEventPublisher publisher; - private BeanFactory beanFactory; - private RelationalMappingContext mappingContext; - private JdbcConverter converter; - private DataAccessStrategy dataAccessStrategy; - private QueryMappingConfiguration queryMappingConfiguration; - private NamedParameterJdbcOperations operations; - private EntityCallbacks entityCallbacks; - private Dialect dialect; + private @Nullable ApplicationEventPublisher publisher; + private @Nullable BeanFactory beanFactory; + private @Nullable RelationalMappingContext mappingContext; + private @Nullable JdbcConverter converter; + private @Nullable DataAccessStrategy dataAccessStrategy; + private @Nullable QueryMappingConfiguration queryMappingConfiguration; + private @Nullable NamedParameterJdbcOperations operations; + private EntityCallbacks entityCallbacks = EntityCallbacks.create(); + private @Nullable Dialect dialect; /** * Creates a new {@link JdbcRepositoryFactoryBean} for the given repository interface. @@ -85,6 +101,14 @@ public class JdbcRepositoryFactoryBean, S, ID extend @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { + Assert.state(this.dataAccessStrategy != null, "DataAccessStrategy is required and must not be null"); + Assert.state(this.mappingContext != null, "MappingContext is required and must not be null"); + Assert.state(this.converter != null, "RelationalConverter is required and must not be null"); + Assert.state(this.dialect != null, "Dialect is required and must not be null"); + Assert.state(this.publisher != null, "ApplicationEventPublisher is required and must not be null"); + 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.setQueryMappingConfiguration(queryMappingConfiguration); @@ -149,6 +173,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend super.setBeanFactory(beanFactory); + this.entityCallbacks = EntityCallbacks.create(beanFactory); this.beanFactory = beanFactory; } @@ -157,51 +182,53 @@ public class JdbcRepositoryFactoryBean, S, ID extend Assert.state(this.converter != null, "RelationalConverter is required and must not be null"); - if (mappingContext == null) { - Assert.state(beanFactory != null, "If no MappingContext are set a BeanFactory must be available"); - - this.mappingContext = beanFactory.getBean(RelationalMappingContext.class); + if (this.mappingContext == null) { + this.mappingContext = this.converter.getMappingContext(); } if (this.operations == null) { - Assert.state(beanFactory != null, "If no JdbcOperations are set a BeanFactory must be available"); - - this.operations = beanFactory.getBean(NamedParameterJdbcOperations.class); + Assert.state(this.beanFactory != null, "If no JdbcOperations are set a BeanFactory must be available"); + this.operations = this.beanFactory.getBean(NamedParameterJdbcOperations.class); } if (this.queryMappingConfiguration == null) { - Assert.state(beanFactory != null, "If no QueryMappingConfiguration are set a BeanFactory must be available"); - this.queryMappingConfiguration = beanFactory.getBeanProvider(QueryMappingConfiguration.class) - .getIfAvailable(() -> QueryMappingConfiguration.EMPTY); + if (this.beanFactory == null) { + this.queryMappingConfiguration = QueryMappingConfiguration.EMPTY; + } else { + + this.queryMappingConfiguration = beanFactory.getBeanProvider(QueryMappingConfiguration.class) + .getIfAvailable(() -> QueryMappingConfiguration.EMPTY); + } } - if (this.dataAccessStrategy == null) { + if (this.dataAccessStrategy == null && this.beanFactory != null) { + this.dataAccessStrategy = this.beanFactory.getBeanProvider(DataAccessStrategy.class).getIfAvailable(); + } - Assert.state(beanFactory != null, "If no DataAccessStrategy is set a BeanFactory must be available"); + if (this.dataAccessStrategy == null) { - this.dataAccessStrategy = this.beanFactory.getBeanProvider(DataAccessStrategy.class) // - .getIfAvailable(() -> { + Assert.state(this.dialect != null, "Dialect is required and must not be null"); - Assert.state(this.dialect != null, "Dialect is required and must not be null"); + DataAccessStrategyFactory factory = getDataAccessStrategyFactory(this.mappingContext, this.converter, + this.dialect, this.operations, this.queryMappingConfiguration); - SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(this.mappingContext, this.converter, - this.dialect); - SqlParametersFactory sqlParametersFactory = new SqlParametersFactory(this.mappingContext, this.converter); - InsertStrategyFactory insertStrategyFactory = new InsertStrategyFactory(this.operations, this.dialect); + this.dataAccessStrategy = factory.create(); + } - DataAccessStrategyFactory factory = new DataAccessStrategyFactory(sqlGeneratorSource, this.converter, - this.operations, sqlParametersFactory, insertStrategyFactory, queryMappingConfiguration); + super.afterPropertiesSet(); + } - return factory.create(); - }); - } + private static DataAccessStrategyFactory getDataAccessStrategyFactory(RelationalMappingContext mappingContext, + JdbcConverter converter, Dialect dialect, NamedParameterJdbcOperations operations, + QueryMappingConfiguration queryMapping) { - if (beanFactory != null) { - entityCallbacks = EntityCallbacks.create(beanFactory); - } + SqlGeneratorSource source = new SqlGeneratorSource(mappingContext, converter, dialect); + SqlParametersFactory spf = new SqlParametersFactory(mappingContext, converter); + InsertStrategyFactory isf = new InsertStrategyFactory(operations, dialect); - super.afterPropertiesSet(); + return new DataAccessStrategyFactory(source, converter, operations, spf, isf, queryMapping); } + } 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 4c567f78a..2e3bbd8e3 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 @@ -57,19 +57,19 @@ import org.springframework.test.util.ReflectionTestUtils; */ @MockitoSettings(strictness = Strictness.LENIENT) @ExtendWith(MockitoExtension.class) -public class JdbcRepositoryFactoryBeanUnitTests { +class JdbcRepositoryFactoryBeanUnitTests { - JdbcRepositoryFactoryBean factoryBean; + private JdbcRepositoryFactoryBean factoryBean; @Mock DataAccessStrategy dataAccessStrategy; @Mock ApplicationEventPublisher publisher; @Mock(answer = Answers.RETURNS_DEEP_STUBS) ListableBeanFactory beanFactory; @Mock Dialect dialect; - RelationalMappingContext mappingContext; + private RelationalMappingContext mappingContext; @BeforeEach - public void setUp() { + void setUp() { this.mappingContext = new JdbcMappingContext(); @@ -90,7 +90,7 @@ public class JdbcRepositoryFactoryBeanUnitTests { } @Test // DATAJDBC-151 - public void setsUpBasicInstanceCorrectly() { + void setsUpBasicInstanceCorrectly() { factoryBean.setDataAccessStrategy(dataAccessStrategy); factoryBean.setMappingContext(mappingContext); @@ -104,20 +104,20 @@ public class JdbcRepositoryFactoryBeanUnitTests { } @Test // DATAJDBC-151 - public void requiresListableBeanFactory() { + void requiresListableBeanFactory() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> factoryBean.setBeanFactory(mock(BeanFactory.class))); } @Test // DATAJDBC-155 - public void afterPropertiesThrowsExceptionWhenNoMappingContextSet() { + void afterPropertiesThrowsExceptionWhenNoMappingContextSet() { assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> factoryBean.setMappingContext(null)); } @Test // DATAJDBC-155 - public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() { + void afterPropertiesSetDefaultsNullablePropertiesCorrectly() { factoryBean.setConverter(new MappingJdbcConverter(mappingContext, dataAccessStrategy)); factoryBean.setApplicationEventPublisher(publisher); @@ -128,10 +128,8 @@ public class JdbcRepositoryFactoryBeanUnitTests { assertThat(factoryBean.getObject()).isNotNull(); assertThat(ReflectionTestUtils.getField(factoryBean, "dataAccessStrategy")) .isInstanceOf(DefaultDataAccessStrategy.class); - assertThat(ReflectionTestUtils.getField(factoryBean, "queryMappingConfiguration")) - .isEqualTo(QueryMappingConfiguration.EMPTY); - assertThat(ReflectionTestUtils.getField(factoryBean, "mappingContext")) - .isEqualTo(mappingContext); + assertThat(factoryBean).hasFieldOrPropertyWithValue("queryMappingConfiguration", QueryMappingConfiguration.EMPTY); + assertThat(factoryBean).hasFieldOrPropertyWithValue("mappingContext", mappingContext); } private static class DummyEntity {