Spring MVC 3: zwraca stronę Spring-Data jako JSON

Mam warstwę dostępu do danych wykonaną za pomocą Spring-Data. Obecnie tworzę na nim aplikację webową. Ta jedna metoda kontrolera powinna zwracać Stronę Spring-Data sformatowaną jako JSON.

Taka strona jest listą z dodatkowymi informacjami stronicowania, takimi jak całkowita ilość rekordów i tak dalej.

Czy to możliwe i jeśli tak to jak?

I bezpośrednio z tym związane Czy Mogę zdefiniować odwzorowanie nazw właściwości? Np. oznacza to, że musiałbym zdefiniować, jak stronicowanie właściwości informacji są nazwane w JSON (inaczej niż w page). Czy to możliwe i jak?

Author: Oliver Gierke, 2013-05-28

3 answers

Jest wsparcie dla takiego scenariusza nadchodzącego na wiosnę HATEOAS i Spring Data Commons. Spring HATEOAS jest wyposażony w obiekt PageMetadata, który zasadniczo zawiera te same dane, co Page, ale w mniej wymuszony sposób, dzięki czemu można go łatwiej skalować i nie skalować.

Innym aspektem, dla którego implementujemy to w połączeniu z Spring HATEOAS i Spring Data commons jest to, że nie ma wartości w prostym rozbudowywaniu strony, jej treści i metadanych, ale chcesz również wygenerować linki do być może istniejących następnych lub poprzednich stron, aby Klient nie musiał konstruować Uri, aby przejść przez te strony.

Przykład

Przyjmij klasę domeny Person:

class Person {

  Long id;
  String firstname, lastname;
}

Jak również odpowiednie repozytorium:

interface PersonRepository extends PagingAndSortingRepository<Person, Long> { }

Możesz teraz wyświetlić Kontroler Spring MVC w następujący sposób:

@Controller
class PersonController {

  @Autowired PersonRepository repository;

  @RequestMapping(value = "/persons", method = RequestMethod.GET)
  HttpEntity<PagedResources<Person>> persons(Pageable pageable, 
    PagedResourcesAssembler assembler) {

    Page<Person> persons = repository.findAll(pageable);
    return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
  }
}

Prawdopodobnie jest tu sporo do wyjaśnienia. Zróbmy to krok po kroku:

  1. mamy Spring MVC kontroler podłącza do niego repozytorium. Wymaga to skonfigurowania danych Spring (poprzez @Enable(Jpa|Mongo|Neo4j|Gemfire)Repositories lub odpowiedniki XML). Metoda kontrolera jest mapowana do /persons, co oznacza, że będzie akceptować wszystkie żądania GET do tej metody.
  2. Typ rdzenia zwracany z metody to PagedResources - Typ od Spring HATEOAS, który reprezentuje pewne treści wzbogacone o Links plus PageMetadata.
  3. Gdy metoda jest wywoływana, Spring MVC będzie musiał utworzyć instancje dla Pageable i PagedResourcesAssembler. Aby to zadziałało, musisz włączyć obsługę Spring Data web poprzez adnotację @EnableSpringDataWebSupport, która ma zostać wprowadzona w nadchodzącym milowym etapie Spring Data Commons lub poprzez samodzielne definicje bean (udokumentowane tutaj ).

    Pageable zostaną wypełnione informacje z żądania. Domyślna konfiguracja zmieni ?page=0&size=10 w Pageable żądanie pierwszej strony o rozmiarze strony 10.

    PageableResourcesAssembler pozwala na łatwe obracanie Page w PagedResources instancje. Spowoduje to nie tylko dodanie metadanych strony do odpowiedzi, ale także dodanie odpowiednich linków do reprezentacji w zależności od tego, do której strony uzyskujesz dostęp i jak skonfigurowana jest rozdzielczość Pageable.

Przykładowa konfiguracja JavaConfig, aby włączyć to dla JPA, wyglądałaby tak:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
@EnableJpaRepositories
class ApplicationConfig {

  // declare infrastructure components like EntityManagerFactory etc. here
}

Przykładowe zapytanie i odpowiedź

Załóżmy, że mamy 30 Persons w bazie danych. Możesz teraz uruchomić żądanie GET http://localhost:8080/persons, a zobaczysz coś podobnego do tego:

{ "links" : [
    { "rel" : "next", "href" : "http://localhost:8080/persons?page=1&size=20 }
  ],
  "content" : [
    … // 20 Person instances rendered here
  ],
  "pageMetadata" : {
    "size" : 20,
    "totalElements" : 30,
    "totalPages" : 2,
    "number" : 0
  }
}

Zauważ, że asembler wytworzył poprawny URI, a także pobiera domyślną konfigurację, aby przekształcić parametry w Pageable dla nadchodzącego żądania. Oznacza to, że jeśli zmienisz tę konfigurację, linki automatycznie dostosują się do zmiany. Domyślnie asembler wskazuje na metodę kontrolera, w której został wywołany, ale można ją dostosować, przekazując niestandardową Link, która ma być używana jako baza do budowania linków do stron do przeciążeń PagedResourcesAssembler.toResource(…) metoda.

Outlook

Bity PagedResourcesAssembler będą dostępne w nadchodzącym przełomowym wydaniu Spring Data Babbage release train. Jest już dostępny w aktualnych migawkach. Możesz zobaczyć działający przykład tego w mojej aplikacji Spring RESTBucks przykładowej aplikacji . Po prostu Sklonuj go, uruchom mvn jetty:run i zwiń http://localhost:8080/pages.

 51
Author: Oliver Gierke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-26 11:35:45

Oliver, Twoja odpowiedź jest świetna i zaznaczam ją jako odpowiedź. Tutaj tylko dla kompletności, co wymyśliłem na średni czas, który może być przydatny dla kogoś innego.

Używam jQuery Datatables jako widżetu siatki / tabeli. Wysyła bardzo konkretny parametr do serwera i wyłącza bardzo konkretną odpowiedź: zobacz http://datatables.net/usage/server-side .

Aby to osiągnąć, tworzony jest obiekt custom helper odzwierciedlający to, czego oczekuje datatables. Zauważ, że getter i setter musi być nazwany tak, jak są one inaczej produkowane json jest źle (wielkość liter nazwy właściwości i datatables używa tej "pseudo Węgierskiej notacji"...).

public class JQueryDatatablesPage<T> implements java.io.Serializable {

    private final int iTotalRecords;
    private final int iTotalDisplayRecords;
    private final String sEcho;
    private final List<T> aaData;

    public JQueryDatatablesPage(final List<T> pageContent,
            final int iTotalRecords,
            final int iTotalDisplayRecords,
            final String sEcho){

        this.aaData = pageContent;
        this.iTotalRecords = iTotalRecords;
        this.iTotalDisplayRecords = iTotalDisplayRecords;
        this.sEcho = sEcho;
    }

    public int getiTotalRecords(){
        return this.iTotalRecords;
    }

    public int getiTotalDisplayRecords(){
        return this.iTotalDisplayRecords;
    }

    public String getsEcho(){
        return this.sEcho;
    }

    public List<T> getaaData(){
        return this.aaData;
    }
}

Druga część to metoda w kontrolerze:

@RequestMapping(value = "/search", method = RequestMethod.GET, produces = "application/json")
public @ResponseBody String search (
        @RequestParam int iDisplayStart,
        @RequestParam int iDisplayLength,
        @RequestParam int sEcho, // for datatables draw count
        @RequestParam String search) throws IOException {

    int pageNumber = (iDisplayStart + 1) / iDisplayLength;
    PageRequest pageable = new PageRequest(pageNumber, iDisplayLength);
    Page<SimpleCompound> page = compoundService.myCustomSearchMethod(search, pageable);
    int iTotalRecords = (int) (int) page.getTotalElements();
    int iTotalDisplayRecords = page.getTotalPages() * iDisplayLength;
    JQueryDatatablesPage<SimpleCompound> dtPage = new JQueryDatatablesPage<>(
            page.getContent(), iTotalRecords, iTotalDisplayRecords,
            Integer.toString(sEcho));

    String result = toJson(dtPage);
    return result;

}

private String toJson(JQueryDatatablesPage<?> dt) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new Hibernate4Module());
    return mapper.writeValueAsString(dt);
}

compoundService jest wspierany przez repozytorium Spring-Data. Zarządza transakcjami i zabezpieczeniami na poziomie metody. toJSON() metoda wykorzystuje Jackson 2.0 i musisz zarejestrować odpowiedni moduł do mapera, w moim przypadku dla hibernate 4.

W przypadku, gdy masz relacje dwukierunkowe, musisz przypisać wszystkie klasy encji za pomocą

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="jsonId")

To pozwala Jacksonowi 2.0 serializować zależności kołowe (nie było to możliwe we wcześniejszej wersji i wymaga, aby Twoje encje były adnotowane).

Będziesz musiał dodać następujące zależności:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.2.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate4</artifactId>
    <version>2.2.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.2.1</version>
    <type>jar</type>
</dependency>
 5
Author: beginner_,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-29 08:12:44

Używając Spring Boot (i dla Mongo DB) udało mi się z powodzeniem wykonać następujące czynności:

@RestController
@RequestMapping("/product")
public class ProductController {
   //...
    @RequestMapping(value = "/all", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE })
       HttpEntity<PagedResources<Product>> get(@PageableDefault Pageable p, PagedResourcesAssembler assembler) {
       Page<Product> product = productRepository.findAll(p);
       return new ResponseEntity<>(assembler.toResource(product), HttpStatus.OK);
    }
}

A Klasa modelu jest taka:

@Document(collection = "my_product")
@Data
@ToString(callSuper = true)
public class Product extends BaseProduct {
    private String itemCode;
    private String brand;
    private String sku;    
}
 0
Author: Pramod Alagambhat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-11 14:03:56