Break cycle in spring-boot-data-* modules

There was a cycle in the spring-boot-data-* modules.

SpringDataWebAutoConfiguration (which sits at the spring-data-commons
level) referenced RepositoryRestMvcAutoConfiguration (which sits
at the spring-data-rest level). This dependency's expressed in the
wrong direction as data-rest depends on data-commons. The resulting
cycle was not noticable as it was expressed through a String afterName
attribute on `@AutoConfiguration`.

spring-boot-data-commons was also using spring-boot-data-jpa for its
tests, which also results in an inverted dependency relationship.

This commit reworks things so that all of the spring-boot-data-*
modules depend upon spring-boot-data-commons, mirroring the
dependency relationship in Spring Data. The integration tests in
spring-boot-data-commons have been reworked to use Spring Data
directly, avoiding the need for a circular from
spring-boot-data-commons to another spring-boot-data-* module.
This commit is contained in:
Andy Wilkinson
2025-05-30 15:19:14 +01:00
committed by Phillip Webb
parent e0a6237b8a
commit 9ee79c1bbc
21 changed files with 54 additions and 174 deletions

View File

@@ -50,7 +50,7 @@ public class MetricsRepositoryMethodInvocationListener implements RepositoryMeth
* @param tagsProvider provider for metrics tags
* @param metricName name of the metric to record
* @param autoTimer the auto-timers to apply or {@code null} to disable auto-timing
* @since 2.5.4
* @since 4.0.0
*/
public MetricsRepositoryMethodInvocationListener(Supplier<MeterRegistry> registrySupplier,
RepositoryTagsProvider tagsProvider, String metricName, AutoTimer autoTimer) {

View File

@@ -45,7 +45,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
* @author Yanming Zhou
* @since 4.0.0
*/
@AutoConfiguration(afterName = "org.springframework.boot.data.rest.autoconfigure.RepositoryRestMvcAutoConfiguration")
@AutoConfiguration
@EnableSpringDataWebSupport
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ PageableHandlerMethodArgumentResolver.class, WebMvcConfigurer.class })

View File

@@ -14,39 +14,19 @@
* limitations under the License.
*/
package org.springframework.boot.data.metrics.autoconfigure.city;
package org.springframework.boot.data.domain.city;
import java.io.Serializable;
import org.springframework.data.annotation.Id;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class City implements Serializable {
private static final long serialVersionUID = 1L;
public class City {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String state;
@Column(nullable = false)
private String country;
@Column(nullable = false)
private String map;
protected City() {
}
public City(String name, String country) {
this.name = name;
this.country = country;
@@ -56,21 +36,13 @@ public class City implements Serializable {
return this.name;
}
public String getState() {
return this.state;
}
public String getCountry() {
return this.country;
}
public String getMap() {
return this.map;
}
@Override
public String toString() {
return getName() + "," + getState() + "," + getCountry();
return getName() + "," + getCountry();
}
}

View File

@@ -14,19 +14,10 @@
* limitations under the License.
*/
package org.springframework.boot.data.metrics.autoconfigure.city;
package org.springframework.boot.data.domain.city;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
public interface CityRepository extends JpaRepository<City, Long> {
@Override
Page<City> findAll(Pageable pageable);
Page<City> findByNameLikeAndCountryLikeAllIgnoringCase(String name, String country, Pageable pageable);
City findByNameAndCountryAllIgnoringCase(String name, String country);
public interface CityRepository extends CrudRepository<City, Long> {
}

View File

@@ -22,16 +22,20 @@ import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.data.jpa.autoconfigure.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.data.metrics.autoconfigure.city.CityRepository;
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
import org.springframework.boot.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration;
import org.springframework.boot.data.domain.city.City;
import org.springframework.boot.data.domain.city.CityRepository;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import static org.assertj.core.api.Assertions.assertThat;
@@ -40,14 +44,22 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Phillip Webb
*/
@WithResource(name = "schema.sql", content = """
CREATE TABLE CITY (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(30),
country VARCHAR(30)
);
""")
class RepositoryMetricsAutoConfigurationIntegrationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(SimpleMeterRegistry.class)
.withConfiguration(
AutoConfigurations.of(HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, RepositoryMetricsAutoConfiguration.class))
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, TestConfig.class);
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
RepositoryMetricsAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class,
DataSourceInitializationAutoConfiguration.class))
.withUserConfiguration(TestConfig.class);
@Test
void repositoryMethodCallRecordsMetrics() {
@@ -68,8 +80,8 @@ class RepositoryMetricsAutoConfigurationIntegrationTests {
}
@Configuration(proxyBeanMethods = false)
@AutoConfigurationPackage
static class TestConfig {
@EnableJdbcRepositories(basePackageClasses = City.class)
static class TestConfig extends AbstractJdbcConfiguration {
}

View File

@@ -1,70 +0,0 @@
/*
* Copyright 2012-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.boot.data.web.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.data.jpa.autoconfigure.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.data.web.autoconfigure.domain.city.City;
import org.springframework.boot.data.web.autoconfigure.domain.city.CityRepository;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.geo.Distance;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringDataWebAutoConfiguration} and
* {@link JpaRepositoriesAutoConfiguration}.
*
* @author Dave Syer
* @author Stephane Nicoll
*/
class SpringDataWebAutoConfigurationJpaTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class, SpringDataWebAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true");
@Test
void springDataWebIsConfiguredWithJpaRepositories() {
this.contextRunner.withUserConfiguration(TestConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(CityRepository.class);
assertThat(context).hasSingleBean(PageableHandlerMethodArgumentResolver.class);
assertThat(context).hasSingleBean(SortHandlerMethodArgumentResolver.class);
assertThat(context.getBean(FormattingConversionService.class).canConvert(String.class, Distance.class))
.isTrue();
});
}
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(City.class)
@EnableWebMvc
static class TestConfiguration {
}
}

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2012-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.boot.data.web.autoconfigure.domain.city;
import java.io.Serializable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class City implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String state;
@Column(nullable = false)
private String country;
@Column(nullable = false)
private String map;
protected City() {
}
public City(String name, String country) {
this.name = name;
this.country = country;
}
public String getName() {
return this.name;
}
public String getState() {
return this.state;
}
public String getCountry() {
return this.country;
}
public String getMap() {
return this.map;
}
@Override
public String toString() {
return getName() + "," + getState() + "," + getCountry();
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2012-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.boot.data.web.autoconfigure.domain.city;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CityRepository extends JpaRepository<City, Long> {
@Override
Page<City> findAll(Pageable pageable);
Page<City> findByNameLikeAndCountryLikeAllIgnoringCase(String name, String country, Pageable pageable);
City findByNameAndCountryAllIgnoringCase(String name, String country);
}