Create spring-boot-data-web module

This commit is contained in:
Stéphane Nicoll
2025-03-30 08:35:56 +02:00
committed by Phillip Webb
parent 41befa4fea
commit dbd5c9847d
17 changed files with 60 additions and 14 deletions

View File

@@ -0,0 +1,89 @@
/*
* 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.autoconfigure.data.web;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties.Pageable;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.data.web.config.PageableHandlerMethodArgumentResolverCustomizer;
import org.springframework.data.web.config.SortHandlerMethodArgumentResolverCustomizer;
import org.springframework.data.web.config.SpringDataWebSettings;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's web support.
* <p>
* When in effect, the auto-configuration is the equivalent of enabling Spring Data's web
* support through the {@link EnableSpringDataWebSupport @EnableSpringDataWebSupport}
* annotation.
*
* @author Andy Wilkinson
* @author Vedran Pavic
* @author Yanming Zhou
* @since 4.0.0
*/
@AutoConfiguration(afterName = "org.springframework.boot.data.rest.autoconfigure.RepositoryRestMvcAutoConfiguration")
@EnableSpringDataWebSupport
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ PageableHandlerMethodArgumentResolver.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(PageableHandlerMethodArgumentResolver.class)
@EnableConfigurationProperties(SpringDataWebProperties.class)
public class SpringDataWebAutoConfiguration {
private final SpringDataWebProperties properties;
public SpringDataWebAutoConfiguration(SpringDataWebProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public PageableHandlerMethodArgumentResolverCustomizer pageableCustomizer() {
return (resolver) -> {
Pageable pageable = this.properties.getPageable();
resolver.setPageParameterName(pageable.getPageParameter());
resolver.setSizeParameterName(pageable.getSizeParameter());
resolver.setOneIndexedParameters(pageable.isOneIndexedParameters());
resolver.setPrefix(pageable.getPrefix());
resolver.setQualifierDelimiter(pageable.getQualifierDelimiter());
resolver.setFallbackPageable(PageRequest.of(0, pageable.getDefaultPageSize()));
resolver.setMaxPageSize(pageable.getMaxPageSize());
};
}
@Bean
@ConditionalOnMissingBean
public SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return (resolver) -> resolver.setSortParameter(this.properties.getSort().getSortParameter());
}
@Bean
@ConditionalOnMissingBean
public SpringDataWebSettings springDataWebSettings() {
return new SpringDataWebSettings(this.properties.getPageable().getSerializationMode());
}
}

View File

@@ -0,0 +1,177 @@
/*
* 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.autoconfigure.data.web;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode;
/**
* Configuration properties for Spring Data Web.
*
* @author Vedran Pavic
* @author Yanming Zhou
* @since 4.0.0
*/
@ConfigurationProperties("spring.data.web")
public class SpringDataWebProperties {
private final Pageable pageable = new Pageable();
private final Sort sort = new Sort();
public Pageable getPageable() {
return this.pageable;
}
public Sort getSort() {
return this.sort;
}
/**
* Pageable properties.
*/
public static class Pageable {
/**
* Page index parameter name.
*/
private String pageParameter = "page";
/**
* Page size parameter name.
*/
private String sizeParameter = "size";
/**
* Whether to expose and assume 1-based page number indexes. Defaults to "false",
* meaning a page number of 0 in the request equals the first page.
*/
private boolean oneIndexedParameters = false;
/**
* General prefix to be prepended to the page number and page size parameters.
*/
private String prefix = "";
/**
* Delimiter to be used between the qualifier and the actual page number and size
* properties.
*/
private String qualifierDelimiter = "_";
/**
* Default page size.
*/
private int defaultPageSize = 20;
/**
* Maximum page size to be accepted.
*/
private int maxPageSize = 2000;
/**
* Configures how to render Spring Data Pageable instances.
*/
private PageSerializationMode serializationMode = PageSerializationMode.DIRECT;
public String getPageParameter() {
return this.pageParameter;
}
public void setPageParameter(String pageParameter) {
this.pageParameter = pageParameter;
}
public String getSizeParameter() {
return this.sizeParameter;
}
public void setSizeParameter(String sizeParameter) {
this.sizeParameter = sizeParameter;
}
public boolean isOneIndexedParameters() {
return this.oneIndexedParameters;
}
public void setOneIndexedParameters(boolean oneIndexedParameters) {
this.oneIndexedParameters = oneIndexedParameters;
}
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getQualifierDelimiter() {
return this.qualifierDelimiter;
}
public void setQualifierDelimiter(String qualifierDelimiter) {
this.qualifierDelimiter = qualifierDelimiter;
}
public int getDefaultPageSize() {
return this.defaultPageSize;
}
public void setDefaultPageSize(int defaultPageSize) {
this.defaultPageSize = defaultPageSize;
}
public int getMaxPageSize() {
return this.maxPageSize;
}
public void setMaxPageSize(int maxPageSize) {
this.maxPageSize = maxPageSize;
}
public PageSerializationMode getSerializationMode() {
return this.serializationMode;
}
public void setSerializationMode(PageSerializationMode serializationMode) {
this.serializationMode = serializationMode;
}
}
/**
* Sort properties.
*/
public static class Sort {
/**
* Sort parameter name.
*/
private String sortParameter = "sort";
public String getSortParameter() {
return this.sortParameter;
}
public void setSortParameter(String sortParameter) {
this.sortParameter = sortParameter;
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Auto-configuration for Spring Data's Web Support.
*/
package org.springframework.boot.autoconfigure.data.web;

View File

@@ -0,0 +1,4 @@
{
"groups": [],
"properties": []
}

View File

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration

View File

@@ -0,0 +1,70 @@
/*
* 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.autoconfigure.data.web;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.data.web.domain.city.City;
import org.springframework.boot.autoconfigure.data.web.domain.city.CityRepository;
import org.springframework.boot.data.jpa.autoconfigure.JpaRepositoriesAutoConfiguration;
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

@@ -0,0 +1,132 @@
/*
* 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.autoconfigure.data.web;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode;
import org.springframework.data.web.config.SpringDataWebSettings;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringDataWebAutoConfiguration}.
*
* @author Andy Wilkinson
* @author Vedran Pavic
* @author Stephane Nicoll
* @author Yanming Zhou
*/
class SpringDataWebAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SpringDataWebAutoConfiguration.class));
@Test
void webSupportIsAutoConfiguredInWebApplicationContexts() {
this.contextRunner
.run((context) -> assertThat(context).hasSingleBean(PageableHandlerMethodArgumentResolver.class));
}
@Test
void autoConfigurationBacksOffInNonWebApplicationContexts() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(SpringDataWebAutoConfiguration.class))
.run((context) -> assertThat(context).doesNotHaveBean(PageableHandlerMethodArgumentResolver.class));
}
@Test
void customizePageable() {
this.contextRunner
.withPropertyValues("spring.data.web.pageable.page-parameter=p",
"spring.data.web.pageable.size-parameter=s", "spring.data.web.pageable.default-page-size=10",
"spring.data.web.pageable.prefix=abc", "spring.data.web.pageable.qualifier-delimiter=__",
"spring.data.web.pageable.max-page-size=100", "spring.data.web.pageable.serialization-mode=VIA_DTO",
"spring.data.web.pageable.one-indexed-parameters=true")
.run((context) -> {
PageableHandlerMethodArgumentResolver argumentResolver = context
.getBean(PageableHandlerMethodArgumentResolver.class);
SpringDataWebSettings springDataWebSettings = context.getBean(SpringDataWebSettings.class);
assertThat(argumentResolver).hasFieldOrPropertyWithValue("pageParameterName", "p");
assertThat(argumentResolver).hasFieldOrPropertyWithValue("sizeParameterName", "s");
assertThat(argumentResolver).hasFieldOrPropertyWithValue("oneIndexedParameters", true);
assertThat(argumentResolver).hasFieldOrPropertyWithValue("prefix", "abc");
assertThat(argumentResolver).hasFieldOrPropertyWithValue("qualifierDelimiter", "__");
assertThat(argumentResolver).hasFieldOrPropertyWithValue("fallbackPageable", PageRequest.of(0, 10));
assertThat(argumentResolver).hasFieldOrPropertyWithValue("maxPageSize", 100);
assertThat(springDataWebSettings.pageSerializationMode()).isEqualTo(PageSerializationMode.VIA_DTO);
});
}
@Test
void defaultPageable() {
this.contextRunner.run((context) -> {
SpringDataWebProperties.Pageable properties = new SpringDataWebProperties().getPageable();
PageableHandlerMethodArgumentResolver argumentResolver = context
.getBean(PageableHandlerMethodArgumentResolver.class);
SpringDataWebSettings springDataWebSettings = context.getBean(SpringDataWebSettings.class);
assertThat(argumentResolver).hasFieldOrPropertyWithValue("pageParameterName",
properties.getPageParameter());
assertThat(argumentResolver).hasFieldOrPropertyWithValue("sizeParameterName",
properties.getSizeParameter());
assertThat(argumentResolver).hasFieldOrPropertyWithValue("oneIndexedParameters",
properties.isOneIndexedParameters());
assertThat(argumentResolver).hasFieldOrPropertyWithValue("prefix", properties.getPrefix());
assertThat(argumentResolver).hasFieldOrPropertyWithValue("qualifierDelimiter",
properties.getQualifierDelimiter());
assertThat(argumentResolver).hasFieldOrPropertyWithValue("fallbackPageable",
PageRequest.of(0, properties.getDefaultPageSize()));
assertThat(argumentResolver).hasFieldOrPropertyWithValue("maxPageSize", properties.getMaxPageSize());
assertThat(springDataWebSettings.pageSerializationMode()).isEqualTo(properties.getSerializationMode());
});
}
@Test
void customizeSort() {
this.contextRunner.withPropertyValues("spring.data.web.sort.sort-parameter=s").run((context) -> {
SortHandlerMethodArgumentResolver argumentResolver = context
.getBean(SortHandlerMethodArgumentResolver.class);
assertThat(argumentResolver).hasFieldOrPropertyWithValue("sortParameter", "s");
});
}
@Test
void customizePageSerializationModeViaConfigProps() {
this.contextRunner.withPropertyValues("spring.data.web.pageable.serialization-mode=VIA_DTO").run((context) -> {
SpringDataWebSettings springDataWebSettings = context.getBean(SpringDataWebSettings.class);
assertThat(springDataWebSettings.pageSerializationMode()).isEqualTo(PageSerializationMode.VIA_DTO);
});
}
@Test
void customizePageSerializationModeViaCustomBean() {
this.contextRunner
.withBean("customSpringDataWebSettings", SpringDataWebSettings.class,
() -> new SpringDataWebSettings(PageSerializationMode.VIA_DTO))
.run((context) -> {
assertThat(context).doesNotHaveBean("springDataWebSettings");
SpringDataWebSettings springDataWebSettings = context.getBean(SpringDataWebSettings.class);
assertThat(springDataWebSettings.pageSerializationMode()).isEqualTo(PageSerializationMode.VIA_DTO);
});
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.autoconfigure.data.web.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

@@ -0,0 +1,32 @@
/*
* 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.autoconfigure.data.web.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);
}