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