簡體   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