【问题标题】:Spring Data Pagination returns no results with JSONViewSpring Data Pagination 使用 JSONView 不返回任何结果
【发布时间】:2026-01-08 11:05:01
【问题描述】:

我在我的 REST 控制器中使用 Spring 数据分页并返回分页实体。我想在 JSONViews 的帮助下控制以 JSON 形式返回的数据。

当我返回单个对象时,我能够实现结果。但是当我返回 Page 时,我收到了空白 JSON 作为响应。

以下是我的方法签名。

@JsonView(TravelRequestView.MyRequests.class)
@RequestMapping("/travel/requests")
public Page<TravelRequest> getUserTravelRequests(
            @RequestParam("ps") int pageSize, @RequestParam("p") int page,
            @RequestParam(defaultValue = "", value = "q") String searchString)

当我删除 @JsonView 注释时,我能够收到响应。

【问题讨论】:

    标签: java json spring spring-mvc pagination


    【解决方案1】:

    如果您使用的是 spring-boot,那么另一个更简单的解决方案是将以下内容添加到 application.yml

    spring:
      jackson:
        mapper:
          DEFAULT_VIEW_INCLUSION: true
    

    或application.properties

    spring.jackson.mapper.DEFAULT_VIEW_INCLUSION=true
    

    通过这种方式,我们的优势是保留了Spring Container管理的ObjectMapper,而不是创建新的ObjectMapper。因此,一旦我们使用 spring 管理的 ObjectMapper,我们定义的任何自定义序列化器仍将继续工作,例如 CustomDateSerialzer

    参考:http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html

    【讨论】:

    • 这对我有帮助。
    • 也为我工作!又好又干净!迄今为止最好的答案:)
    • 此解决方案忽略我的对象中的每个@JsonView 注释并打印所有这些...
    【解决方案2】:

    设置 DEFAULT_VIEW_INCLUSION 具有全局效果,而我们所需要的只是能够序列化 Page 对象。以下代码将为 Page 注册一个序列化程序,并对您的代码进行简单更改:

    @Bean
    public Module springDataPageModule() {
        return new SimpleModule().addSerializer(Page.class, new JsonSerializer<Page>() {
            @Override
            public void serialize(Page value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                gen.writeStartObject();
                gen.writeNumberField("totalElements",value.getTotalElements());
                gen.writeNumberField("totalPages", value.getTotalPages());
                gen.writeNumberField("number", value.getNumber());
                gen.writeNumberField("size", value.getSize());
                gen.writeBooleanField("first", value.isFirst());
                gen.writeBooleanField("last", value.isLast());
                gen.writeFieldName("content");
                serializers.defaultSerializeValue(value.getContent(),gen);
                gen.writeEndObject();
            }
        });
    }
    

    另一个(可以说更优雅)的解决方案是注册以下 ResponseBodyAdvice。它将确保您的 REST 端点仍将返回 JSON 数组,并设置 HTTP 标头“X-Has-Next-Page”以指示是否有更多数据。优点是: 1)没有额外的计数(*)查询到您的数据库(单个查询) 2) 响应更优雅,因为它返回一个 JSON 数组

    /**
     * ResponseBodyAdvice to support Spring data Slice object in JSON responses.
     * If the value is a slice, we'll write the List as an array, and add a header to the HTTP response
     *
     * @author blagerweij
     */
    @ControllerAdvice
    public class SliceResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    
        @Override
        public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
            return true;
        }
    
        @Override
        public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
            if (body instanceof Slice) {
                Slice slice = (Slice) body;
                response.getHeaders().add("X-Has-Next-Page", String.valueOf(slice.hasNext()));
                return slice.getContent();
            }
            return body;
        }
    }
    

    【讨论】:

    • 非常感谢,这是唯一适合我的解决方案。我正在使用带有 spring data jpa 的 spring boot :)
    • 你的回答就像一个魅力。但它从页面类中删除了一些属性,例如totalPages、last、first、sort、size、number 和numberOfElements。有没有办法添加这些字段?
    • 当然,你可以把它们包含在序列化代码中,我会在提供的答案中更新代码
    【解决方案3】:

    试试下面的代码,

    @Configuration
    public class MyInterceptorConfig extends WebMvcConfigurerAdapter{
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
          MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
          ObjectMapper mapper = new ObjectMapper() {
            private static final long serialVersionUID = 1L;
            @Override
            protected DefaultSerializerProvider _serializerProvider(SerializationConfig config) {
              // replace the configuration with my modified configuration.
              // calling "withView" should keep previous config and just add my changes.
              return super._serializerProvider(config.withView(TravelRequestView.MyRequests.class));
            }        
          };
          mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
          converter.setObjectMapper(mapper);
          converters.add(converter);
        }
    

    虽然我不想因此而受到赞扬, 这是来自

    的参考

    Jackson JsonView not being applied

    它会检索一个实体的所有用 jsonview (TravelRequestView.MyRequests.class) 注释的变量以及所有没有用 jsonview 注释的变量。如果您不想要对象的某些属性,请使用不同的视图进行注释。

    【讨论】:

      【解决方案4】:

      我实际上找到了一种更简单更好的方法。我遇到的问题是我无法在收到的 Page 对象上设置 @JsonView 注释。因此,我创建了 Page 接口的实现并将我的 JsonViews 添加到其中。而不是返回 Page,我现在返回 MyPage

      public class MyPage<T> implements Page<T> {
      
          private Page<T> pageObj;
      
          public MyPage(Page<T> pageObj) {
              this.pageObj = pageObj;
          }
      
          @JsonView(PaginatedResult.class)
          @Override
          public int getNumber() {
              return pageObj.getNumber();
          }
      
          @Override
          public int getSize() {
              return pageObj.getSize();
          }
      
          @JsonView(PaginatedResult.class)
          @Override
          public int getNumberOfElements() {
              return pageObj.getNumberOfElements();
          }
      
          @JsonView(PaginatedResult.class)
          @Override
          public List<T> getContent() {
              return pageObj.getContent();
          }
      
          @Override
          public boolean hasContent() {
              return pageObj.hasContent();
          }
      
          @Override
          public Sort getSort() {
              return pageObj.getSort();
          }
      
          @JsonView(PaginatedResult.class)
          @Override
          public boolean isFirst() {
              return pageObj.isFirst();
          }
      
          @JsonView(PaginatedResult.class)
          @Override
          public boolean isLast() {
              return pageObj.isLast();
          }
      
          @Override
          public boolean hasNext() {
              return pageObj.hasNext();
          }
      
          @Override
          public boolean hasPrevious() {
              return pageObj.hasPrevious();
          }
      
          @Override
          public Pageable nextPageable() {
              return pageObj.nextPageable();
          }
      
          @Override
          public Pageable previousPageable() {
              return pageObj.previousPageable();
          }
      
          @Override
          public Iterator<T> iterator() {
              return pageObj.iterator();
          }
      
          @JsonView(PaginatedResult.class)
          @Override
          public int getTotalPages() {
              return pageObj.getTotalPages();
          }
      
          @JsonView(PaginatedResult.class)
          @Override
          public long getTotalElements() {
              return pageObj.getTotalElements();
          }
      
      }
      

      【讨论】:

      • 什么是分页结果?将被映射的类?
      • 很好的答案,使用适配器模式。又好又简单。
      • 精彩如简单:)
      【解决方案5】:

      你需要递归地添加注解@JsonView(TravelRequestView.MyRequests.class)。将其添加到您希望在 Page 类中看到的字段中。

      public class Page<T> {
          @JsonView(TravelRequestView.MyRequests.class)
          private T view;
       ...
      }
      

      或为 ObjectMapper 启用 DEFAULT_VIEW_INCLUSION:

      <mvc:annotation-driven>
          <mvc:message-converters>
              <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
              <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                  <property name="objectMapper">
                      <bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                          <property name="defaultViewInclusion" value="true"/>
                      </bean>
                  </property>
              </bean>
          </mvc:message-converters>
      </mvc:annotation-driven>
      

      或使用 dto 对象作为您可以控制所有视图的响应

      【讨论】:

      • 这里的页面是spring数据库的一部分。我无法修改它。
      • 您可以为您的 ObjectMapper 启用 DEFAULT_VIEW_INCLUSION
      【解决方案6】:

      更简洁的方法是告诉 Jackson 为 Page 类型的 bean 序列化所有道具。 为此,您只需声明以稍微适应 Jackson BeanSerializer :属性过滤。

      如下面的代码所示,我刚刚删除了页面实例的过滤:

      public class MyPageSerializer extends BeanSerializer {
      
      /**
       * MODIFIED By Gauthier PEEL
       */
      @Override
      protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
              throws IOException, JsonGenerationException {
          final BeanPropertyWriter[] props;
          // ADDED
          // ADDED
          // ADDED
          if (bean instanceof Page) {
              // for Page DO NOT filter anything so that @JsonView is passthrough at this level
              props = _props;
          } else {
              // ADDED
              // ADDED
              if (_filteredProps != null && provider.getActiveView() != null) {
                  props = _filteredProps;
              } else {
                  props = _props;
              }
          }
      
              // rest of the method unchanged
          }
      
          // inherited constructor removed for concision
      }
      

      然后你需要用一个 Module 向 Jackson 声明它:

      public class MyPageModule extends SimpleModule {
      
          @Override
          public void setupModule(SetupContext context) {
      
              context.addBeanSerializerModifier(new BeanSerializerModifier() {
      
                  @Override
                  public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc,
                          JsonSerializer<?> serializer) {
                      if (serializer instanceof BeanSerializerBase) {
                          return new MyPageSerializer ((BeanSerializerBase) serializer);
                      }
                      return serializer;
      
                  }
              });
          }
      }
      

      Spring conf now : 在你的 @Configuration 中创建一个新的 MyPageModule 的 @Bean

      @Configuration
      public class WebConfigPage extends WebMvcConfigurerAdapter {
      
          /**
           * To enable Jackson @JsonView to work with Page<T>
           */
          @Bean
          public MyPageModule myPageModule() {
              return new MyPageModule();
          }
      
      }
      

      你就完成了。

      【讨论】:

      • @Martin 我更正了此示例中 TworkSpringDataPageSerializer 的类型应为 MyPageSerializer。
      最近更新 更多