17. Een database op het web beschikbaar maken
17.1. Architectuur van de webservice / jSON
We gaan de volgende architectuur opzetten:
![]() |
- in [1] worden de lagen [DAO, [JPA], JDBC] worden geïmplementeerd door een van de 24 configuraties die in de voorgaande hoofdstukken en met name in paragraaf 15 zijn beschreven;
- de laag [DAO] [3] van de externe client implementeert dezelfde interface als de laag [DAO] [1], waardoor we dezelfde testlaag kunnen gebruiken als in de voorgaande hoofdstukken. Het is alsof de lagen [2-3] transparant zijn voor de laag [4];
We zullen gebruikmaken van de volgende projecten:
- het project [sgbd-config-jdbc], dat de laag JDBC van een van de zes SGBD-lagen configureert;
- het project [sgbd-config-jpa-*], dat de laag JPA configureert van de SGBD die is gekozen voor een van de drie onderzochte implementaties JPA (Hibernate, EclipseLink, OpenJpa);
- het generieke project [spring-jdbc-04] dat de laag [DAO] [1] implementeert;
- het generieke project [spring-jpa-generic] dat de laag [DAO] en [2] implementeert;
- het generieke project [spring-webjson-server-jdbc-generic] dat een webservice implementeert op basis van het project [spring-jdbc-04];
- het generieke project [spring-webjson-server-jpa-generic], dat een webservice implementeert op basis van het project [spring-jpa-generic];
- de generieke client [spring-webjson-client-generic], die de enige client zal zijn voor de 24 configuraties van de webservice;
17.2. Het opzetten van de werkomgeving
We gaan met de volgende elementen werken:
- SGBD MySQL 5.6.25;
- implementatie JPA Hibernate;
Importeer de volgende projecten in STS:
![]() |
- de projecten [spring-webjson-*] zijn te vinden in de map [<exemples>\spring-database-generic\spring-webjson];
- voer [Alt-F5] uit en genereer vervolgens alle bovengenoemde projecten opnieuw;
Ga als volgt te werk om te controleren of de werkomgeving correct is geïnstalleerd:
- start de webservice met de uitvoeringsconfiguratie [spring-webjson-server-jpa-generic-hibernate], die is gebaseerd op een implementatie van JPA / Hibernate;
![]() | ![]() |
en vervolgens:
- start de client van deze webservice met de uitvoeringsconfiguratie [spring-webjson-client-generic], die een test is van JUnit:
![]() | ![]() |
De test moet slagen:
![]() |
- in [1]: stop de webservice en start de webservice vervolgens opnieuw met de uitvoeringsconfiguratie [spring-webjson-server-jdbc-generic], die is gebaseerd op een implementatie JDBC:
![]() | ![]() |
start vervolgens de client van deze webservice met de uitvoeringsconfiguratie [spring-webjson-client-generic]:
![]() | ![]() |
De test moet slagen:
![]() |
17.3. Implementatie van de webservice / jSON / JDBC
We kijken eerst naar de volgende architectuur:
![]() |
waarbij de laag [DAO] [1] rechtstreeks communiceert met de laag JDBC van de SGBD.
17.3.1. Het Eclipse-project van de webservice
Het Eclipse-project van de webservice / jSON / JDBC is als volgt:
![]() |
Dit is een Maven-project waarvan het bestand [pom.xml] als volgt is:
<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>
<!-- weblaag -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- laag [DAO] -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-jdbc-generic-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- plug-ins -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- regels 11-15: het bovenliggende Maven-project;
- regels 24-28: de afhankelijkheid van de laag [DAO / JDBC], geïmplementeerd door het project [spring-jdbc-generic-04];
- regels 19-22: de afhankelijkheid van het artefact [spring-boot-starter-web]. Dit artefact bevat alle afhankelijkheden die nodig zijn voor het aanmaken van een webservice / jSON. Het bevat echter ook overbodige bibliotheken. Een nauwkeurigere configuratie zou dus nodig zijn, maar deze configuratie is handig om mee te beginnen.
De afhankelijkheden die deze configuratie met zich meebrengt, zijn de volgende:
![]() | ![]() | 1 ![]() |
- in [1] zien we dat Eclipse de afhankelijkheid van het projectarchief [spring-jdbc-generic-04] heeft gedetecteerd;
De bovenstaande afhankelijkheden gelden zowel voor de laag [DAO] als voor de laag [web].
17.3.2. Configuratie van de laag [web]
De laag [web] wordt geconfigureerd door twee Spring-configuratiebestanden:
![]() |
17.3.2.1. De klasse [WebConfig]
De klasse [WebConfig] heeft als belangrijkste taak het configureren van:
- de Tomcat-server waarop de webservice zal worden geïmplementeerd;
- de jSON-filters voor het serialiseren en deserialiseren van de objecten [Produit] en [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 {
// -------------------------------- configuratie laag [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);
}
// -------------------------------- configuratie filters [json]
...
}
- regel 25: de klasse is een Spring-configuratieklasse;
- regel 26: de annotatie [@EnableWebMvc] geeft aan dat de weblaag is geïmplementeerd met Spring MVC. Dit zorgt voor impliciete configuraties die we niet zelf hoeven uit te voeren;
- regels 30-31: injectie van de Spring-context van de applicatie;
- regels 33-37: definitie van de bean [dispatcherServlet] die, in Spring-toepassingen MVC, de rol vervult van [FrontController], wiens taak het is om verzoeken van clients door te sturen naar de controller die deze kan verwerken;
- regels 39-42: de servlet van de webservice wordt geregistreerd, evenals de URL die deze verwerkt. Hier is [/*] geschreven, wat staat voor alle URLs;
- regels 44-47: definitie van de bean [embeddedServletContainerFactory], die de te gebruiken webserver definieert. Hier is dat de Tomcat-webserver [http://tomcat.apache.org/]. Men kan ook de Jetty-server [http://www.eclipse.org/jetty/] gebruiken. Dit zijn beide servers die in de Maven-afhankelijkheden zijn opgenomen. Wanneer [Spring Boot] het opstarten van het project aanstuurt, start het automatisch de in de configuratie aangewezen webserver op en implementeert daarop de webservice of webapplicatie;
De configuratie van de jSON-filters verloopt als volgt:
package spring.webjson.server.config;
import java.util.List;
...
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- configuratie laag [web]
...
// -------------------------------- filterconfiguratie [json]
// toewijzing 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;
}
}
- regel 8: de klasse [WebConfig] is een uitbreiding van de klasse [WebMvcConfigurerAdapter]. Deze laatste klasse configureert de webapplicatie met standaardwaarden. Als je deze configuratie wilt aanpassen, moet je bepaalde methoden van deze klasse herdefiniëren. Hier willen we de methode [configureMessageConverters] van de klasse [22-26] herdefiniëren (let op de annotatie @Override), die een lijst met ‘converters’ definieert. De webservice /jSON en zijn client wisselen tekstregels uit. Een converter is een hulpmiddel dat in staat is om een object te maken op basis van een ontvangen tekstregel (deserialisatie) en om een tekstregel te maken op basis van een object (serialisatie). In dit geval zijn de tekstregels jSON-strings. We spreken dus van jSON-serialisatie/deserialisatie;
- regel 23: de methode [configureMessageConverters] ontvangt als parameter een lijst met converters;
- regels 24-25: de converter jSON [MappingJackson2HttpMessageConverter] van de regels [14-20] wordt aan deze lijst toegevoegd. Dit maakt de uitwisseling van jSON tussen de client en de server mogelijk;
- regels [14-20]: definiëren een converter jSON die wordt geïmplementeerd door de klasse [MappingJackson2HttpMessageConverter]. Deze klasse (regel 10) is te vinden in de Maven-afhankelijkheden van het project;
- regels [17-18]: er wordt een mapper jSON aangemaakt en toegewezen aan de converter [MappingJackson2HttpMessageConverter];
- regels [29-32]: definiëren de mapper jSON, die op regel 17 is aangemaakt, als een Spring-bean. Hierdoor wordt deze in de Spring-context geplaatst en kan deze worden geïnjecteerd in andere beans of worden gebruikt in de code van de webapplicatie;
- regels 34-41: definiëren een filter jSON voor de voorgaande mapper jSON;
- regel 35: de annotatie [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] zorgt ervoor dat de hier gedefinieerde bean geen singleton is. Telkens wanneer deze aan de context wordt opgevraagd, wordt de methode [jsonMapperCategorieWithoutProduits] opnieuw uitgevoerd. Dit is hier nodig omdat we vier jSON-filters definiëren. Er mag echter op een bepaald moment slechts één actief zijn. Door de bean het bereik [ConfigurableBeanFactory.SCOPE_PROTOTYPE] te geven, zorgen we ervoor dat de methode opnieuw wordt uitgevoerd en dat het vorige filter door het nieuwe wordt vervangen;
- om deze filters te begrijpen, moet men bedenken dat in de laag [DAO]:
- de entiteit [Produit] is voorzien van de annotatie [jsonFilterProduit];
- de entiteit [Categorie] is voorzien van de annotatie [jsonFilterCategorie];
Er moeten dus filters met deze namen worden gedefinieerd.
- regels [34-41]: definiëren een filter met de naam [jsonMapperShortCategorie] waarmee de weergave jSON van een categorie zonder de bijbehorende producten kan worden verkregen;
- regels [43-51]: definiëren een filter met de naam [jsonMapperLongCategorie] waarmee de weergave jSON van een categorie met de bijbehorende producten kan worden verkregen;
- regels [53-60]: definiëren een filter met de naam [jsonMapperShortProduit] waarmee de weergave jSON van een product zonder de bijbehorende categorie kan worden verkregen;
- regels [62-70]: definiëren een filter met de naam [jsonMapperLongProduit] waarmee de weergave jSON van een product met zijn categorie kan worden verkregen;
17.3.2.2. De klasse [AppConfig]
De klasse [AppConfig] configureert de gehele applicatie, d.w.z. de lagen [web] en [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 {
}
- regel 7: de klasse is een Spring-configuratieklasse;
- regel 9: we importeren de beans van de laag [DAO / JDBC] en die welke zijn gedefinieerd door de klasse [WebConfig]. Alle beans van de laag [DAO] zullen dus beschikbaar zijn in de webapplicatie / jSON;
- regel 8: geeft aan in welke pakketten andere Spring-beans te vinden zijn;
17.3.3. De uitzondering [ServerException]
![]() |
Net als in de voorgaande hoofdstukken, waarbij de laag [DAO] een ongecontroleerde uitzondering [DaoException] genereerde, zal de laag [web] een ongecontroleerde uitzondering [ServerException] genereren:
package spring.webjson.server.infrastructure;
import generic.jdbc.infrastructure.UncheckedException;
public class ServerException extends UncheckedException {
private static final long serialVersionUID = 1L;
// fabrikanten
public ServerException() {
super();
}
public ServerException(int code, Throwable e, String simpleClassName) {
super(code, e, simpleClassName);
}
}
- regel 5: de klasse [ServerException] is een uitbreiding van de klasse [UncheckedException] die is gedefinieerd in het project dat de laag JDBC configureert (regel 3);
17.3.4. De controllers
![]() |
![]() |
We hebben hier twee controllers:
- [CategorieController] regelt de verzoeken over de categorieën;
- [CategorieController] regelt de verzoeken met betrekking tot producten;
De door de controllers blootgestelde URL komen één op één overeen met de methoden van de interfaces [DaoCategorie] en [DaoProduit] van de laag [DAO]:
![]() | ![]() |
Zoals hierboven:
- de webmethode [deleteAllCategories] roept de methode [deleteAllEntities] van de klasse [DaoCategorie] aan;
- de webmethode [getShortCategoriesById] roept de methode [getShortEntitiesById] van de klasse [DaoCategorie] aan;
Hetzelfde geldt voor de producten:
![]() | ![]() |
17.3.4.1. De URL die worden weergegeven door de controller [CategorieController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.4.2. De URL weergegeven door de controller [ProduitController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.5. Generieke implementatie van de webservice
![]() |
Uit de lijst met URL die door de webservice wordt aangeboden, blijkt dat er dezelfde soorten URL worden aangeboden voor het beheer van categorieën en producten. In plaats van twee zeer vergelijkbare controllers te schrijven, laten we ze afleiden van een klasse die al het werk doet dat beide controllers gemeen hebben. Dit wordt de hierboven genoemde klasse [AbstractController]. Deze klasse implementeert de volgende interface [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> {
// lijst van alle T-entiteiten
public Response<List<T>> getAllShortEntities();
public Response<List<T>> getAllLongEntities();
// van specifieke entiteiten – korte versie
public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
// van specifieke entiteiten – lange versie
public Response<List<T>> getLongEntitiesById(HttpServletRequest request);
public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);
// update van meerdere entiteiten
public Response<List<T>> saveEntities(HttpServletRequest request);
// verwijdering van alle entiteiten
public Response<Void> deleteAllEntities();
// verwijdering van meerdere entiteiten
public Response<Void> deleteEntitiesById(HttpServletRequest request);
public Response<Void> deleteEntitiesByName(HttpServletRequest request);
}
Deze interface bevat de methoden van de interface van de laag [DAO] die zal worden gebruikt:
package spring.jdbc.dao;
import java.util.List;
import spring.jdbc.entities.AbstractCoreEntity;
public interface IDao<T extends AbstractCoreEntity> {
// lijst van alle T-entiteiten
public List<T> getAllShortEntities();
public List<T> getAllLongEntities();
// van specifieke entiteiten – korte versie
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);
// van specifieke entiteiten – lange versie
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);
// meerdere entiteiten bijwerken
public List<T> saveEntities(Iterable<T> entities);
public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);
// verwijdering van alle entiteiten
public void deleteAllEntities();
// verwijdering van meerdere entiteiten
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);
}
De overgang van de interface [IDao<T>] van de laag [DAO] naar de interface [Iws<T>] van de webservice heeft aan de volgende regels voldaan:
- de methoden van de interface [Iws<T>] zullen geen uitzondering genereren. Mocht er toch een uitzondering optreden, dan wordt deze ingekapseld in het object [Response];
- de varianten van de parameters, zoals in regels 45 en 47 van [Iterable<String> names, String... names], verdwijnen. De methoden halen hun parameters uit het verzoek HTTP van de client van het type [HttpServletRequest request];
Alle antwoorden van de webservice worden ingekapseld in het volgende object [Response]:
![]() |
package spring.webjson.server.service;
public class Response<T> {
// ----------------- eigenschappen
// status van de bewerking
private int status;
// een foutmelding
private String exception;
// de inhoud van het antwoord
private T body;
// constructors
public Response() {
}
public Response(int status, String exception, T body) {
this.status = status;
this.exception = exception;
this.body = body;
}
// getters en setters
...
}
- regel 4: het antwoord bevat een type T;
- regel 12: het antwoord van het type T;
- regels 7-10: het is mogelijk dat een methode een uitzondering tegenkomt. In dat geval zal deze een antwoord retourneren met:
- regel 8: status!=0;
- regel 10: een foutmelding;
De klasse [AbstractController] is als volgt:
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;
// laag DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
// lokaal
private String simpleClassName = getClass().getSimpleName();
@PostConstruct
public void init(){
dao=getDao();
}
@Override
public Response<List<T>> getAllShortEntities() {
try {
// antwoord
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 {
// antwoord
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 {
// verwijderen
dao.deleteAllEntities();
// antwoord
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) {
...
}
}
De methoden zijn allemaal op dezelfde manier geïmplementeerd:
- als ze informatie verwachten, halen ze die op uit het object [HttpServletRequest request];
- ze roepen de methode aan van de laag [DAO] die dezelfde naam draagt als zijzelf;
- ze verwerken de uitzonderingen die kunnen optreden, hetzij in bewerking 1 (het ophalen van de parameters), hetzij in bewerking 2 (het aanroepen van de laag [DAO]);
Laten we eerst eens bekijken hoe de laag [DAO] in de klasse [AbstractController] wordt ingevoegd:
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
@Autowired
protected ApplicationContext context;
// laag DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
@PostConstruct
public void init(){
dao=getDao();
}
- regel 1: de klasse is abstract en implementeert de generieke interface [Iws<T>];
- regels 3-4: injectie van de Spring-context;
- regel 7: de nog onbekende referentie van de te gebruiken laag [DAO];
- regel 9: de abstracte methode [getDao] die de te gebruiken referentie van de laag [DAO] zal retourneren. Deze methode wordt door de afgeleide klasse geherdefinieerd en het is dus de afgeleide klasse die aangeeft welke laag [DAO] moet worden gebruikt (DaoProduit of DaoCategorie);
- regel 11: de annotatie [@PostConstruct] markeert een methode die moet worden uitgevoerd wanneer het instantiëren van het object is voltooid. Wanneer dit instantiëren is voltooid, zijn de Spring-injecties uitgevoerd. De dochterklasse heeft dan de referentie van haar laag [DAO] verkregen en kan deze dus doorgeven aan haar bovenliggende klasse;
De methode [getShortEntitiesById] is als volgt:
@Override
public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
try {
// de verzonden waarde wordt opgehaald
String body = CharStreams.toString(request.getReader());
// deze wordt gedeserialiseerd
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
});
// antwoord
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);
}
}
- regel 5: de door de client verzonden waarde is een tekenreeks jSON. Hier wordt deze opgehaald;
- regels 7-9: de tekenreeks jSON bevat de lijst met primaire sleutels van de entiteiten waarvan de verkorte versie gewenst is;
- regel 11: de gelijknamige methode [DAO] wordt aangeroepen. Het antwoord van het type [List<T>] wordt ingekapseld in een object [Response];
- regel 13: het geval waarin de laag [DAO] een uitzondering heeft gegenereerd;
- regel 15: het geval van andere uitzonderingen, in het bijzonder de mogelijke uitzondering bij het deserialiseren van de parameter jSON, regel 8;
De methode [getShortEntitiesByName] is vergelijkbaar:
@Override
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
try {
// de verzonden waarde wordt opgehaald
String body = CharStreams.toString(request.getReader());
// de waarde wordt gedeserialiseerd
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
});
// antwoord
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);
}
}
- regels 4-9: hier is de parameter jSON de lijst met namen van de categorieën waarvan de verkorte versie gewenst is;
De methode [saveEntities] is niet geïmplementeerd omdat deze sterk afhankelijk is van de aard van de te bewaren entiteit, [Categorie] of [Produit]. Er is weinig code om te factoriseren. Dit werk wordt daarom overgelaten aan de afgeleide klassen.
@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. De controller [CategorieController]
![]() |
De controller [CategorieController] beheert de verwerking van URL met betrekking tot de categorieën:
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;
}
// lokaal
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllShortCategories() {
// bovenliggend
Response<List<Categorie>> response = super.getAllShortEntities();
// serialisatiefilters jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllLongCategories() {
// bovenliggend
Response<List<Categorie>> response = super.getAllLongEntities();
// serialisatiefilters jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
// bovenliggend
Response<List<Categorie>> response = super.getShortEntitiesById(request);
// serialisatiefilters jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
// bovenliggend
Response<List<Categorie>> response = super.getShortEntitiesByName(request);
// serialisatiefilters jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
// bovenliggend
Response<List<Categorie>> response = super.getLongEntitiesById(request);
// serialisatiefilters jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
// bovenliggend
Response<List<Categorie>> response = super.getLongEntitiesByName(request);
// serialisatiefilters jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// antwoord
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);
}
}
- regel 26: de klasse [CategorieController] is een uitbreiding van de klasse [AbstractController];
- regel 25: de annotatie [@RestController] maakt van de klasse een Spring-component. Deze annotatie geeft bovendien aan dat de klasse een webservice is waarvan de methoden hun antwoord rechtstreeks naar de client sturen in het formaat jSON;
- regels 28-29: de referentie van de laag [DAO] wordt hier geïnjecteerd;
- regels 31-34: herdefinitie van de methode [getDao], die in de bovenliggende klasse als abstract is gedeclareerd en tot doel heeft een verwijzing naar de te gebruiken laag [DAO] terug te geven;
De methoden zijn allemaal volgens hetzelfde patroon opgebouwd:
- delegatie van de verwerking aan de bovenliggende klasse;
- initialisatie van de mapper jSON die de serialisatie van het antwoord zal uitvoeren;
- verzending van het antwoord;
Laten we eens kijken naar de signatuur van enkele URL:
| - de URL [/getAllShortCategories] wordt aangeroepen met een GET |
| - de URL [/getShortCategoriesById] wordt aangeroepen met een POST. De geposte waarde is de tekenreeks jSON van de primaire sleutels van de gewenste categorieën; |
| - URL [/getLongCategoriesByName] wordt aangeroepen met een POST. De verzonden waarde is de tekenreeks jSON met de namen van de gewenste categorieën; |
Laten we nu de methode [saveCategories] nader bekijken, die niet het formaat van de andere methoden volgt:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// de categorieën worden opgeslagen
try {
// de verzonden waarde wordt opgehaald
String body = CharStreams.toString(request.getReader());
// de waarde wordt gedeserialiseerd
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
// de categorieën worden opgeslagen
categories = daoCategorie.saveEntities(categories);
// het resultaat wordt weergegeven
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);
}
}
// resultaat
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);
}
}
- regel 1: de URL [/saveCategories] gaat vergezeld van een geposte waarde. Dit is de tekenreeks jSON met de lange versies van de categorieën die moeten worden bewaard;
- regels 5-10: de te behouden categorieën worden opnieuw aangemaakt op basis van de tekenreeks jSON. De koppeling [produit.categorie] die een [Produit] met zijn [Catégorie] verbindt, is gelijk aan null, omdat in de lange versie van een [Categorie], elke [Produit] zich in zijn korte versie bevindt, zonder het veld [categorie]. Dit vormt geen probleem, aangezien de [DAO]-laag, geïmplementeerd met JDBC, deze informatie niet nodig heeft;
- regel 12: de categorieën worden opgeslagen. De ontvangen lijst met categorieën is aangevuld met de primaire sleutels van de opgeslagen elementen, categorieën en producten. Verder is er niets veranderd. In plaats van de volledige ontvangen lijst terug te geven, wat kosten met zich meebrengt, geven we alleen de primaire sleutels van de elementen in deze lijst terug. Hiervoor gebruiken we de volgende klassen [CoreCategorie] en [CoreProduit]:
![]() |
package spring.webjson.server.entities;
import java.util.List;
public class CoreCategorie {
// primaire sleutel
private Long id;
// fabrikanten
public CoreCategorie() {
}
public CoreCategorie(Long id) {
this.id=id;
}
// productlijst
private List<CoreProduit> coreProduits;
// getters en setters
...
}
- regel 8: de primaire sleutel van een product;
- regel 20: de primaire sleutels van zijn producten;
package spring.webjson.server.entities;
public class CoreProduit {
// primaire sleutel
private Long id;
// constructors
public CoreProduit() {
}
public CoreProduit(Long id) {
this.id = id;
}
// getters en setters
...
}
- regel 6: de primaire sleutel van een product;
Laten we teruggaan naar de code van de methode [saveCategories]:
...
// categorieën opslaan
categories = daoCategorie.saveEntities(categories);
// we geven het resultaat weer
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);
}
}
// resultaat
return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
- regels 5-17: we stellen de lijst samen van [CoreCategorie] die we aan de externe klant gaan retourneren;
- regel 19: het antwoord wordt teruggestuurd en geserialiseerd in jSON;
17.3.7. Beheer van filters jSON
Voor elke methode van een controller zijn er twee momenten voor serialisatie/deserialisatie jSON:
- het deserialiseren van de ingevoerde waarde: dit wordt hier expliciet afgehandeld;
- de serialisatie van het resultaat: dit wordt hier impliciet afgehandeld;
Laten we beginnen met de deserialisatie van de in [CategorieController.saveCategories] geplaatste waarde:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// de categorieën opslaan
try {
// de verzonden waarde wordt opgehaald
String body = CharStreams.toString(request.getReader());
// de waarde wordt gedeserialiseerd
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
- regel 8: in de Spring-context wordt een mapper opgehaald die is geconfigureerd om het filter jSON [jsonMapperLongCategorie] te verwerken. Laten we teruggaan naar de definitie van deze mapper in de configuratieklasse [WebConfig]:
// -------------------------------- configuratie filters [json]
// toewijzing 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;
}
- regels 32-40: de mapper jSON [jsonMapperLongCategorie] die door de klasse [CategorieController] wordt opgehaald;
- regel 35: deze mapper wordt weergegeven door de methode [jsonMapper] uit de regels 18-21;
- regels 18-21: de methode [jsonMapper] geeft de mapper jSON weer van de converter [MappingJackson2HttpMessageConverter] uit de regels 3-9;
Met andere woorden, de mapper jSON die door regel 4 hieronder wordt opgehaald in [CategorieController.saveCategories]:
// de verzonden waarde wordt opgehaald
String body = CharStreams.toString(request.getReader());
// deze wordt gedeserialiseerd
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
is de converter die standaard door Spring wordt gebruikt om de door de client verzonden waarde te deserialiseren en het naar de client teruggestuurde resultaat te serialiseren. In de bovenstaande regels heeft er geen impliciete deserialisatie van de verzonden waarde plaatsgevonden. Hiervoor had het volgende moeten worden geschreven:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(@RequestBody List<Categorie> categories) {
In dat geval zou er een automatische deserialisatie hebben plaatsgevonden van de waarde die in de parameter [categories] is gepost. Maar er was het probleem van het filter [jsonFilterCategorie] dat de entiteiten [Categorie] hebben. Dit moet worden geconfigureerd. Daarom hebben we gekozen voor een expliciete deserialisatie (regels 4-5). Het tweede punt dat we moeten bekijken, is dat de mapper van regel 4 (die standaard door Spring wordt gebruikt: MVC) ook geschikt is voor de serialisatie van het resultaat [Response<List<CoreCategorie>]. De entiteit [CoreCategorie] heeft namelijk geen filter jSON. Het is dus niet nodig om de verkregen mapper jSON met een extra filter te configureren. Er vindt dan een impliciete serialisatie plaats van het antwoord dat aan de klant wordt gegeven.
17.3.8. De controller [ProduitController]
![]() |
De controller [ProduitController] beheert de verwerking van de URL met betrekking tot de producten. De code ervan is vergelijkbaar met die van de controller [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;
}
// lokaal
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllShortProduits() {
// bovenliggend
Response<List<Produit>> response = super.getAllShortEntities();
// serialisatiefilters jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllLongProduits() {
// bovenliggend
Response<List<Produit>> response = super.getAllLongEntities();
// serialisatiefilters jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
// bovenliggend
Response<List<Produit>> response = super.getShortEntitiesById(request);
// serialisatiefilters jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
// bovenliggend
Response<List<Produit>> response = super.getShortEntitiesByName(request);
// serialisatiefilters jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
// bovenliggend
Response<List<Produit>> response = super.getLongEntitiesById(request);
// serialisatiefilters jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// antwoord
return response;
}
@RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
// bovenliggend
Response<List<Produit>> response = super.getLongEntitiesByName(request);
// serialisatiefilters jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// antwoord
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);
}
}
Alleen de methode [saveProduits] heeft een andere structuur dan de andere methoden:
@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
try {
// de verzonden waarde wordt opgehaald
String body = CharStreams.toString(request.getReader());
// deze wordt gedeserialiseerd
ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
});
// de producten worden opgeslagen
produits = daoProduit.saveEntities(produits);
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : produits) {
coreProduits.add(new CoreProduit(produit.getId()));
}
// het antwoord wordt teruggestuurd
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);
}
}
- regels 4-9: op basis van de ontvangen tekenreeks jSON wordt de lijst met [Produit]-waarden die moeten worden bewaard, gereconstrueerd. Aangezien de ontvangen tekenreeks jSON die van de korte versies van de producten is, is het veld [categorie] daarvan gelijk aan null. Ook hier heeft de laag DAO / JDBC deze informatie niet nodig;
- regel 11: de producten worden opgeslagen;
- regels 12-15: de lijst met [CoreProduit] die moeten worden geretourneerd, wordt samengesteld;
- regel 18: het antwoord wordt teruggestuurd; dit wordt geserialiseerd (impliciete serialisatie uitgevoerd door Spring MVC) door de mapper van regel 7, voordat het naar de externe client wordt verzonden (zie bespreking in paragraaf 17.3.7);
17.3.9. De uitvoeringsklasse van de webservice / jSON
![]() |
De klasse [Boot] is de uitvoerbare klasse van het project:
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);
}
}
- regel 10: de statische methode [SpringApplication.run] wordt uitgevoerd. De klasse [SpringApplication] is een klasse van het project [spring Boot] (regel 3). Er worden twee parameters aan doorgegeven:
- [AppConfig.class]: de klasse die de gehele applicatie configureert;
- [args]: de eventuele argumenten die worden doorgegeven aan de methode [main] op regel 9. Deze parameter wordt hier niet gebruikt;
Wanneer deze klasse wordt uitgevoerd, zijn de volgende logbestanden beschikbaar:
- regels 11-15: de beans die de filters jSON definiëren, worden gedetecteerd. Ze herdefiniëren beans met dezelfde namen die zijn aangetroffen in het configuratieproject van de laag JDBC;
- regels 17-18: de Tomcat-server wordt gestart, die de webservice /jSON zal uitvoeren;
- regels 19-21: de Spring-context MVC wordt geïnitialiseerd;
- regels 24-43: de blootgestelde URL-diensten worden gedetecteerd;
17.3.10. Testen van de webservice / jSON
Voor het uitvoeren van de tests gebruiken we de client [Advanced Rest Client] (zie paragraaf 23.11) om de URL-endpunten te benaderen die door de webservice / jSON worden blootgesteld (de webservice / jSON moet uiteraard worden gestart, evenals de SGBD). Om een gevulde database te krijgen, voeren we de uitvoeringsconfiguratie met de naam [spring-jdbc-generic-04-fillDataBase] uit, die de database vult met 5 categorieën en 10 producten:
![]() |
![]() |
- in [1-3] vragen we de URL en [/getAllLongCategories] op via een opdracht HTTP en GET;
We krijgen het volgende antwoord:
![]() |
- in [1], het verzoek HTTP van de client;
- in [2], het antwoord HTTP van de server;
- in [3] geeft de status [200 OK] aan dat de server het verzoek correct heeft verwerkt;
- in [4], het antwoord jSON van de server;
Het volledige antwoord jSON luidt als volgt:
{"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 betekent dat er geen fouten aan de serverzijde zijn opgetreden;
- exception: null betekent dat er geen foutmelding is;
- body: is de inhoud van het antwoord, in dit geval de lijst met categorieën en hun producten. Er zijn twee categorieën met elk 5 producten;
We gaan het product [produit15] toevoegen aan de categorie [categorie1]. Hiervoor gebruiken we de URL [/saveProduits], die wacht op de reeks jSON van de producten die moeten worden opgeslagen (toevoegen/wijzigen). Deze reeks ziet er als volgt uit:
[{"id":null,"version":null,"nom":"produit15","idCategorie":1881,"prix":111.0,"description":"desc15"}]}]
Het verzoek aan de webservice / jSON wordt als volgt uitgevoerd:
![]() |
- in [1], de gevraagde URL;
- in [2] wordt deze aangevraagd via een bewerking POST;
- in [3] is de reeks jSON verzonden;
- in [4] wordt aan de server aangegeven dat jSON naar de server wordt verzonden;
Het antwoord van de server is als volgt:
![]() |
- in [1] hebben we een lijst met [CoreProduit] en hun primaire sleutels ontvangen. Hier hebben we een lijst met één item ontvangen met de primaire sleutel van het product dat we zojuist in de database hebben ingevoerd;
Laten we nu de uitgebreide versie opvragen van de categorie met de naam [categorie[1]:
![]() |
- in [1], de opgevraagde URL;
- in [2], maken we er POST van;
- in [3,4] is de verzonden waarde een tekenreeks jSON. Deze vertegenwoordigt de lijst met namen van de categorieën waarvan we de lange versie willen;
We krijgen het volgende resultaat:
![]() |
- in [5] heeft de categorie [categorie[1]] nu een zesde product;
Laten we dit product nu verwijderen:
![]() |
- in [1], het gevraagde URL;
- in [2] maken we een POST;
- in [3-4] wordt een tekenreeks jSON verzonden die de lijst met primaire sleutels van de producten bevat die men wil verwijderen;
Het verkregen resultaat is als volgt:
![]() |
- [status:0] geeft aan dat het verwijderen goed is verlopen;
Laten we nu het product [produit[1,5]] opvragen om te controleren of het daadwerkelijk is verwijderd:
![]() |
We krijgen het volgende resultaat:
![]() |
- [status:0] geeft aan dat de bewerking zonder uitzonderingen is verlopen;
- [body:[0]] geeft aan dat [body] een lijst is met 0 elementen. De entiteit [produit[1,5]] is dus inderdaad verwijderd;
Alle bewerkingen met [GET] kunnen in een gewone webbrowser worden uitgevoerd:
![]() |
De lezer wordt uitgenodigd om de andere URL-bewerkingen van de webservice / jSON te testen.








































