[英]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.