繁体   English   中英

Spring Rest 控制器返回特定字段

[英]Spring Rest Controller Return Specific Fields

我一直在思考使用 Spring MVC 设计 JSON API 的最佳方法。 众所周知,IO 很昂贵,因此我不想让客户端进行多次 API 调用来获得他们需要的东西。 但与此同时,我不一定要归还厨房水槽。

例如,我正在开发一个类似于 IMDB 的游戏 API,但用于视频游戏。

如果我返回与 Game 相关的所有内容,它会看起来像这样。

/api/游戏/1

{
    "id": 1,
    "title": "Call of Duty Advanced Warfare",
    "release_date": "2014-11-24",
    "publishers": [
        {
            "id": 1,
            "name": "Activision"
        }
    ],
    "developers": [
        {
            "id": 1,
            "name": "Sledge Hammer"
        }
    ],
    "platforms": [
        {
            "id": 1,
            "name": "Xbox One",
            "manufactorer": "Microsoft",
            "release_date": "2013-11-11"
        },
        {
            "id": 2,
            "name": "Playstation 4",
            "manufactorer": "Sony",
            "release_date": "2013-11-18"
        },
        {
            "id": 3,
            "name": "Xbox 360",
            "manufactorer": "Microsoft",
            "release_date": "2005-11-12"
        }
    ],
    "esrbRating": {
        "id": 1,
        "code": "T",
        "name": "Teen",
        "description": "Content is generally suitable for ages 13 and up. May contain violence, suggestive themes, crude humor, minimal blood, simulated gambling and/or infrequent use of strong language."
    },
    "reviews": [
        {
            "id": 1,
            "user_id": 111,
            "rating": 4.5,
            "description": "This game is awesome"
        }
    ]
}

然而,他们可能不需要所有这些信息,但他们可能再次需要。 从 I/O 和性能来看,调用所有内容似乎是个坏主意。

我考虑通过在请求中指定包含参数来做到这一点。

现在例如,如果您没有指定任何包含,那么您将得到以下内容。

{
    "id": 1,
    "title": "Call of Duty Advanced Warfare",
    "release_date": "2014-11-24"
}

但是,您希望您的请求的所有信息看起来像这样。

/api/game/1?include=publishers,developers,platforms,reviews,esrbRating

这样,客户就可以指定他们想要多少信息。 但是,我对使用 Spring MVC 实现这一点的最佳方法感到不知所措。

我在想控制器看起来像这样。

public @ResponseBody Game getGame(@PathVariable("id") long id, 
    @RequestParam(value = "include", required = false) String include)) {

        // check which include params are present

        // then someone do the filtering?
}

我不确定您将如何选择序列化 Game 对象。 这甚至可能吗。 在 Spring MVC 中解决这个问题的最佳方法是什么?

仅供参考,我正在使用包含 Jackson 的 Spring Boot 进行序列化。

您可以将其序列化为Map<String, Object> ,而不是返回Game对象,其中映射键表示属性名称。 因此,您可以根据include参数将值添加到地图中。

@ResponseBody
public Map<String, Object> getGame(@PathVariable("id") long id, String include) {

    Game game = service.loadGame(id);
    // check the `include` parameter and create a map containing only the required attributes
    Map<String, Object> gameMap = service.convertGameToMap(game, include);

    return gameMap;

}

例如,如果您有一个Map<String, Object>像这样:

gameMap.put("id", game.getId());
gameMap.put("title", game.getTitle());
gameMap.put("publishers", game.getPublishers());

它将像这样序列化:

{
  "id": 1,
  "title": "Call of Duty Advanced Warfare",
  "publishers": [
    {
        "id": 1,
        "name": "Activision"
    }
  ]
}

意识到我的答案来得太晚了:我建议看一下Projections

你要问的是预测是关于什么的。

既然你问的是春天,我会试试这个: https ://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts

GraphQL提供了一种按需提供不同预测的非常动态的方式。 我刚刚看到一篇关于如何在SpringBoot中使用GraphQL的非常有用的文章: https ://www.graphql-java.com/tutorials/getting-started-with-spring-boot/

这可以通过 Spring Projections来完成。 也适用于 Kotlin。 看看这里: https ://www.baeldung.com/spring-data-jpa-projections

看起来总是有相当多的手工工作。 如果您使用一些持久性抽象,那么与普通的 SpringJDBC (JdbcTemplate) 相比,您可以减少工作量。 还取决于您的模型是否与数据库列名对齐。 关于查询语言有很好的系列,例如 QueryDSL: https ://www.baeldung.com/rest-search-language-spring-jpa-criteria。

使用 SpringRest 和 QueryDSL 你可以得到这样的结果:

休息控制器:

//...
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.BooleanExpression;
//...
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
//...

@ApiOperation("Returns list of all users")
@GetMapping(value = "/users", produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseStatus(HttpStatus.OK)
public Page<UsersRest> getUsers(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(defaultValue = "userId,desc") String[] sort,
        @RequestParam(required = false) Optional<String> search,
        @RequestParam(required = false) Optional<String> fields) {

    Sort sorting = parser.parseSortingParameters(sort);
    PageRequest pageable = PageRequest.of(page, size, sorting);
    // search
    BooleanExpression searchPredicate = parser.parseSearchParameter(search);
    // requested columns
    Path[] columns = parser.parseFieldsParameter(fields);

    Page<User> userPage = userService.getAllUsers(pageable, searchPredicate, columns);

    return new PageImpl<>(userPage, userPage.getPageable(), userPage.getTotalElements());
}

存储库类:

//...
import com.querydsl.core.QueryResults;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.sql.Configuration;
import com.querydsl.sql.SQLQuery;
import com.querydsl.sql.SQLQueryFactory;
import com.querydsl.sql.spring.SpringConnectionProvider;
import com.querydsl.sql.spring.SpringExceptionTranslator;
//...
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
//...

@Transactional(readOnly = true)
public Page<User> findAll(Pageable pageable, BooleanExpression searchPredicate, Path[] columns) {
    final var userTable = new QUser("USER");

    // Alternatively (if column names are aligned with field names - so manual mapping is not needed) can be used
    // Expressions.path constructor to dynamically create path:
    // http://www.querydsl.com/static/querydsl/latest/reference/html/ch03.html
    OrderSpecifier<?>[] order = convertToDslOrder(pageable.getSort());

    SQLQuery<Tuple> sql = queryFactory
            .select(columns)
            .from(userTable)
            .where(searchPredicate)
            .orderBy(order);

    sql.offset(pageable.getPageNumber());
    sql.limit(pageable.getPageSize());

    QueryResults<Tuple> queryResults = sql.fetchResults();

    final long totalCount = queryResults.getTotal();
    List<Tuple> results = queryResults.getResults();
    List<User> users = userRowMapper(userTable, results);

    return new PageImpl<>(users, pageable, totalCount);
}

解决方案 1:将 @JsonIgnore 添加到您不想包含在 API 响应中的变量(在模型中)

@JsonIgnore
    private Set<Student> students;

解决方案 2:删除您不想包含的变量的 getter。

如果您在其他地方需要它们,请为 getter 使用不同的格式,以便 spring 不知道它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM