Skip to content

17. Udostępnianie bazy danych w sieci

17.1. Architektura serwisu internetowego / jSON

Wdrożymy następującą architekturę:

  • w [1] warstwy [DAO, [JPA], JDBC] są zaimplementowane przez jedną z 24 konfiguracji przedstawionych w poprzednich rozdziałach, a w szczególności w paragrafie 15;
  • warstwa [DAO] [3] klienta zdalnego implementuje ten sam interfejs co warstwa [DAO] [1], co pozwala nam na wykorzystanie tej samej warstwy testowej, co w poprzednich rozdziałach. Wszystko przebiega tak, jakby warstwy [2-3] były przezroczyste dla warstwy [4];

Będziemy opierać się na następujących projektach:

  • projekt [sgbd-config-jdbc], który konfiguruje warstwę JDBC jednej z sześciu warstw SGBD;
  • projekt [sgbd-config-jpa-*], który konfiguruje warstwę JPA w ramach wybranego SGBD dla jednej z trzech badanych implementacji JPA (Hibernate, EclipseLink, OpenJpa);
  • ogólny projekt [spring-jdbc-04], który implementuje warstwę [DAO] [1];
  • projekt generyczny [spring-jpa-generic], który implementuje warstwę [DAO] oraz [2];
  • projekt generyczny [spring-webjson-server-jdbc-generic], który implementuje usługę internetową opartą na projekcie [spring-jdbc-04];
  • projekt generyczny [spring-webjson-server-jpa-generic], który implementuje usługę internetową opartą na projekcie [spring-jpa-generic];
  • klient generyczny [spring-webjson-client-generic], który będzie jedynym klientem dla wszystkich 24 konfiguracji usługi internetowej;

17.2. Przygotowanie środowiska pracy

Będziemy pracować z następującymi elementami:

  • SGBD MySQL 5.6.25;
  • wdrożenie JPA Hibernate;

Zaimportuj następujące projekty do STS:

  
  • projekty [spring-webjson-*] znajdują się w folderze [<exemples>\spring-database-generic\spring-webjson];
  • wykonaj [Alt-F5], a następnie zregeneruj wszystkie powyższe projekty;

Aby sprawdzić, czy środowisko pracy zostało poprawnie zainstalowane, wykonaj następujące czynności:

  • uruchom usługę internetową z konfiguracją uruchomieniową [spring-webjson-server-jpa-generic-hibernate], która opiera się na implementacji JPA / Hibernate;

następnie:

  • uruchom klienta tej usługi internetowej z konfiguracją uruchomieniową [spring-webjson-client-generic], która jest testem JUnit:

Test powinien zakończyć się powodzeniem:

  • w [1] należy zatrzymać usługę internetową, a następnie uruchomić ją z konfiguracją wykonania [spring-webjson-server-jdbc-generic], która opiera się na implementacji JDBC:

następnie uruchom klienta tej usługi internetowej z konfiguracją wykonania [spring-webjson-client-generic]:

Test powinien zakończyć się powodzeniem:

 

17.3. Wdrożenie usługi internetowej / jSON / JDBC

W pierwszej kolejności skupiamy się na następującej architekturze:

gdzie warstwa [DAO] [1] komunikuje się bezpośrednio z warstwą JDBC z SGBD.

17.3.1. Projekt Eclipse serwisu internetowego

Projekt Eclipse usługi internetowej / jSON / JDBC wygląda następująco:

  

Jest to projekt Maven, którego plik [pom.xml] wygląda następująco:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>dvp.spring.database</groupId>
    <artifactId>spring-webjson-server-jdbc-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>spring-webjson-server-jdbc-generic</name>
    <description>démo spring mvc</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- warstwa internetowa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- warstwa [DAO] -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-jdbc-generic-04</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- wtyczki -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • wiersze 11–15: nadrzędny projekt Maven;
  • wiersze 24–28: zależność od warstwy [DAO / JDBC] zaimplementowanej przez projekt [spring-jdbc-generic-04];
  • wiersze 19–22: zależność od artefaktu [spring-boot-starter-web]. Artefakt ten zawiera wszystkie zależności niezbędne do utworzenia serwisu internetowego / jSON. Zawiera on również zbędne biblioteki. Konieczna byłaby zatem bardziej precyzyjna konfiguracja, ale ta konfiguracja jest praktyczna na początek.

Zależności wynikające z tej konfiguracji są następujące:

1
  • w pliku [1] widać, że Eclipse wykryło zależność od archiwum projektu [spring-jdbc-generic-04];

Powyższe zależności dotyczą zarówno warstwy [DAO], jak i warstwy [web].

17.3.2. Konfiguracja warstwy [web]

Warstwa [web] jest konfigurowana za pomocą dwóch plików konfiguracyjnych Spring:

  

17.3.2.1. Klasa [WebConfig]

Głównym zadaniem klasy [WebConfig] jest konfiguracja:

  • serwer Tomcat, na którym zostanie wdrożona usługa internetowa;
  • filtry jSON służące do serializacji i deserializacji obiektów [Produit] oraz [Categorie]:

package spring.webjson.server.config;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // -------------------------------- konfiguracja warstwy [web]
    @Autowired
    private ApplicationContext context;

    @Bean
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet servlet=new DispatcherServlet((WebApplicationContext) context);
        return servlet;
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new ServletRegistrationBean(dispatcherServlet, "/*");
    }

    @Bean
    public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
        return new TomcatEmbeddedServletContainerFactory("", 8081);
    }

    // -------------------------------- konfiguracja filtrów [json]
    ...
}
  • wiersz 25: klasa ta jest klasą konfiguracyjną Springa;
  • wiersz 26: adnotacja [@EnableWebMvc] wskazuje, że warstwa internetowa jest zaimplementowana przy użyciu Springa MVC. Spowoduje to domyślne konfiguracje, których nie będziemy musieli wprowadzać;
  • wiersze 30–31: wstrzyknięcie kontekstu Springa aplikacji;
  • wiersze 33–37: definicja bean’a [dispatcherServlet], który w aplikacjach Spring MVC pełni rolę [FrontController], którego zadaniem jest kierowanie żądań klientów do kontrolera zdolnego do ich przetworzenia;
  • wiersze 39–42: rejestrowany jest serwlet usługi internetowej wraz z URL, które obsługuje. W tym miejscu wpisano [/*], co oznacza wszystkie URLs;
  • wiersze 44–47: definicja komponentu [embeddedServletContainerFactory], który określa serwer WWW, z którego ma być korzystano. W tym przypadku będzie to serwer WWW Tomcat [http://tomcat.apache.org/]. Można również użyć serwera Jetty o nazwie [http://www.eclipse.org/jetty/]. Oba serwery są wbudowane w zależnościach Maven. Gdy [Spring Boot] steruje uruchomieniem projektu, automatycznie uruchamia serwer WWW wskazany w konfiguracji i wdraża na nim usługę lub aplikację internetową;

Konfiguracja filtrów jSON odbywa się w następujący sposób:


package spring.webjson.server.config;

import java.util.List;
...

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // -------------------------------- konfiguracja warstwy [web]
...
    // -------------------------------- konfiguracja filtrów [json]
    // mapowanie jSON
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();
        converter.setObjectMapper(objectMapper);
        return converter;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
        super.configureMessageConverters(converters);
    }

    // filtry jSON
    @Bean
    public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        return mappingJackson2HttpMessageConverter.getObjectMapper();
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }
}
  • wiersz 8: klasa [WebConfig] dziedziczy po klasie [WebMvcConfigurerAdapter]. Ta ostatnia klasa konfiguruje aplikację internetową przy użyciu wartości domyślnych. Jeśli chcemy dostosować tę konfigurację, należy przedefiniować niektóre metody tej klasy. W tym przypadku chcemy przedefiniować metodę [configureMessageConverters] klasy [22-26] (zwróć uwagę na adnotację @Override), która definiuje listę „konwerterów”. Serwis internetowy /jSON i jego klient wymieniają między sobą wiersze tekstu. Konwerter to narzędzie zdolne do utworzenia obiektu na podstawie otrzymanego wiersza tekstu (deseryalizacja) oraz do utworzenia wiersza tekstu na podstawie obiektu (serializacja). W tym przypadku wierszami tekstowymi będą ciągi znaków jSON. Będziemy więc mówić o serializacji/deserializacji jSON;
  • wiersz 23: metoda [configureMessageConverters] przyjmuje jako parametr listę konwerterów;
  • wiersze 24–25: do tej listy dodano konwerter jSON [MappingJackson2HttpMessageConverter] z wierszy [14-20]. Umożliwi to wymianę danych jSON między klientem a serwerem;
  • wiersze [14-20]: definiują konwerter jSON zaimplementowany przez klasę [MappingJackson2HttpMessageConverter]. Klasę tę (wiersz 10) można znaleźć w zależnościach Maven projektu;
  • wiersze [17-18]: tworzony jest mapper jSON i przypisywany do konwertera [MappingJackson2HttpMessageConverter];
  • wiersze [29-32]: definiują mapper jSON utworzony w wierszu 17 jako bean Spring. Dzięki temu trafia on do kontekstu Spring i staje się dostępny do wstrzyknięcia do innych beanów lub wykorzystania w kodzie aplikacji internetowej;
  • wiersze 34–41: definiują filtr jSON dla poprzedniego mapera jSON;
  • linia 35: adnotacja [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] sprawia, że zdefiniowany tutaj bean nie jest singletonem. Za każdym razem, gdy zostanie on zażądany z kontekstu, metoda [jsonMapperCategorieWithoutProduits] zostanie ponownie wykonana. Jest to tutaj konieczne, ponieważ definiujemy cztery filtry jSON. Jednak w danym momencie aktywny może być tylko jeden. Nadając beanowi zakres [ConfigurableBeanFactory.SCOPE_PROTOTYPE], mamy pewność, że metoda zostanie ponownie wykonana, a poprzedni filtr zostanie zastąpiony nowym;
  • aby zrozumieć te filtry, należy pamiętać, że w warstwie [DAO]:
    • entyteta [Produit] została oznaczona adnotacją [jsonFilterProduit];
    • entyteta [Categorie] została oznaczona adnotacją [jsonFilterCategorie];

Należy zatem zdefiniować filtry o tych nazwach.

  • wiersze [34-41]: definiują filtr o nazwie [jsonMapperShortCategorie], który pozwala uzyskać reprezentację jSON kategorii bez jej produktów;
  • wiersze [43-51]: definiują filtr o nazwie [jsonMapperLongCategorie], który pozwala uzyskać reprezentację jSON kategorii wraz z jej produktami;
  • wiersze [53-60]: definiują filtr o nazwie [jsonMapperShortProduit], który pozwala uzyskać reprezentację jSON produktu bez jego kategorii;
  • wiersze [62-70]: definiują filtr o nazwie [jsonMapperLongProduit], który pozwala uzyskać reprezentację jSON produktu wraz z kategorią;

17.3.2.2. Klasa [AppConfig]

Klasa [AppConfig] konfiguruje całą aplikację, tj. warstwy [web] i [DAO]:


package spring.webjson.server.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan(basePackages = { "spring.webjson.server.service" })
@Import({ spring.jdbc.config.AppConfig.class, WebConfig.class })
public class AppConfig {

}
  • wiersz 7: klasa ta jest klasą konfiguracyjną Spring;
  • wiersz 9: importujemy bean’y z warstwy [DAO / JDBC] oraz te zdefiniowane przez klasę [WebConfig]. Wszystkie bean z warstwy [DAO] będą zatem dostępne w aplikacji internetowej / jSON;
  • wiersz 8: wskazuje, w których pakietach znajdują się inne komponenty Spring;

17.3.3. Wyjątek [ServerException]

  

Podobnie jak w poprzednich rozdziałach, warstwa [DAO] generowała niekontrolowany wyjątek [DaoException], natomiast warstwa [web] wygeneruje niekontrolowany wyjątek [ServerException]:


package spring.webjson.server.infrastructure;

import generic.jdbc.infrastructure.UncheckedException;

public class ServerException extends UncheckedException {

    private static final long serialVersionUID = 1L;

    // producenci
    public ServerException() {
        super();
    }

    public ServerException(int code, Throwable e, String simpleClassName) {
        super(code, e, simpleClassName);
    }
}
  • wiersz 5: klasa [ServerException] rozszerza klasę [UncheckedException] zdefiniowaną w projekcie konfigurującym warstwę JDBC (wiersz 3);

17.3.4. Kontrolery

  

Będziemy mieli tutaj dwa kontrolery:

  • [CategorieController] będzie obsługiwać zapytania dotyczące kategorii;
  • [CategorieController] będzie obsługiwać zapytania dotyczące produktów;

Metody URL udostępniane przez kontrolery odpowiadają jeden do jednego metodom interfejsów [DaoCategorie] i [DaoProduit] warstwy [DAO]:

Tak więc, jak pokazano powyżej:

  • metoda internetowa [deleteAllCategories] wywoła metodę [deleteAllEntities] z klasy [DaoCategorie];
  • metoda internetowa [getShortCategoriesById] wywoła metodę [getShortEntitiesById] z klasy [DaoCategorie];

To samo dotyczy produktów:

17.3.4.1. Produkty URL udostępniane przez kontroler [CategorieController]

URL
Méthode

@RequestMapping(value = "/saveCategories",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<List<CoreCategorie>>
 saveCategories
(HttpServletRequest request)
====
La méthode reçoit par un POST les catégories à persister. 
Celles-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les catégories sont persistées par la méthode 
[saveEntities] de la couche [DaoCategorie]. Seules les clés
 primaires des objets persistés (catégories / produits) sont
 renvoyées au client.

@RequestMapping(value = "/deleteAllCategories",
 method = RequestMethod.GET)

public Response<Void> deleteAllCategories()
====
L'URL n'a aucun paramètre. Les catégories sont supprimées par
 la méthode [deleteAllEntities] de la couche [DaoCategorie].

@RequestMapping(value = "/deleteCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteCategoriesById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des
 catégories à supprimer. Celles-ci sont accessibles dans
 l'objet [HttpServletRequest request]. Les catégories sont
 supprimées par la méthode [deleteEntitiesById] de la couche
 [DaoCategorie].

@RequestMapping(value = "/deleteCategoriesByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteCategoriesByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des catégories à 
supprimer. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories sont supprimées
 par la méthode [deleteEntitiesByname] de la couche 
[DaoCategorie].

@RequestMapping(value = "/getAllShortCategories",
 method = RequestMethod.GET)

public Response<List<Categorie>> getAllShortCategories()
====
L'URL n'a aucun paramètre. Les catégories courtes sont 
obtenues par la méthode [getAllShortEntities] de la couche
 [DaoCategorie].

@RequestMapping(value = "/getAllLongCategories",
 method = RequestMethod.GET)

public Response<List<Categorie>> getAllLongCategories()
====
L'URL n'a aucun paramètre. Les catégories longues sont
 obtenues par la méthode [getAllLongEntities] de la couche 
[DaoCategorie].

@RequestMapping(value = "/getLongCategoriesById",
 method = RequestMethod.POST)

public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des
 catégories désirées. Celles-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories longues sont
 obtenues par la méthode [getLongEntitiesById] de la couche
 [DaoCategorie].

@RequestMapping(value = "/getLongCategoriesByName",
 method = RequestMethod.POST)

public Response<List<Categorie>> getLongCategoriesByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des catégories
 désirées. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories longues sont 
obtenues par la méthode [getLongEntitiesByName] de la couche
 [DaoCategorie].

@RequestMapping(value = "/getShortCategoriesByName",
 method = RequestMethod.POST)

public Response<List<Categorie>> getShortCategoriesByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des catégories
 désirées. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories courtes sont
 obtenues par la méthode [getShortEntitiesByName] de la
 couche [DaoCategorie].

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST)

public Response<List<Categorie>> getShortCategoriesById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des
 catégories désirées. Celles-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories courtes sont
 obtenues par la méthode [getShortEntitiesById] de la couche
 [DaoCategorie].

17.3.4.2. URL udostępnione przez kontroler [ProduitController]

URL
Méthode

@RequestMapping(value = "/saveProduits", method =
 RequestMethod.POST, consumes = "application/json;
 charset=UTF-8")

public Response<List<CoreProduit>> saveProduits
(HttpServletRequest request)
====
La méthode reçoit les produits à persister par un POST. Ceux-
ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits sont persistés par la méthode
 [saveEntities] de la couche [DaoProduit]. Seules les clés
 primaires des produits sont renvoyées au client.

@RequestMapping(value = "/deleteAllProduits",
 method = RequestMethod.GET)

public Response<Void> deleteAllProduits()
====
L'URL n'a aucun paramètre. Les produits sont supprimés par la
 méthode [deleteAllEntities] de la couche [DaoProduit].

@RequestMapping(value = "/deleteProduitsById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteProduitsById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des produits
 à supprimer. Celles-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les produits sont supprimés
 par la méthode [deleteEntitiesById] de la couche 
[DaoProduit].

@RequestMapping(value = "/deleteProduitsByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteProduitsByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des produits à
 supprimer. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les produits sont supprimés
 par la méthode [deleteEntitiesByname] de la couche
 [DaoProduit].

@RequestMapping(value = "/getAllShortProduits",
 method = RequestMethod.GET)

public Response<List<Produit>> getAllShortProduits()
====
L'URL n'a aucun paramètre. Les catégories courtes sont
 obtenues par la méthode [getAllShortEntities] de la couche
 [DaoProduit].

@RequestMapping(value = "/getAllLongProduits",
 method = RequestMethod.GET)

public Response<List<Produit>> getAllLongProduits()
====
L'URL n'a aucun paramètre. Les produits longs sont obtenus
 par la méthode [getAllLongEntities] de la couche
 [DaoProduit].

@RequestMapping(value = "/getLongProduitsById",
 method = RequestMethod.POST)

public Response<List<Produit>> getLongProduitsById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des produits
 désirés. Celles-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les produits longs sont 
obtenus par la méthode [getLongEntitiesById] de la couche

 [DaoProduit].

@RequestMapping(value = "/getLongProduitsByName",
 method = RequestMethod.POST)

public Response<List<Produit>> getLongProduitsByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des produits désirés.
 Ceux-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits longs sont obtenus par la méthode
 [getLongEntitiesByName] de la couche [DaoProduit].

@RequestMapping(value = "/getShortProduitsByName",
 method = RequestMethod.POST)

public Response<List<Produit>> getShortProduitsByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des produits désirés.
 Ceux-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits courts sont obtenus par la méthode 
[getShortEntitiesByName] de la couche [DaoProduit].

@RequestMapping(value = "/getShortProduitsById",
 method = RequestMethod.POST)

public Response<List<Produit>> getShortProduitsById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des produits
 désirés. Celles-ci sont accessibles dans l'objet 
[HttpServletRequest request]. Les produits courts sont 
obtenus par la méthode [getShortEntitiesById] de la couche
 [DaoProduit].

17.3.5. Ogólna implementacja usługi internetowej

  

Lista elementów URL udostępnianych przez usługę internetową pokazuje, że oferowane są te same typy elementów URL do zarządzania kategoriami i produktami. Zamiast pisać dwa bardzo podobne kontrolery, sprawimy, by wywodziły się one z klasy, która wykona całą pracę wspólną dla obu kontrolerów. Będzie to powyższa klasa [AbstractController]. Klasa ta zaimplementuje następujący interfejs [Iws]:


package spring.webjson.server.service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import spring.jdbc.entities.AbstractCoreEntity;

public interface Iws<T extends AbstractCoreEntity> {

    // lista wszystkich elementów T
    public Response<List<T>> getAllShortEntities();

    public Response<List<T>> getAllLongEntities();

    // poszczególnych jednostek – wersja skrócona
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request);

    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);

    // poszczególnych jednostek – wersja długa
    public Response<List<T>> getLongEntitiesById(HttpServletRequest request);

    public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);

    // aktualizacja wielu jednostek
    public Response<List<T>> saveEntities(HttpServletRequest request);

    // usunięcie wszystkich elementów
    public  Response<Void> deleteAllEntities();

    // usunięcie wielu elementów
    public  Response<Void> deleteEntitiesById(HttpServletRequest request);

    public  Response<Void> deleteEntitiesByName(HttpServletRequest request);
}

Interfejs ten przejmuje metody z interfejsu warstwy [DAO], z której będziemy korzystać:


package spring.jdbc.dao;

import java.util.List;

import spring.jdbc.entities.AbstractCoreEntity;

public interface IDao<T extends AbstractCoreEntity> {

    // lista wszystkich elementów T
    public List<T> getAllShortEntities();

    public List<T> getAllLongEntities();

    // poszczególnych elementów – wersja skrócona
    public List<T> getShortEntitiesById(Iterable<Long> ids);

    public List<T> getShortEntitiesById(Long... ids);

    public List<T> getShortEntitiesByName(Iterable<String> names);

    public List<T> getShortEntitiesByName(String... names);

    // określonych elementów – wersja długa
    public List<T> getLongEntitiesById(Iterable<Long> ids);

    public List<T> getLongEntitiesById(Long... ids);

    public List<T> getLongEntitiesByName(Iterable<String> names);

    public List<T> getLongEntitiesByName(String... names);

    // aktualizacja wielu elementów
    public List<T> saveEntities(Iterable<T> entities);

    public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);

    // usunięcie wszystkich elementów
    public void deleteAllEntities();

    // usunięcie kilku elementów
    public void deleteEntitiesById(Iterable<Long> ids);

    public void deleteEntitiesById(Long... ids);

    public void deleteEntitiesByName(Iterable<String> names);

    public void deleteEntitiesByName(String... names);

    public void deleteEntitiesByEntity(Iterable<T> entities);

    public void deleteEntitiesByEntity(@SuppressWarnings("unchecked") T... entities);
}

Przejście z interfejsu [IDao<T>] warstwy [DAO] do interfejsu [Iws<T>] serwisu internetowego odbyło się zgodnie z następującymi zasadami:

  • metody interfejsu [Iws<T>] nie będą generować wyjątków. Jeśli wystąpi wyjątek, zostanie on zamknięty w obiekcie [Response];
  • warianty parametrów, takie jak w wierszach 45 i 47 [Iterable<String> names, String... names], znikają. Metody pobierają swoje parametry z żądania HTTP od klienta typu [HttpServletRequest request];

Wszystkie odpowiedzi serwisu internetowego zostaną zawarte w następującym obiekcie [Response]:

  

package spring.webjson.server.service;


public class Response<T> {

    // ----------------- właściwości
    // status operacji
    private int status;
    // komunikat o błędzie
    private String exception;
    // treść odpowiedzi
    private T body;

    // konstruktory
    public Response() {

    }

    public Response(int status, String exception, T body) {
        this.status = status;
        this.exception = exception;
        this.body = body;
    }

    // metody pobierające i ustawiające
...
}
  • wiersz 4: odpowiedź zawiera typ T;
  • wiersz 12: odpowiedź typu T;
  • wiersze 7–10: może się zdarzyć, że metoda napotka wyjątek. W takim przypadku zwróci odpowiedź zawierającą:
    • wiersz 8: status!=0;
    • wiersz 10: komunikat o błędzie;

Klasa [AbstractController] ma następującą postać:


package spring.webjson.server.service;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import spring.jdbc.dao.IDao;
import spring.jdbc.entities.AbstractCoreEntity;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.infrastructure.ServerException;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;

public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {

    @Autowired
    protected ApplicationContext context;

    // warstwa DAO
    private IDao<T> dao;

    abstract protected IDao<T> getDao();

    // lokalny
    private String simpleClassName = getClass().getSimpleName();

    @PostConstruct
    public void init(){
        dao=getDao();
    }
    
    @Override
    public Response<List<T>> getAllShortEntities() {
        try {
            // odpowiedź
            return new Response<List<T>>(0, null, dao.getAllShortEntities());
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1007, e, simpleClassName).toString(), null);
        }
    }

    @Override
    public Response<List<T>> getAllLongEntities() {
        try {
            // odpowiedź
            return new Response<List<T>>(0, null, dao.getAllLongEntities());
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1008, e, simpleClassName).toString(), null);
        }
    }

    @Override
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
    ...
    }

    @Override
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
    ...
    }

    @Override
    public Response<List<T>> getLongEntitiesById(HttpServletRequest request) {
    ...
    }

    @Override
    public Response<List<T>> getLongEntitiesByName(HttpServletRequest request) {
    ...
    }

        @Override
    public Response<List<T>> saveEntities(HttpServletRequest request) {
        return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
    }


    @Override
    public Response<Void> deleteAllEntities() {
        try {
            // usuwamy
            dao.deleteAllEntities();
            // odpowiedź
            return new Response<Void>(0, null, null);
        } catch (DaoException e) {
            return new Response<Void>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<Void>(2, new ServerException(1014, e, simpleClassName).toString(), null);
        }
    }

    @Override
    public Response<Void> deleteEntitiesById(HttpServletRequest request) {
        ...
    }

    @Override
    public Response<Void> deleteEntitiesByName(HttpServletRequest request) {
    ...
    }

}

Wszystkie metody są zaimplementowane w ten sam sposób:

  1. jeśli oczekują informacji, pobierają je z obiektu [HttpServletRequest request];
  2. wywołują metodę warstwy [DAO] o tej samej nazwie co one same;
  3. obsługują wyjątki, które mogą wystąpić albo w operacji 1 pobierania parametrów, albo w operacji 2 wywołania warstwy [DAO];

Przyjrzyjmy się najpierw, w jaki sposób warstwa [DAO] jest wstrzykiwana do klasy [AbstractController]:


public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {

    @Autowired
    protected ApplicationContext context;

    // warstwa DAO
    private IDao<T> dao;

    abstract protected IDao<T> getDao();

    @PostConstruct
    public void init(){
        dao=getDao();
}

  • wiersz 1: klasa jest abstrakcyjna i implementuje interfejs generyczny [Iws<T>];
  • wiersze 3–4: wstrzyknięcie kontekstu Spring;
  • wiersz 7: wciąż nieznane odwołanie do warstwy [DAO], która ma zostać użyta;
  • wiersz 9: abstrakcyjna metoda [getDao], która zwróci odwołanie do warstwy [DAO], która ma zostać użyta. Metoda ta zostanie przedefiniowana przez klasę potomną, a zatem to właśnie klasa potomna wskaże, która warstwa [DAO] ma zostać użyta (DaoProduit lub DaoCategorie);
  • wiersz 11: adnotacja [@PostConstruct] oznacza metodę, która ma zostać wykonana po zakończeniu instancjonowania obiektu. Po zakończeniu instancjonowania wstrzyknięcia Spring zostały już wykonane. Klasa potomna uzyska wówczas odwołanie do swojej warstwy [DAO] i będzie mogła je przekazać klasie nadrzędnej;

Metoda [getShortEntitiesById] ma następującą postać:


    @Override
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
        try {
            // odzyskuje się wartość przesłaną
            String body = CharStreams.toString(request.getReader());
            // deserializujemy ją
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
            });
            // odpowiedź
            return new Response<List<T>>(0, null, dao.getShortEntitiesById(ids));
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1009, e, simpleClassName).toString(), null);
        }
}
  • wiersz 5: wartość przesłana przez klienta będzie ciągiem znaków jSON. W tym miejscu jest ona pobierana;
  • wiersze 7–9: ciąg znaków jSON zawiera listę kluczy głównych podmiotów, dla których potrzebna jest skrócona wersja;
  • wiersz 11: wywoływana jest metoda o tej samej nazwie [DAO]. Odpowiedź typu [List<T>] jest zawarta w obiekcie [Response];
  • wiersz 13: przypadek, w którym warstwa [DAO] zgłosiła wyjątek;
  • wiersz 15: przypadek pozostałych wyjątków, w szczególności możliwego wyjątku podczas deseryalizacji parametru jSON, wiersz 8;

Metoda [getShortEntitiesByName] działa analogicznie:


@Override
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
        try {
            // odbieramy przesłaną wartość
            String body = CharStreams.toString(request.getReader());
            // deserializujemy ją
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
            });
            // odpowiedź
            return new Response<List<T>>(0, null, dao.getShortEntitiesByName(noms));
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1010, e, simpleClassName).toString(), null);
        }
}
  • wiersze 4–9: w tym przypadku parametr jSON stanowi listę nazw kategorii, dla których wymagana jest wersja skrócona;

Metoda [saveEntities] nie została zaimplementowana, ponieważ jest w znacznym stopniu zależna od charakteru entytety, którą należy zapisać – [Categorie] lub [Produit]. Kod do wyodrębnienia jest niewielki. Zadanie to pozostawia się zatem klasom potomnym.


        @Override
    public Response<List<T>> saveEntities(HttpServletRequest request) {
        return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
    }

17.3.6. Kontroler [CategorieController]

  

Kontroler [CategorieController] zarządza przetwarzaniem URL w odniesieniu do kategorii:


package spring.webjson.server.service;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Categorie;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreCategorie;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;

@RestController
public class CategorieController extends AbstractController<Categorie> {

    @Autowired
    private IDao<Categorie> daoCategorie;

    @Override
    protected IDao<Categorie> getDao() {
        return daoCategorie;
    }

    // lokalny
    private String simpleClassName = getClass().getSimpleName();

    @RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories() {
        // nadrzędny
        Response<List<Categorie>> response = super.getAllShortEntities();
        // filtry serializacji jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllLongCategories() {
        // nadrzędny
        Response<List<Categorie>> response = super.getAllLongEntities();
        // filtry serializacji jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
        // element nadrzędny
        Response<List<Categorie>> response = super.getShortEntitiesById(request);
        // filtry serializacji jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
        // element nadrzędny
        Response<List<Categorie>> response = super.getShortEntitiesByName(request);
        // filtry serializacji jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
        // element nadrzędny
        Response<List<Categorie>> response = super.getLongEntitiesById(request);
        // filtry serializacji jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
        // element nadrzędny
        Response<List<Categorie>> response = super.getLongEntitiesByName(request);
        // filtry serializacji jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
    ...
    }

    @RequestMapping(value = "/deleteAllCategories", method = RequestMethod.GET)
    public Response<Void> deleteAllCategories() {
        return super.deleteAllEntities();
    }

    @RequestMapping(value = "/deleteCategoriesById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteCategoriesById(HttpServletRequest request) {
        return super.deleteEntitiesById(request);
    }

    @RequestMapping(value = "/deleteCategoriesByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteCategoriesByName(HttpServletRequest request) {
        return super.deleteEntitiesByName(request);
    }

}
  • wiersz 26: klasa [CategorieController] rozszerza klasę [AbstractController];
  • wiersz 25: adnotacja [@RestController] sprawia, że klasa staje się komponentem Spring. Adnotacja ta wskazuje ponadto, że klasa jest usługą internetową, której metody wysyłają odpowiedzi bezpośrednio do klienta w formacie jSON;
  • wiersze 28–29: w tym miejscu wstrzykiwane jest odwołanie do warstwy [DAO];
  • wiersze 31–34: redefinicja metody [getDao], zadeklarowanej jako abstrakcyjna w klasie nadrzędnej, której celem jest zwrócenie odwołania do warstwy [DAO] do wykorzystania;

Wszystkie metody są zbudowane według tego samego wzorca:

  • przekazanie przetwarzania do klasy nadrzędnej;
  • inicjalizacja mapera jSON, który przeprowadzi serializację odpowiedzi;
  • wysłanie odpowiedzi;

Przyjrzyjmy się sygnaturze kilku metod URL:


@RequestMapping(value = "/getAllShortCategories", method
 = RequestMethod.GET)
- funkcja URL [/getAllShortCategories] jest wywoływana z parametrem GET

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- funkcja URL [/getShortCategoriesById] jest wywoływana z funkcją POST. Wartością przekazaną jest ciąg znaków jSON zawierający klucze główne
pożądanych kategorii;

@RequestMapping(value = "/getLongCategoriesByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
– funkcja URL [/getLongCategoriesByName] jest wywoływana z
POST. Wartością wysyłaną jest ciąg znaków jSON zawierający nazwy
pożądanych kategorii;

Teraz przyjrzyjmy się bliżej metodzie [saveCategories], która nie jest zgodna z formatem pozostałych metod:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // zapisujemy kategorie
        try {
            // odzyskuje się wartość przesłaną
            String body = CharStreams.toString(request.getReader());
            // deserializujemy ją
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
            });
            // zapisujemy kategorie
            categories = daoCategorie.saveEntities(categories);
            // zwracamy wynik
            List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
            for (Categorie categorie : categories) {
                CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
                coreCategories.add(coreCategorie);
                List<Produit> produits = categorie.getProduits();
                if (produits != null) {
                    List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
                    for (Produit produit : categorie.getProduits()) {
                        coreProduits.add(new CoreProduit(produit.getId()));
                    }
                    coreCategorie.setCoreProduits(coreProduits);
                }
            }
            // wynik
            return new Response<List<CoreCategorie>>(0, null, coreCategories);
        } catch (DaoException e) {
            return new Response<List<CoreCategorie>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<CoreCategorie>>(2, new ServerException(1020, e, simpleClassName).toString(), null);
        }
}
  • wiersz 1: ciągowi URL [/saveCategories] towarzyszy wartość wysyłana. Jest to ciąg jSON zawierający pełne nazwy kategorii, które mają zostać zachowane;
  • wiersze 5–10: kategorie, które mają zostać zachowane, są odtwarzane na podstawie ciągu znaków jSON. Łącze [produit.categorie], które łączy [Produit] z jego [Catégorie], ma wartość null, ponieważ w długiej wersji [Categorie] każdy element [Produit] występuje w wersji skróconej, bez pola [categorie]. Nie stanowi to przeszkody, ponieważ warstwa [DAO] zaimplementowana przy użyciu JDBC nie potrzebuje tej informacji;
  • wiersz 12: kategorie są zapisywane w bazie. Otrzymana lista kategorii została wzbogacona o klucze główne zapisanych elementów, kategorii i produktów. Nic więcej się nie zmieniło. Zamiast zwracać całą otrzymaną listę, co wiąże się z pewnym obciążeniem, zwracamy jedynie klucze główne elementów tej listy. W tym celu wykorzystujemy następujące klasy: [CoreCategorie] i [CoreProduit]:
  

package spring.webjson.server.entities;

import java.util.List;

public class CoreCategorie {

    // klucz główny
    private Long id;
    
    // producenci
    public CoreCategorie() {

    }

    public CoreCategorie(Long id) {
        this.id=id;
    }

    // lista produktów
    private List<CoreProduit> coreProduits;

    // metody pobierające i ustawiające
    ...
}
  • wiersz 8: klucz podstawowy produktu;
  • wiersz 20: klucze główne tych produktów;

package spring.webjson.server.entities;

public class CoreProduit {

    // klucz główny
    private Long id;

    // konstruktory
    public CoreProduit() {

    }

    public CoreProduit(Long id) {
        this.id = id;
    }

    // metody pobierające i ustawiające
...
}
  • wiersz 6: klucz podstawowy produktu;

Wróćmy do kodu metody [saveCategories]:


...            
// zapisujemy kategorie
            categories = daoCategorie.saveEntities(categories);
            // zwracamy wynik
            List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
            for (Categorie categorie : categories) {
                CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
                coreCategories.add(coreCategorie);
                List<Produit> produits = categorie.getProduits();
                if (produits != null) {
                    List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
                    for (Produit produit : categorie.getProduits()) {
                        coreProduits.add(new CoreProduit(produit.getId()));
                    }
                    coreCategorie.setCoreProduits(coreProduits);
                }
            }
            // wynik
            return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
  • wiersze 5–17: tworzymy listę [CoreCategorie], którą zwrócimy klientowi zdalnemu;
  • wiersz 19: odpowiedź jest zwracana i serializowana w formacie jSON;

17.3.7. Zarządzanie filtrami jSON

Dla każdej metody kontrolera istnieją dwa momenty serializacji / deserializacji jSON:

  • deserializacja wartości wprowadzonej: jest tu obsługiwana jawnie;
  • serializacja wyniku: jest tutaj obsługiwana niejawnie;

Zacznijmy od deseryalizacji wartości przesłanej w [CategorieController.saveCategories]:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // zapisujemy kategorie
        try {
            // pobieranie wartości przesłanej
            String body = CharStreams.toString(request.getReader());
            // deserializujemy ją
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
  • wiersz 8: w kontekście Springa pobieramy mapper skonfigurowany do obsługi filtra jSON [jsonMapperLongCategorie]. Wróćmy do definicji tego mappera w klasie konfiguracyjnej [WebConfig]:

// -------------------------------- konfiguracja filtrów [json]
    // mapowanie jSON
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();
        converter.setObjectMapper(objectMapper);
        return converter;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
        super.configureMessageConverters(converters);
    }

    // filtry jSON
    @Bean
    public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        return mappingJackson2HttpMessageConverter.getObjectMapper();
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }

  • wiersze 32–40: mapper jSON [jsonMapperLongCategorie] pobrany przez klasę [CategorieController];
  • wiersz 35: ten mapper jest zwracany przez metodę [jsonMapper] z wierszy 18–21;
  • wiersze 18–21: metoda [jsonMapper] zwraca mapper jSON z konwertera [MappingJackson2HttpMessageConverter] z wierszy 3–9;

Innymi słowy, mapper jSON pobierany przez wiersz 4 poniżej z [CategorieController.saveCategories]:


            // odbieramy przesłaną wartość
            String body = CharStreams.toString(request.getReader());
            // deserializujemy ją
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});

jest konwerterem używanym domyślnie przez Spring MVC do deseryalizacji wartości przesłanej przez klienta oraz serializacji wyniku, który jest do niego wysyłany. W powyższych wierszach nie nastąpiła domyślna deseryalizacja przesłanej wartości. Aby to zrobić, należałoby napisać:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(@RequestBody List<Categorie> categories) {

W tym przypadku nastąpiłaby automatyczna deseryalizacja wartości przesłanej w parametrze [categories]. Pojawiłby się jednak problem z filtrem [jsonFilterCategorie], który posiadają encje [Categorie]. Należy go skonfigurować. Z tego powodu zdecydowaliśmy się na jawną deseryalizację (wiersze 4–5). Drugą kwestią, na którą należy zwrócić uwagę, jest to, że mapper z wiersza 4 (który jest domyślnie używany przez Spring MVC) nadaje się również do serializacji wyniku [Response<List<CoreCategorie>]. W rzeczywistości encja [CoreCategorie] nie posiada filtra jSON. Nie ma zatem potrzeby konfigurowania mapera jSON uzyskany z dodatkowym filtrem. W tym przypadku nastąpi domyślna serializacja odpowiedzi wysyłanej do klienta.

17.3.8. Kontroler [ProduitController]

  

Kontroler [ProduitController] zarządza przetwarzaniem URL dotyczących produktów. Jego kod jest analogiczny do kodu kontrolera [CategorieController]:


package spring.webjson.server.service;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;

@RestController
public class ProduitController extends AbstractController<Produit> {

    @Autowired
    private IDao<Produit> daoProduit;

    @Override
    protected IDao<Produit> getDao() {
        return daoProduit;
    }

    // lokalny
    private String simpleClassName = getClass().getSimpleName();

    @RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllShortProduits() {
        // element nadrzędny
        Response<List<Produit>> response = super.getAllShortEntities();
        // filtry serializacji jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllLongProduits() {
        // nadrzędny
        Response<List<Produit>> response = super.getAllLongEntities();
        // filtry serializacji jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
        // element nadrzędny
        Response<List<Produit>> response = super.getShortEntitiesById(request);
        // filtry serializacji jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
        // element nadrzędny
        Response<List<Produit>> response = super.getShortEntitiesByName(request);
        // filtry serializacji jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
        // element nadrzędny
        Response<List<Produit>> response = super.getLongEntitiesById(request);
        // filtry serializacji jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
        // element nadrzędny
        Response<List<Produit>> response = super.getLongEntitiesByName(request);
        // filtry serializacji jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // odpowiedź
        return response;
    }

    @RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
        ...
    }

    @RequestMapping(value = "/deleteAllProduits", method = RequestMethod.GET)
    public Response<Void> deleteAllProduits() {
        return super.deleteAllEntities();
    }

    @RequestMapping(value = "/deleteProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteProduitsById(HttpServletRequest request) {
        return super.deleteEntitiesById(request);
    }

    @RequestMapping(value = "/deleteProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteProduitsByName(HttpServletRequest request) {
        return super.deleteEntitiesByName(request);
    }

}

Jedynie metoda [saveProduits] ma strukturę odmienną od pozostałych metod:


@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
        try {
            // odbieramy przesłaną wartość
            String body = CharStreams.toString(request.getReader());
            // deserializujemy ją
            ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
            List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
            });
            // zapisujemy produkty
            produits = daoProduit.saveEntities(produits);
            List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
            for (Produit produit : produits) {
                coreProduits.add(new CoreProduit(produit.getId()));
            }
            // zwracamy odpowiedź
            return new Response<List<CoreProduit>>(0, null, coreProduits);
        } catch (DaoException e) {
            return new Response<List<CoreProduit>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<CoreProduit>>(2, new ServerException(1021, e, simpleClassName).toString(), null);
        }
}
  • wiersze 4–9: na podstawie otrzymanego ciągu znaków jSON odtwarza się listę [Produit], które mają zostać zachowane. Ponieważ otrzymany ciąg jSON dotyczy skróconych wersji produktów, pole [categorie] tych produktów ma wartość null. Ponownie warstwa DAO / JDBC nie potrzebuje tej informacji;
  • wiersz 11: produkty są zapisywane;
  • wiersze 12–15: tworzona jest lista [CoreProduit] do zwrócenia;
  • wiersz 18: zwracana jest odpowiedź, która zostanie zserializowana (domyślna serializacja przeprowadzana przez Spring MVC) przez mapper z wiersza 7 przed wysłaniem do klienta zdalnego (por. omówienie w paragrafie 17.3.7);

17.3.9. Klasa wykonawcza serwisu internetowego / jSON

  

Klasa [Boot] jest klasą wykonalną projektu:


package spring.webjson.server.boot;

import org.springframework.boot.SpringApplication;

import spring.webjson.server.config.AppConfig;

public class Boot {

    public static void main(String[] args) {
        SpringApplication.run(AppConfig.class, args);
    }
}
  • wiersz 10: wykonywana jest metoda statyczna [SpringApplication.run]. Klasa [SpringApplication] jest klasą projektu [spring Boot] (wiersz 3). Przekazywane są do niej dwa parametry:
    • [AppConfig.class]: klasa, która konfiguruje całą aplikację;
    • [args]: ewentualne argumenty przekazane do metody [main] w wierszu 9. Ten parametr nie jest tutaj wykorzystywany;

Po uruchomieniu tej klasy pojawiają się następujące logi:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

11:34:08.661 [main] INFO  spring.webjson.server.boot.Boot - Starting Boot on Gportpers3 with PID 6796 (started by ST in D:\data\istia-1415\spring data\dvp\dvp-spring-database-05\spring-database-generic\spring-webjson\spring-webjson-server-jdbc-generic)
11:34:08.700 [main] INFO  o.s.b.c.e.AnnotationConfigEmbeddedWebApplicationContext - Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2df32bf7: startup date [Mon Jun 08 11:34:08 CEST 2015]; root of context hierarchy
11:34:08.916 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapper': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapper; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapper; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.917 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperLongCategorie': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperLongCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperLongCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.918 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperShortProduit': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperShortProduit; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperShortProduit; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.919 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperShortCategorie': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperShortCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperShortCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.919 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperLongProduit': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperLongProduit; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperLongProduit; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:09.409 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat initialized with port(s): 8081 (http)
11:34:09.641 [main] INFO  o.a.catalina.core.StandardService - Starting service Tomcat
11:34:09.642 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet Engine: Apache Tomcat/8.0.20
11:34:09.778 [localhost-startStop-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
11:34:09.778 [localhost-startStop-1] INFO  o.s.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 1081 ms
11:34:09.839 [localhost-startStop-1] INFO  o.s.b.c.e.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/*]
11:34:10.558 [main] INFO  o.h.validator.internal.util.Version - HV000001: Hibernate Validator 5.1.3.Final
11:34:10.654 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2df32bf7: startup date [Mon Jun 08 11:34:08 CEST 2015]; root of context hierarchy
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/saveCategories],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.webjson.server.entities.CoreCategorie>> spring.webjson.server.service.CategorieController.saveCategories(javax.servlet.http.HttpServletRequest)
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getShortCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllLongCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getAllLongCategories()
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getLongCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getShortCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllShortCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getAllShortCategories()
11:34:10.747 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getLongCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.747 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteAllCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteAllCategories()
11:34:10.748 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/saveProduits],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.webjson.server.entities.CoreProduit>> spring.webjson.server.service.ProduitController.saveProduits(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getShortProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllLongProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getAllLongProduits()
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getShortProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllShortProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getAllShortProduits()
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getLongProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteAllProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteAllProduits()
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getLongProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.809 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8081"]
11:34:10.826 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8081"]
11:34:10.860 [main] INFO  o.a.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read
11:34:11.733 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8081 (http)
11:34:11.934 [main] INFO  spring.webjson.server.boot.Boot - Started Boot in 3.533 seconds (JVM running for 4.137)
11:34:20.382 [http-nio-8081-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring FrameworkServlet 'dispatcherServlet'
11:34:20.384 [http-nio-8081-exec-1] INFO  o.s.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization started
11:34:20.410 [http-nio-8081-exec-1] INFO  o.s.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization completed in 26 ms
11:34:33.103 [http-nio-8081-exec-8] INFO  o.s.b.f.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
11:34:33.168 [http-nio-8081-exec-8] INFO  o.s.j.support.SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase, Hana]
  • wiersze 11–15: wykryto komponenty definiujące filtry jSON. Przejmują one kontrolę nad komponentami o tych samych nazwach wykrytymi w projekcie konfiguracyjnym warstwy JDBC;
  • wiersze 17–18: uruchomienie serwera Tomcat, który będzie obsługiwał serwis internetowy / jSON;
  • wiersze 19–21: inicjowany jest kontekst Springa MVC;
  • wiersze 24–43: wykryto udostępnione URL;

17.3.10. Testy serwisu internetowego / jSON

Aby przeprowadzić testy, używamy klienta [Advanced Rest Client] (patrz paragraf 23.11) do wysyłania zapytań do URL udostępnianych przez serwis internetowy / jSON (usługa internetowa / jSON musi być oczywiście uruchomiona, podobnie jak SGBD). Aby zapełnić bazę danych, uruchamiamy konfigurację wykonawczą o nazwie [spring-jdbc-generic-04-fillDataBase], która wypełnia bazę danymi zawierającymi 5 kategorii i 10 produktów:

 
  • w [1-3] żądamy URL i [/getAllLongCategories] za pomocą polecenia HTTP i GET;

Otrzymujemy następującą odpowiedź:

  • w [1], żądanie klienta HTTP;
  • w [2], odpowiedź serwera HTTP;
  • w [3] status [200 OK] wskazuje, że serwer poprawnie przetworzył żądanie;
  • w [4] – odpowiedź serwera jSON;

Pełna odpowiedź jSON wygląda następująco:


{"status":0,"exception":null,"body":[{"id":1880,"version":1,"nom":"categorie[0]","produits":[{"id":9072,"version":1,"nom":"produit[0,0]","idCategorie":1880,"prix":100.0,"description":"desc[0,0]"},{"id":9073,"version":1,"nom":"produit[0,1]","idCategorie":1880,"prix":101.0,"description":"desc[0,1]"},{"id":9074,"version":1,"nom":"produit[0,2]","idCategorie":1880,"prix":102.0,"description":"desc[0,2]"},{"id":9075,"version":1,"nom":"produit[0,3]","idCategorie":1880,"prix":103.0,"description":"desc[0,3]"},{"id":9076,"version":1,"nom":"produit[0,4]","idCategorie":1880,"prix":104.0,"description":"desc[0,4]"}]},{"id":1881,"version":1,"nom":"categorie[1]","produits":[{"id":9077,"version":1,"nom":"produit[1,0]","idCategorie":1881,"prix":110.00000000000001,"description":"desc[1,0]"},{"id":9078,"version":1,"nom":"produit[1,1]","idCategorie":1881,"prix":111.00000000000001,"description":"desc[1,1]"},{"id":9079,"version":1,"nom":"produit[1,2]","idCategorie":1881,"prix":112.00000000000001,"description":"desc[1,2]"},{"id":9080,"version":1,"nom":"produit[1,3]","idCategorie":1881,"prix":112.99999999999999,"description":"desc[1,3]"},{"id":9081,"version":1,"nom":"produit[1,4]","idCategorie":1881,"prix":114.00000000000001,"description":"desc[1,4]"}]}]}
  • status:0 oznacza, że po stronie serwera nie wystąpiły żadne błędy;
  • wyjątek: null oznacza, że nie ma komunikatu o błędzie;
  • treść: to treść odpowiedzi, w tym przypadku lista kategorii wraz z produktami. Są dwie kategorie, a każda z nich zawiera 5 produktów;

Dodamy do kategorii [categorie1] produkt [produit15]. W tym celu wykorzystamy produkt URL oraz [/saveProduits], które oczekują na ciąg jSON zawierający produkty do zapisania (dodanie / modyfikacja). Ciąg ten będzie wyglądał następująco:

[{"id":null,"version":null,"nom":"produit15","idCategorie":1881,"prix":111.0,"description":"desc15"}]}]

Żądanie do serwisu internetowego / jSON jest formułowane w następujący sposób:

  • w przypadku [1], żądana jest operacja URL;
  • w [2] jest ona żądana poprzez operację POST;
  • w [3] wysłano ciąg jSON;
  • w [4] informuje się serwer, że zostanie mu wysłane jSON;

Odpowiedź serwera jest następująca:

  • w [1] otrzymaliśmy listę [CoreProduit] wraz z ich kluczami głównymi. W tym przypadku otrzymaliśmy listę zawierającą jeden element z kluczem głównym produktu, który właśnie wprowadziliśmy do bazy;

Teraz zapytajmy o rozszerzoną wersję kategorii o nazwie [categorie[1]:

  • w [1], zamiast żądanego URL;
  • z [2] tworzymy POST;
  • w [3,4] wartością przesłaną jest ciąg znaków jSON. Reprezentuje on listę nazw kategorii, dla których chcemy uzyskać wersję rozszerzoną;

Otrzymujemy następujący wynik:

  • w [5] kategoria [categorie[1]] ma teraz szósty produkt;

Usuńmy teraz ten produkt:

  • w [1], żądany URL;
  • z [2] tworzymy POST;
  • z [3-4] wysyła się ciąg jSON zawierający listę kluczy głównych produktów, które chcemy usunąć;

Otrzymany wynik jest następujący:

 
  • [status:0] wskazuje, że usunięcie przebiegło pomyślnie;

Teraz sprawdźmy produkt [produit[1,5]], aby upewnić się, że został on rzeczywiście usunięty:

 

Otrzymujemy następujący wynik:

 
  • [status:0] wskazuje, że operacja przebiegła bez wyjątków;
  • [body:[0]] wskazuje, że [body] jest listą zawierającą 0 elementów. Entyteta [produit[1,5]] została zatem pomyślnie usunięta;

Wszystkie operacje [GET] można wykonać w zwykłej przeglądarce:

Zachęcamy czytelnika do przetestowania pozostałych operacji URL serwisu internetowego / jSON.