Spring Data Pagination在JSONView中没有返回任何结果

时间:2015-08-10 07:01:32

标签: java json spring spring-mvc pagination

我在REST控制器中使用Spring数据分页并返回Paged实体。我想在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注释时,我能够收到响应。

6 个答案:

答案 0 :(得分:7)

设置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;
    }
}

答案 1 :(得分:7)

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

spring:
  jackson:
    mapper:
      DEFAULT_VIEW_INCLUSION: true

或application.properties

spring.jackson.mapper.DEFAULT_VIEW_INCLUSION=true

使用这种方法,我们的优势在于保留了Spring容器管理的ObjectMapper而不创建新的ObjectMapper。因此,一旦我们使用spring管理的ObjectMapper,我们定义的任何Custom Serialzers仍将继续工作,例如CustomDateSerialzer

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

答案 2 :(得分:4)

尝试下面的代码,

@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注释的变量。如果您不想要某个对象的某些属性,请使用不同的视图进行注释。

答案 3 :(得分:2)

我实际上找到了一种更简单,更好的方法。我遇到的问题是我无法在我收到的Page对象上设置@JsonView注释。所以我创建了一个Page接口的实现,并添加了我的JsonViews。而不是返回页面,我现在返回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();
    }

}

答案 4 :(得分:1)

您需要递归添加注释@JsonView(TravelRequestView.MyRequests.class)。将其添加到要在Page class中查看的字段中。

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对象作为您可以控制所有观看次数的回复

答案 5 :(得分:0)

更清楚的是告诉杰克逊为一个类型为Page的bean序列化所有道具。 要做到这一点,你只需声明要适应Jackson BeanSerializer:com.fasterxml.jackson.databind.ser.BeanSerializer创建一个扩展BeanSerializer的类,调用PageSerialier,如果bean的类型为Page&lt;&gt;不要应用属性过滤。

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

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
}

然后你需要用模块向杰克逊宣布:

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现在:在你的@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();
    }

}

你完成了。