Skip to content

17. Eine Datenbank im Web verfügbar machen

17.1. Web/JSON-Service-Architektur

Wir werden die folgende Architektur implementieren:

  • In [1] werden die [DAO-, [JPA]- und JDBC-Schichten] unter Verwendung einer der 24 Konfigurationen implementiert, die in den vorangegangenen Kapiteln, insbesondere in Absatz 15, vorgestellt wurden;
  • die [DAO]-Schicht [3] des Remote-Clients implementiert dieselbe Schnittstelle wie die [DAO]-Schicht [1], was es uns ermöglicht, dieselbe Testschicht wie in den vorangegangenen Kapiteln zu verwenden. Es ist, als wären die Schichten [2–3] für die Schicht [4] transparent;

Wir werden auf die folgenden Projekte zurückgreifen:

  • das Projekt [sgbd-config-jdbc], das die JDBC-Schicht eines der sechs DBMS konfiguriert;
  • das Projekt [sgbd-config-jpa-*], das die JPA-Schicht des ausgewählten DBMS für eine der drei untersuchten JPA-Implementierungen (Hibernate, EclipseLink, OpenJpa) konfiguriert;
  • das generische Projekt [spring-jdbc-04], das die [DAO]-Schicht [1] implementiert;
  • das generische Projekt [spring-jpa-generic], das die [DAO]-Schicht [2] implementiert;
  • das generische Projekt [spring-webjson-server-jdbc-generic], das einen Webservice auf Basis des Projekts [spring-jdbc-04] implementiert;
  • das generische Projekt [spring-webjson-server-jpa-generic], das einen Webdienst auf Basis des Projekts [spring-jpa-generic] implementiert;
  • den generischen Client [spring-webjson-client-generic], der als einziger Client für alle 24 Webservice-Konfigurationen dienen wird;

17.2. Einrichten der Entwicklungsumgebung

Wir werden mit den folgenden Komponenten arbeiten:

  • MySQL 5.6.25-Datenbank;
  • Hibernate-JPA-Implementierung;

Importieren Sie die folgenden Projekte in STS:

  
  • Die [spring-webjson-*]-Projekte befinden sich im Ordner [<examples>\spring-database-generic\spring-webjson];
  • Drücken Sie [Alt-F5] und generieren Sie anschließend alle oben genannten Projekte neu;

Um zu überprüfen, ob die Entwicklungsumgebung korrekt installiert ist, gehen Sie wie folgt vor:

  • Starten Sie den Webdienst mit der Laufzeitkonfiguration [spring-webjson-server-jpa-generic-hibernate], die auf einer JPA/Hibernate-Implementierung basiert;

dann:

  • Starten Sie den Client für diesen Webdienst unter Verwendung der Laufzeitkonfiguration [spring-webjson-client-generic], bei der es sich um einen JUnit-Test handelt:

Der Test sollte erfolgreich sein:

  • Beenden Sie in [1] den Webdienst und starten Sie ihn anschließend mit der Laufzeitkonfiguration [spring-webjson-server-jdbc-generic], die auf einer JDBC-Implementierung basiert:

Starten Sie anschließend den Client für diesen Webdienst mit der Laufzeitkonfiguration [spring-webjson-client-generic]:

Der Test sollte erfolgreich sein:

 

17.3. Webdienst / JSON / JDBC-Implementierung

Wir konzentrieren uns zunächst auf die folgende Architektur:

wobei die [DAO]-Schicht [1] direkt mit der JDBC-Schicht des DBMS kommuniziert.

17.3.1. Das Eclipse-Projekt für den Webservice

Das Eclipse-Projekt für den Webservice / JSON / JDBC sieht wie folgt aus:

  

Es handelt sich um ein Maven-Projekt, dessen [pom.xml]-Datei wie folgt aussieht:


<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>
        <!-- web layer -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- layer [DAO] -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-jdbc-generic-04</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- plugins -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
 
</project>
  • Zeilen 11–15: das übergeordnete Maven-Projekt;
  • Zeilen 24–28: die Abhängigkeit von der [DAO / JDBC]-Schicht, die vom Projekt [spring-jdbc-generic-04] implementiert wird;
  • Zeilen 19–22: die Abhängigkeit vom Artefakt [spring-boot-starter-web]. Dieses Artefakt enthält alle Abhängigkeiten, die zum Erstellen eines Webdienstes / JSON benötigt werden. Es enthält jedoch auch unnötige Bibliotheken. Eine präzisere Konfiguration wäre daher erforderlich, aber diese Konfiguration ist für den Einstieg nützlich.

Die durch diese Konfiguration eingeführten Abhängigkeiten lauten wie folgt:

1
  • In [1] ist zu sehen, dass Eclipse die Abhängigkeit vom Projektarchiv [spring-jdbc-generic-04] erkannt hat;

Die oben genannten Abhängigkeiten betreffen sowohl die [DAO]-Schicht als auch die [web]-Schicht.

17.3.2. Konfiguration der [web]-Schicht

Die [web]-Schicht wird durch zwei Spring-Konfigurationsdateien konfiguriert:

  

17.3.2.1. Die [WebConfig]-Klasse

Die Hauptaufgabe der [WebConfig]-Klasse besteht darin, Folgendes zu konfigurieren:

  • den Tomcat-Server, auf dem der Webdienst bereitgestellt wird;
  • die JSON-Filter für die Serialisierung und Deserialisierung von [Product]- und [Category]-Objekten:

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 {
 
    // -------------------------------- layer configuration [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);
    }
 
    // -------------------------------- configuration filters [json]
    ...
}
  • Zeile 25: Die Klasse ist eine Spring-Konfigurationsklasse;
  • Zeile 26: Die Annotation [@EnableWebMvc] gibt an, dass die Webschicht mit Spring MVC implementiert wird. Dies löst implizite Konfigurationen aus, die wir nicht extra einrichten müssen;
  • Zeilen 30–31: Einbindung des Spring-Kontexts der Anwendung;
  • Zeilen 33–37: Definition der [dispatcherServlet]-Bean, die in Spring-MVC-Anwendungen als [FrontController] fungiert und deren Aufgabe es ist, Client-Anfragen an den Controller weiterzuleiten, der sie bearbeiten kann;
  • Zeilen 39–42: Das Webservice-Servlet wird zusammen mit den von ihm verarbeiteten URLs registriert. Hier haben wir [/*] geschrieben, was alle URLs bedeutet;
  • Zeilen 44–47: Definition der [embeddedServletContainerFactory]-Bean, die den zu verwendenden Webserver angibt. In diesem Fall ist dies der Tomcat-Webserver [http://tomcat.apache.org/]. Sie können auch den Jetty-Server [http://www.eclipse.org/jetty/] verwenden. Beide sind eingebettete Server, die in den Maven-Abhängigkeiten enthalten sind. Wenn [Spring Boot] den Projektstart initiiert, startet es automatisch den in der Konfiguration angegebenen Webserver und stellt den Dienst oder die Webanwendung darauf bereit;

Die JSON-Filter sind wie folgt konfiguriert:


package spring.webjson.server.config;
 
import java.util.List;
...
 
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
 
    // -------------------------------- layer configuration [web]
...
    // -------------------------------- configuration filters [json]
    // mapping 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);
    }
 
    // filters 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;
    }
}
  • Zeile 8: Die Klasse [WebConfig] erweitert die Klasse [WebMvcConfigurerAdapter]. Letztere Klasse konfiguriert die Webanwendung mit Standardwerten. Wenn Sie diese Konfiguration anpassen möchten, müssen Sie bestimmte Methoden dieser Klasse überschreiben. Hier möchten wir die Methode [configureMessageConverters] in den Zeilen [22–26] überschreiben (beachten Sie die Annotation @Override), die eine Liste von „Konvertern“ definiert. Der Webdienst/JSON und sein Client tauschen Textzeilen aus. Ein Konverter ist ein Werkzeug, das in der Lage ist, aus einer empfangenen Textzeile ein Objekt zu erstellen (Deserialisierung) und aus einem Objekt eine Textzeile zu erstellen (Serialisierung). Hier handelt es sich bei den Textzeilen um JSON-Strings. Wir sprechen daher von JSON-Serialisierung/Deserialisierung;
  • Zeile 23: Die Methode [configureMessageConverters] nimmt eine Liste von Konvertern als Parameter entgegen;
  • Zeilen 24–25: Der JSON-Konverter [MappingJackson2HttpMessageConverter] aus den Zeilen [14–20] wird dieser Liste hinzugefügt. Dies ermöglicht den Austausch von JSON-Daten zwischen dem Client und dem Server;
  • Zeilen [14–20]: Definieren einen JSON-Konverter, der von der Klasse [MappingJackson2HttpMessageConverter] implementiert wird. Diese Klasse (Zeile 10) ist in den Maven-Abhängigkeiten des Projekts zu finden;
  • Zeilen [17–18]: Es wird ein JSON-Mapper erstellt und dem [MappingJackson2HttpMessageConverter] zugewiesen;
  • Zeilen [29–32]: definieren den in Zeile 17 erstellten JSON-Mapper als Spring-Bean. Dadurch wird er in den Spring-Kontext eingebunden und steht zur Injektion in andere Beans oder zur Verwendung im Webanwendungscode zur Verfügung;
  • Zeilen 34–41: Definieren einen JSON-Filter für den vorherigen JSON-Mapper;
  • Zeile 35: Die Annotation [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] stellt sicher, dass die hier definierte Bean kein Singleton ist. Jedes Mal, wenn sie aus dem Kontext angefordert wird, wird die Methode [jsonMapperCategoryWithoutProducts] erneut ausgeführt. Dies ist hier notwendig, da wir vier JSON-Filter definieren. Es sollte jedoch zu jedem Zeitpunkt nur einer aktiv sein. Indem wir der Bean den Scope [ConfigurableBeanFactory.SCOPE_PROTOTYPE] zuweisen, stellen wir sicher, dass die Methode erneut ausgeführt wird und der vorherige Filter durch den neuen ersetzt wird;
  • Um diese Filter zu verstehen, denken Sie daran, dass in der [DAO]-Schicht:
    • die Entität [Product] mit der Annotation [jsonFilterProduct] versehen wurde;
    • die Entität [Category] mit [jsonFilterCategory] annotiert wurde;

Wir müssen daher Filter mit diesen Namen definieren.

  • Zeilen [34–41]: Definieren Sie einen Filter namens [jsonMapperShortCategory], der die JSON-Darstellung einer Kategorie ohne deren Produkte bereitstellt;
  • Zeilen [43–51]: Definieren Sie einen Filter namens [jsonMapperLongCategory], der die JSON-Darstellung einer Kategorie zusammen mit ihren Produkten liefert;
  • Zeilen [53–60]: Definieren Sie einen Filter namens [jsonMapperShortProduct], der die JSON-Darstellung eines Produkts ohne dessen Kategorie bereitstellt;
  • Zeilen [62-70]: Definieren Sie einen Filter namens [jsonMapperLongProduct], der die JSON-Darstellung eines Produkts mit seiner Kategorie bereitstellt;

17.3.2.2. Die Klasse [AppConfig]

Die Klasse [AppConfig] konfiguriert die gesamte Anwendung, d. h. die Schichten [web] und [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 {
 
}
  • Zeile 7: Die Klasse ist eine Spring-Konfigurationsklasse;
  • Zeile 9: Wir importieren die Beans aus der [DAO / JDBC]-Schicht sowie diejenigen, die von der [WebConfig]-Klasse definiert wurden. Alle Beans aus der [DAO]-Schicht stehen somit in der Web-/JSON-Anwendung zur Verfügung;
  • Zeile 8: gibt die Pakete an, in denen andere Spring-Beans zu finden sind;

17.3.3. Die [ServerException]

  

Genau wie in den vorherigen Kapiteln, in denen die [DAO]-Schicht eine nicht abgefangene [DaoException] ausgelöst hat, löst die [Web]-Schicht eine nicht abgefangene [ServerException] aus:


package spring.webjson.server.infrastructure;
 
import generic.jdbc.infrastructure.UncheckedException;
 
public class ServerException extends UncheckedException {
 
    private static final long serialVersionUID = 1L;
 
    // manufacturers
    public ServerException() {
        super();
    }
 
    public ServerException(int code, Throwable e, String simpleClassName) {
        super(code, e, simpleClassName);
    }
}
  • Zeile 5: Die Klasse [ServerException] erweitert die Klasse [UncheckedException], die in dem Projekt definiert ist, das die JDBC-Schicht konfiguriert (Zeile 3);

17.3.4. Die Controller

  

Hier werden wir zwei Controller haben:

  • [CategoryController] wird Anfragen im Zusammenhang mit Kategorien bearbeiten;
  • [CategorieController] wird Anfragen im Zusammenhang mit Produkten bearbeiten;

Die von den Controllern bereitgestellten URLs entsprechen eins zu eins den Methoden der Schnittstellen [DaoCategorie] und [DaoProduit] in der [DAO]-Schicht:

Wie oben gezeigt:

  • ruft die Web-Methode [deleteAllCategories] die Methode [deleteAllEntities] der Klasse [DaoCategorie] auf;
  • Die Web-Methode [getShortCategoriesById] ruft die Methode [getShortEntitiesById] der Klasse [DaoCategorie] auf;

Das Gleiche gilt für Produkte:

17.3.4.1. Die vom [CategoryController] bereitgestellten URLs

URL
Methode

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

public Response<List<CoreCategory>>
 saveCategories
(HttpServletRequest request)
====
Die Methode empfängt die zu speichernden Kategorien über eine POST-Anfrage.
Diese sind in der [HttpServletRequest
 Request] zugänglich. Die Kategorien werden durch die
[saveEntities]-Methode der [DaoCategorie]-Schicht. Es werden nur die
 der persistierten Objekte (Kategorien / Produkte) werden
 an den Client zurückgegeben.

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

public Response<Void> deleteAllCategories()
====
Die URL enthält keine Parameter. Die Kategorien werden durch
 die Methode [deleteAllEntities] in der [DaoCategorie]-Schicht.

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

public Response<Void> deleteCategoriesById
(HttpServletRequest request)
====
Die Methode erhält die Primärschlüssel der
 zu löschenden Kategorien. Diese sind im
 [HttpServletRequest request]-Objekt. Die Kategorien werden
 mit der Methode [deleteEntitiesById] der
 [DaoCategorie]-Schicht gelöscht.

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

public Response<Void> deleteCategoriesByName
(HttpServletRequest request)
====
Die Methode empfängt die Namen der zu löschenden Kategorien
. Diese sind im
 [HttpServletRequest request] zugänglich. Die Kategorien werden
 mit der Methode [deleteEntitiesByname] aus der
[DaoCategorie]-Schicht gelöscht.

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

public Response<List<Category>> getAllShortCategories()
====
Die URL enthält keine Parameter. Die Kurz-Kategorien werden
mit der Methode [getAllShortEntities] aus der
 [DaoCategorie] abgerufen.

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

public Response<List<Category>> getAllLongCategories()
====
Die URL enthält keine Parameter. Die langen Kategorien werden
 mit der Methode [getAllLongEntities] aus dem
[DaoCategorie] abgerufen.

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

public Response<List<Category>> getLongCategoriesById(HttpServletRequest request)
====
Die Methode empfängt die Primärschlüssel der
 gewünschten Kategorien über eine POST-Anfrage. Diese sind im
 [HttpServletRequest request] ab. Die Langkategorien werden
 mit der Methode [getLongEntitiesById] der
 [DaoCategorie] abgerufen.

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

public Response<List<Category>> getLongCategoriesByName
(HttpServletRequest request)
====
Die Methode empfängt die Namen der gewünschten Kategorien über eine POST-Anfrage
 . Diese sind in der
 [HttpServletRequest request] ab. Die langen Kategorien werden
mit der Methode [getLongEntitiesByName] der
 [DaoCategorie] abgerufen.

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

public Response<List<Category>> getShortCategoriesByName
(HttpServletRequest request)
====
Die Methode empfängt die Namen der gewünschten Kategorien über eine POST-Anfrage
 . Diese sind im
 [HttpServletRequest request] ab. Die Kurzbezeichnungen der Kategorien werden
 werden von der Methode [getShortEntitiesByName] der
 [DaoCategorie]-Schicht.

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

public Response<List<Category>> getShortCategoriesById
(HttpServletRequest request)
====
Die Methode empfängt die Primärschlüssel der
 gewünschten Kategorien über eine POST-Anfrage. Diese sind im
 [HttpServletRequest request] zugänglich. Die kurzen Kategorienamen sind
 abgerufen mithilfe der Methode [getShortEntitiesById] der
 [DaoCategorie] abgerufen.

17.3.4.2. Die vom [ProductController] bereitgestellten URLs

URL
Methode

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

public Response<List<CoreProduct>> saveProducts
(HttpServletRequest request)
====
Die Methode empfängt die zu speichernden Produkte über eine POST-Anfrage. Diese
sind in der [HttpServletRequest
 Request] zugänglich. Die Produkte werden durch die
 [saveEntities]-Methode in der [DaoProduit]-Schicht. Nur die
 werden an den Client zurückgegeben.

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

public Response<Void> deleteAllProducts()
====
Die URL enthält keine Parameter. Die Produkte werden durch die
 Methode [deleteAllEntities] in der DaoProduit-Schicht ( ) gelöscht.

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

public Response<Void> deleteProductsById
(HttpServletRequest request)
====
Die Methode empfängt die Primärschlüssel der Produkte
 , die gelöscht werden sollen, über eine POST-Anfrage. Diese sind im
 [HttpServletRequest request] zugänglich. Die Produkte werden
 mit der Methode [deleteEntitiesById] der
[DaoProduit] gelöscht.

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

public Response<Void> deleteProductsByName
(HttpServletRequest request)
====
Die Methode empfängt die Namen der zu löschenden Produkte
 . Diese sind im
 [HttpServletRequest request] zugänglich. Die Produkte werden
 mit der Methode [deleteEntitiesByname] aus der
 [DaoProduit]-Schicht gelöscht.

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

public Response<List<Product>> getAllShortProducts()
====
Die URL enthält keine Parameter. Die Kurzbezeichnungen der Kategorien werden
 mit der Methode [getAllShortEntities] aus der
 [DaoProduct]-Schicht abgerufen.

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

public Response<List<Product>> getAllLongProducts()
====
Die URL enthält keine Parameter. Lange Produkte werden
 mit der Methode [getAllLongEntities] aus der
 [DaoProduct]-Schicht abgerufen.

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

public Response<List<Product>> getLongProductsById
(HttpServletRequest request)
====
Die Methode empfängt die Primärschlüssel der gewünschten Produkte über eine POST-Anfrage
 . Diese sind in der
 [HttpServletRequest request] ab. Die Long-Produkte werden
mit der Methode [getLongEntitiesById] des

 [DaoProduit]

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

public Response<List<Product>> getLongProductsByName
(HttpServletRequest request)
====
Die Methode empfängt die Namen der gewünschten Produkte über eine POST-Anfrage.
 Diese sind in der [HttpServletRequest
 Request] ab. Die Langprodukte werden über die
 Methode [getLongEntitiesByName] in der [DaoProduit]-Schicht.

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

public Response<List<Product>> getShortProductsByName
(HttpServletRequest request)
====
Die Methode empfängt die Namen der gewünschten Produkte über eine POST-Anfrage.
 Diese sind in der [HttpServletRequest
 Request] ab. Die Kurzbezeichnungen der Produkte werden über die
[getShortEntitiesByName]-Methode in der [DaoProduit]-Schicht abgerufen.

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

public Response<List<Product>> getShortProductsById
(HttpServletRequest request)
====
Die Methode erhält die Primärschlüssel der gewünschten Produkte über eine POST-Anfrage
 . Diese sind in der
[HttpServletRequest request] ab. Die Kurzprodukte werden
mit der Methode [getShortEntitiesById] des
 [DaoProduit]

17.3.5. Generische Implementierung des Webdienstes

  

Die Liste der vom Webdienst bereitgestellten URLs zeigt, dass wir für die Verwaltung von Kategorien und Produkten dieselben URL-Typen anbieten. Anstatt zwei sehr ähnliche Controller zu schreiben, lassen wir sie von einer Klasse ableiten, die alle Aufgaben übernimmt, die beiden Controllern gemeinsam sind. Dies ist die oben genannte Klasse [AbstractController]. Diese Klasse implementiert die folgende Schnittstelle [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> {
 
    // list of all T entities
    public Response<List<T>> getAllShortEntities();
 
    public Response<List<T>> getAllLongEntities();
 
    // special entities - short version
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
 
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
 
    // special entities - long version
    public Response<List<T>> getLongEntitiesById(HttpServletRequest request);
 
    public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);
 
    // update of several entities
    public Response<List<T>> saveEntities(HttpServletRequest request);
 
    // delete all entities
    public  Response<Void> deleteAllEntities();
 
    // deletion of multiple entities
    public  Response<Void> deleteEntitiesById(HttpServletRequest request);
 
    public  Response<Void> deleteEntitiesByName(HttpServletRequest request);
}

Diese Schnittstelle implementiert die Methoden der [DAO]-Schicht-Schnittstelle, die verwendet werden:


package spring.jdbc.dao;
 
import java.util.List;
 
import spring.jdbc.entities.AbstractCoreEntity;
 
public interface IDao<T extends AbstractCoreEntity> {
 
    // list of all T entities
    public List<T> getAllShortEntities();
 
    public List<T> getAllLongEntities();
 
    // special entities - short version
    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);
 
    // special entities - long version
    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);
 
    // update of several entities
    public List<T> saveEntities(Iterable<T> entities);
 
    public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);
 
    // delete all entities
    public void deleteAllEntities();
 
    // deletion of multiple entities
    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);
}

Der Übergang von der Schnittstelle [IDao<T>] in der [DAO]-Schicht zur Schnittstelle [Iws<T>] im Webdienst erfolgte nach folgenden Regeln:

  • Die Methoden der Schnittstelle [Iws<T>] lösen keine Ausnahmen aus. Tritt eine Ausnahme auf, wird sie im [Response]-Objekt gekapselt;
  • Parametervarianten wie in den Zeilen 45 und 47 [Iterable<String> names, String... names] werden entfernt. Die Methoden beziehen ihre Parameter aus der HTTP-Anfrage des Clients vom Typ [HttpServletRequest request];

Alle Webservice-Antworten werden in das folgende [Response]-Objekt gekapselt:

  

package spring.webjson.server.service;
 
 
public class Response<T> {
 
    // ----------------- properties
    // operation status
    private int status;
    // an error message
    private String exception;
    // the body of the reply
    private T body;
 
    // manufacturers
    public Response() {
 
    }
 
    public Response(int status, String exception, T body) {
        this.status = status;
        this.exception = exception;
        this.body = body;
    }
 
    // getters and setters
...
}
  • Zeile 4: Die Antwort kapselt einen Typ T;
  • Zeile 12: die Antwort vom Typ T;
  • Zeilen 7–10: Eine Methode kann auf eine Ausnahme stoßen. In diesem Fall gibt sie eine Antwort zurück mit:
    • Zeile 8: status!=0;
    • Zeile 10: einer Fehlermeldung;

Die Klasse [AbstractController] sieht wie folgt aus:


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;
 
    // layer DAO
    private IDao<T> dao;
 
    abstract protected IDao<T> getDao();
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @PostConstruct
    public void init(){
        dao=getDao();
    }
    
    @Override
    public Response<List<T>> getAllShortEntities() {
        try {
            // answer
            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 {
            // answer
            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 {
            // we delete
            dao.deleteAllEntities();
            // answer
            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) {
    ...
    }
 
}

Alle Methoden werden auf die gleiche Weise implementiert:

  1. Wenn sie Informationen benötigen, rufen sie diese aus dem Objekt [HttpServletRequest request] ab;
  2. sie rufen die Methode in der [DAO]-Schicht auf, die denselben Namen trägt wie sie selbst;
  3. sie behandeln alle Ausnahmen, die entweder in Schritt 1 (Abrufen von Parametern) oder in Schritt 2 (Aufruf der [DAO]-Schicht) auftreten können;

Betrachten wir zunächst, wie die [DAO]-Schicht in die [AbstractController]-Klasse injiziert wird:


public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
 
    @Autowired
    protected ApplicationContext context;
 
    // layer DAO
    private IDao<T> dao;
 
    abstract protected IDao<T> getDao();
 
    @PostConstruct
    public void init(){
        dao=getDao();
}

  • Zeile 1: Die Klasse ist abstrakt und implementiert die generische Schnittstelle [Iws<T>];
  • Zeilen 3–4: Einbindung des Spring-Kontexts;
  • Zeile 7: die noch unbekannte Referenz auf die zu verwendende [DAO]-Schicht;
  • Zeile 9: Die abstrakte Methode [getDao], die die Referenz auf die zu verwendende [DAO]-Schicht zurückgibt. Diese Methode wird von der Unterklasse überschrieben, sodass die Unterklasse festlegt, welche [DAO]-Schicht verwendet werden soll (DaoProduct oder DaoCategory);
  • Zeile 11: Die Annotation [@PostConstruct] kennzeichnet eine Methode, die ausgeführt wird, sobald die Instanziierung des Objekts abgeschlossen ist. Sobald die Instanziierung abgeschlossen ist, sind die Spring-Injektionen durchgeführt worden. Die Unterklasse hat dann die Referenz auf ihre [DAO]-Schicht erhalten und kann diese daher an ihre Oberklasse weitergeben;

Die Methode [getShortEntitiesById] lautet wie folgt:


    @Override
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
            });
            // answer
            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);
        }
}
  • Zeile 5: Der vom Client gesendete Wert ist eine JSON-Zeichenkette. Hier wird er abgerufen;
  • Zeilen 7–9: Die JSON-Zeichenkette enthält die Liste der Primärschlüssel für die Entitäten, deren Kurzversion gewünscht wird;
  • Zeile 11: Wir rufen die gleichnamige [DAO]-Methode auf. Die Antwort vom Typ [List<T>] wird in einem [Response]-Objekt gekapselt;
  • Zeile 13: Fall, in dem die [DAO]-Schicht eine Ausnahme ausgelöst hat;
  • Zeile 15: Fall für andere Ausnahmen, insbesondere die mögliche Ausnahme während der Deserialisierung des JSON-Parameters, Zeile 8;

Die Methode [getShortEntitiesByName] ist ähnlich:


@Override
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
            });
            // answer
            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);
        }
}
  • Zeilen 4–9: Hier ist der Parameter jSON die Liste der Kategorienamen, für die wir die Kurzform wünschen;

Die Methode [saveEntities] wurde nicht implementiert, da sie stark vom Typ der zu speichernden Entität abhängt, also [Category] oder [Product]. Es gibt kaum Code, der umgestaltet werden müsste. Diese Aufgabe wird daher den Unterklassen überlassen.


        @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. Der [CategoryController]

  

Der [CategorieController]-Controller verarbeitet URLs, die sich auf Kategorien beziehen:


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;
    }
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories() {
        // parent
        Response<List<Categorie>> response = super.getAllShortEntities();
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllLongCategories() {
        // parent
        Response<List<Categorie>> response = super.getAllLongEntities();
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getShortEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getShortEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getLongEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getLongEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        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);
    }
 
}
  • Zeile 26: Die Klasse [CategorieController] erweitert die Klasse [AbstractController];
  • Zeile 25: Die Annotation [@RestController] macht die Klasse zu einer Spring-Komponente. Diese Annotation gibt außerdem an, dass es sich bei der Klasse um einen Webdienst handelt, dessen Methoden ihre Antworten direkt im JSON-Format an den Client senden;
  • Zeilen 28–29: Hier wird die Referenz auf die [DAO]-Schicht injiziert;
  • Zeilen 31–34: Neudefinition der Methode [getDao], die in der übergeordneten Klasse als abstrakt deklariert wurde und deren Zweck darin besteht, eine Referenz auf die zu verwendende [DAO]-Schicht zurückzugeben;

Die Methoden sind alle nach dem gleichen Muster aufgebaut:

  • Delegierung der Verarbeitung an die übergeordnete Klasse;
  • Initialisierung des JSON-Mappers, der die Antwort serialisieren wird;
  • Senden der Antwort;

Werfen wir einen Blick auf die Signaturen einiger URLs:


@RequestMapping(value = "/getAllShortCategories", method
 = RequestMethod.GET)
– die URL [/getAllShortCategories] wird mit einem GET-Request aufgerufen

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- Die URL [/getShortCategoriesById] wird mit einer POST-Anfrage aufgerufen. Der übermittelte Wert ist die JSON-Zeichenkette, die die Primärschlüssel der
gewünschten Kategorien;

@RequestMapping(value = "/getLongCategoriesByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- Die URL [/getLongCategoriesByName] wird mit einer
POST-Anfrage aufgerufen. Der übermittelte Wert ist die JSON-Zeichenkette, die die Namen der
gewünschten Kategorien;

Sehen wir uns nun die Methode [saveCategories] genauer an, die nicht dem Format der anderen Methoden folgt:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // we persist categories
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
            });
            // we persist categories
            categories = daoCategorie.saveEntities(categories);
            // we return the result
            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);
                }
            }
            // result
            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);
        }
}
  • Zeile 1: Die URL [/saveCategories] wird von einem POST-Wert begleitet. Dabei handelt es sich um die JSON-Zeichenkette, die die vollständigen Versionen der zu speichernden Kategorien enthält;
  • Zeilen 5–10: Die zu speichernden Kategorien werden aus der JSON-Zeichenkette neu erstellt. Der Link [product.category], der ein [Product] mit seiner [Category] verbindet, ist null, da in der Langversion einer [Category] jedes [Product] in seiner Kurzversion ohne das Feld [category] vorliegt. Dies ist kein Problem, da die mit JDBC implementierte [DAO]-Schicht diese Informationen nicht benötigt;
  • Zeile 12: Die Kategorien werden gespeichert. Die empfangene Liste der Kategorien wurde um die Primärschlüssel der gespeicherten Elemente, Kategorien und Produkte, erweitert. Sonst hat sich nichts geändert. Anstatt die gesamte empfangene Liste zurückzugeben, was aufwendig ist, geben wir nur die Primärschlüssel der Elemente in dieser Liste zurück. Dazu verwenden wir die folgenden Klassen [CoreCategory] und [CoreProduct]:
  

package spring.webjson.server.entities;
 
import java.util.List;
 
public class CoreCategorie {
 
    // primary key
    private Long id;
    
    // manufacturers
    public CoreCategorie() {
 
    }
 
    public CoreCategorie(Long id) {
        this.id=id;
    }
 
    // list of products
    private List<CoreProduit> coreProduits;
 
    // getters and setters
    ...
}
  • Zeile 8: der Primärschlüssel eines Produkts;
  • Zeile 20: die Primärschlüssel seiner Produkte;

package spring.webjson.server.entities;
 
public class CoreProduit {
 
    // primary key
    private Long id;
 
    // manufacturers
    public CoreProduit() {
 
    }

    public CoreProduit(Long id) {
        this.id = id;
    }
 
    // getters and setters
...
}
  • Zeile 6: der Primärschlüssel eines Produkts;

Kehren wir zum Code für die Methode [saveCategories] zurück:


...            
// on persiste les catégories
            categories = daoCategorie.saveEntities(categories);
            // on rend le résultat
            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);
                }
            }
            // résultat
            return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
  • Zeilen 5–17: Wir erstellen die Liste der [CoreCategory]-Objekte, die wir an den Remote-Client zurückgeben werden;
  • Zeile 19: Die Antwort wird zurückgegeben und in JSON serialisiert;

17.3.7. Umgang mit JSON-Filtern

Für jede Methode eines Controllers gibt es zwei Punkte für die JSON-Serialisierung/Deserialisierung:

  • Deserialisierung des übermittelten Werts: Dies wird hier explizit behandelt;
  • Serialisierung des Ergebnisses: Dies wird hier implizit behandelt;

Beginnen wir mit der Deserialisierung des übermittelten Werts in [CategorieController.saveCategories]:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // we persist categories
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
  • Zeile 8: Wir rufen einen Mapper, der für die Verarbeitung des JSON-Filters [jsonMapperLongCategorie] konfiguriert ist, aus dem Spring-Kontext ab. Kehren wir zur Definition dieses Mappers in der Konfigurationsklasse [WebConfig] zurück:

// -------------------------------- configuration filters [json]
    // mapping 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);
    }
 
    // filters 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;
    }
 
  • Zeilen 32–40: der von der Klasse [CategorieController] abgerufene JSON-Mapper [jsonMapperLongCategorie];
  • Zeile 35: Dieser Mapper wird von der Methode [jsonMapper] in den Zeilen 18–21 zurückgegeben;
  • Zeilen 18–21: Die Methode [jsonMapper] gibt den JSON-Mapper aus dem [MappingJackson2HttpMessageConverter] in den Zeilen 3–9 zurück;

Mit anderen Worten: der JSON-Mapper, der durch Zeile 4 unten in [CategorieController.saveCategories] abgerufen wird:


            // on récupère la valeur postée
            String body = CharStreams.toString(request.getReader());
            // on la désérialise
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});

ist der von Spring MVC verwendete Standardkonverter, um den vom Client gesendeten Wert zu deserialisieren und das an ihn zurückgesendete Ergebnis zu serialisieren. In den obigen Zeilen fand keine implizite Deserialisierung des gesendeten Werts statt. Um dies zu erreichen, hättest du Folgendes schreiben müssen:


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

In diesem Fall hätte eine automatische Deserialisierung des im Parameter [categories] übermittelten Werts stattgefunden. Es gab jedoch das Problem mit dem Filter [jsonFilterCategorie], über den die Entitäten [Categorie] verfügen. Dieser muss konfiguriert werden. Aus diesem Grund haben wir uns für eine explizite Deserialisierung entschieden (Zeilen 4–5). Der zweite zu beachtende Punkt ist, dass der Mapper in Zeile 4 (der standardmäßig von Spring MVC verwendet wird) auch für die Serialisierung des Ergebnisses [Response<List<CoreCategory>] geeignet ist. Tatsächlich verfügt die Entität [CoreCategorie] über keinen JSON-Filter. Daher ist es nicht erforderlich, den resultierenden JSON-Mapper mit einem zusätzlichen Filter zu konfigurieren. In diesem Fall wird die an den Client gesendete Antwort implizit serialisiert.

17.3.8. Der [ProductController]

  

Der Controller [ProduitController] verarbeitet URLs, die sich auf Produkte beziehen. Sein Code ähnelt dem des Controllers [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;
    }
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllShortProduits() {
        // parent
        Response<List<Produit>> response = super.getAllShortEntities();
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllLongProduits() {
        // parent
        Response<List<Produit>> response = super.getAllLongEntities();
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getShortEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getShortEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getLongEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getLongEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        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);
    }
 
}

Nur die Methode [saveProducts] weist eine andere Struktur auf als die anderen Methoden:


@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
            List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
            });
            // we persist products
            produits = daoProduit.saveEntities(produits);
            List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
            for (Produit produit : produits) {
                coreProduits.add(new CoreProduit(produit.getId()));
            }
            // we return the answer
            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);
        }
}
  • Zeilen 4–9: Aus der empfangenen JSON-Zeichenkette rekonstruieren wir die Liste der [Product]-Objekte, die persistent gespeichert werden sollen. Da die empfangene JSON-Zeichenkette die Kurzversionen der Produkte enthält, ist ihr Feld [category] null. Auch hier benötigt die DAO/JDBC-Schicht diese Information nicht;
  • Zeile 11: Die Produkte werden gespeichert;
  • Zeilen 12–15: Die Liste der zurückzugebenden [CoreProduct]-Objekte wird erstellt;
  • Zeile 18: Die Antwort wird zurückgegeben, die vom Mapper aus Zeile 7 serialisiert wird (implizite Serialisierung durch Spring MVC), bevor sie an den Remote-Client gesendet wird (siehe Erläuterung in Abschnitt 17.3.7);

17.3.9. Der Webdienst / die JSON-Ausführungsklasse

  

Die Klasse [Boot] ist die ausführbare Klasse des Projekts:


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);
    }
}
  • Zeile 10: Die statische Methode [SpringApplication.run] wird ausgeführt. Die Klasse [SpringApplication] ist eine Klasse aus dem [Spring Boot]-Projekt (Zeile 3). Ihr werden zwei Parameter übergeben:
    • [AppConfig.class]: die Klasse, die die gesamte Anwendung konfiguriert;
    • [args]: alle Argumente, die in Zeile 9 an die Methode [main] übergeben wurden. Dieser Parameter wird hier nicht verwendet;

Wenn diese Klasse ausgeführt wird, werden die folgenden Protokolleinträge generiert:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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]
  • Zeilen 11–15: Beans, die JSON-Filter definieren, werden erkannt. Sie überschreiben Beans mit denselben Namen, die im Konfigurationsprojekt der JDBC-Schicht gefunden wurden;
  • Zeilen 17–18: Der Tomcat-Server wird gestartet, um den Web-/JSON-Dienst auszuführen;
  • Zeilen 19–21: Der Spring-MVC-Kontext wird initialisiert;
  • Zeilen 24–43: Die exponierten URLs werden erkannt;

17.3.10. Testen des /jSON-Webdienstes

Um die Tests durchzuführen, verwenden wir den [Advanced Rest Client] (siehe Abschnitt 23.11), um die vom /jSON-Webdienst bereitgestellten URLs abzufragen (der /jSON-Webdienst muss natürlich ebenso wie das DBMS laufen). Um die Datenbank zu füllen, führen wir die Ausführungskonfiguration namens [spring-jdbc-generic-04-fillDataBase] aus, die die Datenbank mit 5 Kategorien und 10 Produkten füllt:

 
  • In [1-3] rufen wir die URL [/getAllLongCategories] über eine HTTP-GET-Anfrage ab;

Wir erhalten die folgende Antwort:

  • in [1] die HTTP-Anfrage des Clients;
  • in [2] die HTTP-Antwort des Servers;
  • in [3] zeigt der Status [200 OK] an, dass der Server die Anfrage erfolgreich verarbeitet hat;
  • in [4] die JSON-Antwort des Servers;

Die vollständige JSON-Antwort lautet wie folgt:


{"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 bedeutet, dass keine serverseitigen Fehler aufgetreten sind;
  • exception: null bedeutet, dass keine Fehlermeldung vorliegt;
  • body: ist der Hauptteil der Antwort, in diesem Fall die Liste der Kategorien mit ihren Produkten. Es gibt zwei Kategorien mit jeweils 5 Produkten;

Wir fügen das Produkt [product15] zur Kategorie [category1] hinzu. Dazu verwenden wir die URL [/saveProducts], die eine JSON-Zeichenkette der zu speichernden Produkte erwartet (Einfügen/Aktualisieren). Diese Zeichenkette sieht wie folgt aus:

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

Die Anfrage an den Webservice / JSON erfolgt wie folgt:

  • in [1] die angeforderte URL;
  • in [2] wird sie über einen POST-Vorgang angefordert;
  • in [3] die gesendete JSON-Zeichenkette;
  • in [4] wird dem Server mitgeteilt, dass JSON-Daten gesendet werden;

Die Antwort des Servers lautet wie folgt:

  • In [1] haben wir eine Liste von [CoreProduct]-Objekten mit ihren Primärschlüsseln erhalten. Hier haben wir eine Liste erhalten, die ein einzelnes Element mit dem Primärschlüssel des Produkts enthält, das wir gerade in die Datenbank eingefügt haben;

Nun fordern wir die vollständige Version der Kategorie mit dem Namen [category[1]] an:

  • In [1] die angeforderte URL;
  • in [2] senden wir eine POST-Anfrage;
  • in [3,4] ist der übermittelte Wert eine JSON-Zeichenkette. Diese stellt die Liste der Kategorienamen dar, für die wir die Langform wünschen;

Wir erhalten das folgende Ergebnis:

  • In [5] hat die Kategorie [category[1]] nun ein sechstes Produkt;

Löschen wir nun dieses Produkt:

  • In [1] die angeforderte URL;
  • In [2] senden wir eine POST-Anfrage;
  • in [3-4] senden wir eine JSON-Zeichenkette, die die Liste der Primärschlüssel für die Produkte enthält, die wir löschen möchten;

Das Ergebnis lautet wie folgt:

 
  • [status:0] zeigt an, dass die Löschung erfolgreich war;

Nun rufen wir das Produkt [product[1,5]] ab, um zu überprüfen, ob es tatsächlich gelöscht wurde:

 

Wir erhalten folgendes Ergebnis:

 
  • [status:0] zeigt an, dass der Vorgang ohne Ausnahmen abgeschlossen wurde;
  • [body:[0]] bedeutet, dass [body] eine leere Liste ist. Die Entität [product[1,5]] wurde somit erfolgreich gelöscht;

Alle [GET]-Operationen können in einem Standard-Webbrowser ausgeführt werden:

Leser sind eingeladen, die anderen URLs des Webdienstes / JSON zu testen.