Skip to content

17. Exposing a database on the web

17.1. Web service architecture / jSON

We will implement the following architecture:

  • in [1], the layers [DAO, [JPA], JDBC] are implemented by one of the 24 configurations presented in the previous chapters, particularly in paragraph 15;
  • the remote client’s [DAO] [3] layer implements the same interface as the [DAO] [1] layer, which allows us to use the same test layer as in the previous chapters. It is as if the [2-3] layers were transparent to the [4] layer;

We will rely on the following projects:

  1. the [sgbd-config-jdbc] project, which configures the JDBC layer of one of the six SGBD projects;
  2. the [sgbd-config-jpa-*] project, which configures the JPA layer of the SGBD selected for one of the three JPA implementations studied (Hibernate, EclipseLink, OpenJpa);
  3. the generic project [spring-jdbc-04], which implements the layer [DAO] [1];
  4. the generic project [spring-jpa-generic], which implements the layer [DAO] [2];
  5. the generic project [spring-webjson-server-jdbc-generic], which implements a web service based on the project [spring-jdbc-04];
  6. the generic project [spring-webjson-server-jpa-generic], which implements a web service based on the project [spring-jpa-generic];
  7. the generic client [spring-webjson-client-generic], which will be the single client for the 24 web service configurations;

17.2. Setting up the working environment

We will be working with the following elements:

  • SGBD MySQL 5.6.25;
  • JPA Hibernate implementation;

Import the following projects into STS:

  
  • The [spring-webjson-*] projects can be found in the [<exemples>\spring-database-generic\spring-webjson] folder;
  • Run [Alt-F5], then regenerate all of the above projects;

To verify that the working environment is installed correctly, proceed as follows:

  • launch the web service with the [spring-webjson-server-jpa-generic-hibernate] runtime configuration, which is based on a JPA / Hibernate implementation;

then:

  • launch the client for this web service using the [spring-webjson-client-generic] runtime configuration, which is a test of JUnit:

The test should pass:

  • In [1], stop the web service, then launch the web service with the [spring-webjson-server-jdbc-generic] runtime configuration, which is based on the JDBC implementation:

then start the client for this web service with the [spring-webjson-client-generic] runtime configuration:

The test should pass:

 

17.3. Web Service Implementation / jSON / JDBC

We will first examine the following architecture:

where the [DAO] [1] layer communicates directly with the JDBC layer of the SGBD.

17.3.1. The Eclipse project for the web service

The Eclipse project for the web service / jSON / JDBC is as follows:

  

This is a Maven project whose [pom.xml] file is as follows:


<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>
  • lines 11–15: the parent Maven project;
  • lines 24–28: the dependency on the [DAO / JDBC] layer implemented by the [spring-jdbc-generic-04] project;
  • lines 19–22: the dependency on the [spring-boot-starter-web] artifact. This artifact includes all the dependencies necessary for creating a web service / jSON. It also includes unnecessary libraries. A more precise configuration would therefore be necessary, but this configuration is useful for getting started.

The dependencies included in this configuration are as follows:

1
  • In [1], we can see that Eclipse has detected the dependency on the [spring-jdbc-generic-04] project archive;

The dependencies above are shared by both the [DAO] layer and the [web] layer.

17.3.2. Configuration of the [web] layer

The [web] layer is configured by two Spring configuration files:

  

17.3.2.1. The [WebConfig] class

The primary role of the [WebConfig] class is to configure:

  1. the Tomcat server on which the web service will be deployed;
  2. the jSON filters for serializing/deserializing the [Produit] and [Categorie] objects:

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);
    }
 
    // -------------------------------- filter configuration [json]
    ...
}
  1. line 25: the class is a Spring configuration class;
  2. line 26: the [@EnableWebMvc] annotation indicates that the web layer is implemented with Spring MVC. This will trigger implicit configurations that we won’t have to set up;
  3. lines 30–31: injection of the application’s Spring context;
  4. lines 33–37: definition of the [dispatcherServlet] bean, which, in Spring MVC applications, acts as [FrontController], whose role is to route requests from clients to the controller capable of handling them;
  5. lines 39–42: the web service servlet is registered along with the URL it handles. Here we have written [/*], which means all URLs;
  6. lines 44–47: definition of the [embeddedServletContainerFactory] bean, which specifies the web server to use. Here, it will be the Tomcat web server [http://tomcat.apache.org/]. The Jetty server [http://www.eclipse.org/jetty/] can also be used. Both are servers embedded in the Maven dependencies. When [Spring Boot] initiates the project launch, it automatically starts the web server specified in the configuration and deploys the service or web application to it;

The configuration of the jSON filters is done as follows:


package spring.webjson.server.config;
 
import java.util.List;
...
 
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
 
    // -------------------------------- layer configuration [web]
...
    // -------------------------------- filter configuration [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;
    }
}
  1. Line 8: The [WebConfig] class extends the [WebMvcConfigurerAdapter] class. The latter class configures the web application with default values. To customize this configuration, you must override certain methods of this class. Here, we want to override the [configureMessageConverters] method of the [22-26] class (note the @Override annotation), which defines a list of 'converters'. The web service /jSON and its client exchange lines of text. A converter is a tool capable of creating an object from a received text line (deserialization) and creating a text line from an object (serialization). Here, the text lines will be jSON strings. We will therefore refer to jSON serialization/deserialization;
  2. line 23: the [configureMessageConverters] method receives a list of converters as a parameter;
  3. lines 24–25: the jSON and [MappingJackson2HttpMessageConverter] converters from lines [14-20] are added to this list. This will enable exchanges between the client and the server;
  4. lines [14-20]: define a jSON converter implemented by the [MappingJackson2HttpMessageConverter] class. This class (line 10) will be found in the project’s Maven dependencies;
  5. lines [17-18]: a mapper jSON is created and assigned to the converter [MappingJackson2HttpMessageConverter];
  6. lines [29-32]: define the jSON mapper created on line 17 as a Spring bean. This places it in the Spring context and makes it available to be injected into other beans or used in the web application code;
  7. lines 34–41: define a filter jSON for the previous mapper jSON;
  8. line 35: the [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] annotation ensures that the bean defined here is not a singleton. Each time it is requested from the context, the [jsonMapperCategorieWithoutProduits] method will be re-executed. This is necessary here because we are defining four jSON filters. However, only one should be active at any given time. By giving the bean the scope [ConfigurableBeanFactory.SCOPE_PROTOTYPE], we ensure that the method will be re-executed and that the previous filter will be replaced by the new one;
  9. To understand these filters, remember that in the [DAO] layer:
    1. the entity [Produit] has been annotated with the annotation [jsonFilterProduit];
    2. the entity [Categorie] has been annotated with the annotation [jsonFilterCategorie];

Filters with these names must therefore be defined.

  1. lines [34-41]: define a filter named [jsonMapperShortCategorie] that provides the representation jSON of a category without its products;
  2. Lines [43-51]: define a filter named [jsonMapperLongCategorie] that provides the representation jSON of a category with its products;
  3. Lines [53-60]: define a filter named [jsonMapperShortProduit] that provides the representation jSON of a product without its category;
  4. Lines [62-70]: define a filter named [jsonMapperLongProduit] that provides the representation jSON of a product with its category;

17.3.2.2. The [AppConfig] class

The [AppConfig] class configures the entire application, i.e., the [web] and [DAO] layers:


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 {
 
}
  • line 7: the class is a Spring configuration class;
  • line 9: we import the beans from the [DAO / JDBC] layer as well as those defined by the [WebConfig] class. All beans from the [DAO] layer will therefore be available in the /jSON web application;
  • Line 8: specifies the packages where other Spring beans can be found;

17.3.3. The [ServerException] exception

  

Just as in the previous chapters, where the [DAO] layer threw an uncaught exception [DaoException], the [web] layer will throw an uncaught exception [ServerException]:


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);
    }
}
  • line 5: the class [ServerException] extends the class [UncheckedException] defined in the project configuring the layer JDBC (line 3);

17.3.4. The controllers

  

We will have two controllers here:

  • [CategorieController] will handle requests for categories;
  • [CategorieController] will handle requests for products;

The URL methods exposed by the controllers correspond one-to-one to the methods of the [DaoCategorie] and [DaoProduit] interfaces of the [DAO] layer:

As shown above:

  • The web method [deleteAllCategories] will call the method [deleteAllEntities] of the class [DaoCategorie];
  • The web method [getShortCategoriesById] will call the method [getShortEntitiesById] of the class [DaoCategorie];

The same applies to the products:

17.3.4.1. The URL exposed by the [CategorieController] controller

URL
Method

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

public Response<List<CoreCategorie>>
 saveCategories
(HttpServletRequest request)
====
The method receives the categories to be saved via a POST request.
Celles-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les catégories sont persistées par la méthode
[saveEntities] de la couche [DaoCategorie]. Seules les clés
 primaires des objets persistés (catégories / produits) sont
 returned to the client.

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

public Response<Void> deleteAllCategories()
====
The URL has no parameters. The categories are deleted by
 la méthode [deleteAllEntities] de la couche [DaoCategorie].

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

public Response<Void> deleteCategoriesById
(HttpServletRequest request)
====
The method receives the primary keys of the
 categories to be deleted via a POST request. These are accessible in
 l'objet [HttpServletRequest request]. Les catégories sont
 supprimées par la méthode [deleteEntitiesById] de la couche
 [DaoCategorie].

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

public Response<Void> deleteCategoriesByName
(HttpServletRequest request)
====
The method receives the names of the categories to be
supprimer. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories sont supprimées
 par la méthode [deleteEntitiesByname] de la couche
[DaoCategorie].

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

public Response<List<Categorie>> getAllShortCategories()
====
The URL has no parameters. The short categories are
obtenues par la méthode [getAllShortEntities] de la couche
 [DaoCategorie].

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

public Response<List<Categorie>> getAllLongCategories()
====
The URL has no parameters. The long categories are
 obtenues par la méthode [getAllLongEntities] de la couche
[DaoCategorie].

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

public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request)
====
The method receives the primary keys of the
 desired categories via a POST request. These are accessible in the
 [HttpServletRequest request]. Les catégories longues sont
 obtenues par la méthode [getLongEntitiesById] de la couche
 [DaoCategorie].

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

public Response<List<Categorie>> getLongCategoriesByName
(HttpServletRequest request)
====
The method receives the names of the desired categories via a POST request
 . These are accessible in the
 [HttpServletRequest request]. Les catégories longues sont
obtenues par la méthode [getLongEntitiesByName] de la couche
 [DaoCategorie].

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

public Response<List<Categorie>> getShortCategoriesByName
(HttpServletRequest request)
====
The method receives the names of the desired categories via a POST request
 . These are accessible in the
 [HttpServletRequest request]. Les catégories courtes sont
 obtenues par la méthode [getShortEntitiesByName] de la
 couche [DaoCategorie].

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

public Response<List<Categorie>> getShortCategoriesById
(HttpServletRequest request)
====
The method receives the primary keys of the
 desired categories via a POST request. These are accessible in the object
 [HttpServletRequest request]. Les catégories courtes sont
 obtenues par la méthode [getShortEntitiesById] de la couche
 [DaoCategorie].

17.3.4.2. The URL exposed by the [ProduitController]

URL
Method

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

public Response<List<CoreProduit>> saveProduits
(HttpServletRequest request)
====
The method receives the products to be saved via a POST request. These
ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits sont persistés par la méthode
 [saveEntities] de la couche [DaoProduit]. Seules les clés
 are returned to the client.

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

public Response<Void> deleteAllProduits()
====
The URL has no parameters. The products are deleted by the
 méthode [deleteAllEntities] de la couche [DaoProduit].

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

public Response<Void> deleteProduitsById
(HttpServletRequest request)
====
The method receives the primary keys of the products
 to be deleted via a POST request. These are accessible in the
 [HttpServletRequest request]. Les produits sont supprimés
 par la méthode [deleteEntitiesById] de la couche
[DaoProduit].

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

public Response<Void> deleteProduitsByName
(HttpServletRequest request)
====
The method receives the names of the products to be
 be deleted via a POST request. These are accessible in the
 [HttpServletRequest request]. Les produits sont supprimés
 par la méthode [deleteEntitiesByname] de la couche
 [DaoProduit].

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

public Response<List<Produit>> getAllShortProduits()
====
The URL has no parameters. The short categories are
 obtenues par la méthode [getAllShortEntities] de la couche
 [DaoProduit].

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

public Response<List<Produit>> getAllLongProduits()
====
The URL has no parameters. Long products are retrieved
 par la méthode [getAllLongEntities] de la couche
 [DaoProduit].

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

public Response<List<Produit>> getLongProduitsById
(HttpServletRequest request)
====
The method receives the primary keys of the desired products via a POST request
 . These are accessible in the
 [HttpServletRequest request]. Les produits longs sont
obtenus par la méthode [getLongEntitiesById] de la couche
 
 [DaoProduit].

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

public Response<List<Produit>> getLongProduitsByName
(HttpServletRequest request)
====
The method receives the names of the desired products via a POST request.
 Ceux-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits longs sont obtenus par la méthode
 [getLongEntitiesByName] de la couche [DaoProduit].

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

public Response<List<Produit>> getShortProduitsByName
(HttpServletRequest request)
====
The method receives the names of the desired products via a POST request.
 Ceux-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits courts sont obtenus par la méthode
[getShortEntitiesByName] de la couche [DaoProduit].

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

public Response<List<Produit>> getShortProduitsById
(HttpServletRequest request)
====
The method receives the primary keys of the desired products via a POST request
 . These are accessible in the
[HttpServletRequest request]. Les produits courts sont
obtenus par la méthode [getShortEntitiesById] de la couche
 [DaoProduit].

17.3.5. Generic implementation of the web service

  

The list of URL exposed by the web service shows that we offer the same types of URL to manage categories and products. Rather than writing two very similar controllers, we will have them derive from a class that will handle all the work common to both controllers. This will be the [AbstractController] class above. This class will implement the following [Iws] interface:


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();
 
    // particular entities - version short
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
 
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
 
    // particular entities - version long
    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);
}

This interface implements the methods of the [DAO] layer interface that will be used:


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();
 
    // particular entities - version short
    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);
 
    // particular entities - version long
    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);
}

The transition of the [IDao<T>] interface from the [DAO] layer to the [Iws<T>] interface of the web service followed these rules:

  • the methods of the [Iws<T>] interface will not throw an exception. If an exception occurs, it will be encapsulated in the [Response] object;
  • Parameter variants such as lines 45 and 47 ([Iterable<String> names, String... names]) are removed. The methods obtain their parameters from the client’s HTTP request of type [HttpServletRequest request];

All web service responses will be encapsulated in the following [Response] object:

  

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
...
}
  • line 4: the response encapsulates a type T;
  • line 12: the response of type T;
  • lines 7–10: a method may encounter an exception. In this case, it will return a response with:
    • line 8: status!=0;
    • line 10: an error message;

The [AbstractController] class is as follows:


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) {
    ...
    }
 
}

The methods are all implemented in the same way:

  • if they expect information, they retrieve it from the [HttpServletRequest request] object;
  • they call the method in the [DAO] layer that has the same name as they do;
  • they handle any exceptions that may occur, either in operation 1 (retrieving parameters) or in operation 2 (calling the [DAO] layer);

Let’s first examine how the [DAO] layer is injected into the [AbstractController] class:


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();
}

  1. line 1: the class is abstract and implements the generic interface [Iws<T>];
  2. lines 3-4: injection of the Spring context;
  3. line 7: the as-yet-unknown reference to the [DAO] layer to be used;
  4. line 9: the abstract method [getDao] that will return the reference to the [DAO] layer to be used. This method will be overridden by the child class, so it is the child class that will specify which [DAO] layer to use (DaoProduit or DaoCategorie);
  5. Line 11: The annotation [@PostConstruct] annotates a method to be executed when the object instantiation is complete. Once instantiation is complete, the Spring injections have been performed. The child class will then have obtained the reference to its [DAO] layer and can therefore pass it to its parent;

The [getShortEntitiesById] method is as follows:


    @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);
        }
}
  1. line 5: the value posted by the client will be a string jSON. This is where it is retrieved;
  2. lines 7–9: the string jSON contains the list of primary keys for the entities for which we want the short version;
  3. line 11: the method [DAO] of the same name is called. The response of type [List<T>] is encapsulated in a [Response] object;
  4. line 13: case where the [DAO] layer has thrown an exception;
  5. line 15: case of other exceptions, in particular the possible exception during deserialization of the jSON parameter, line 8;

The [getShortEntitiesByName] method is similar:


@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);
        }
}
  • lines 4-9: here, the parameter jSON is the list of category names for which we want the short version;

The [saveEntities] method has not been implemented because it is quite dependent on the nature of the entity to be persisted, [Categorie] or [Produit]. There is little code to refactor. This work is therefore left to the child classes.


        @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. The [CategorieController] controller

  

The [CategorieController] controller handles the processing of URL requests related to categories:


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);
    }
 
}
  • line 26: the class [CategorieController] extends the class [AbstractController];
  • line 25: the annotation [@RestController] makes the class a Spring component. This annotation also indicates that the class is a web service whose methods send their responses directly to the client in the jSON format;
  • lines 28–29: the reference to the [DAO] layer is injected here;
  • lines 31-34: redefinition of the [getDao] method, declared abstract in the parent class, whose purpose is to return a reference to the [DAO] layer to be used;

The methods are all built on the same model:

  1. delegation of processing to the parent class;
  2. initialization of the jSON mapper, which will serialize the response;
  3. sending the response;

Let’s take a look at the signature of a few URL methods:


@RequestMapping(value = "/getAllShortCategories", method
 = RequestMethod.GET)
- the URL [/getAllShortCategories] is called with a GET

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- URL [/getShortCategoriesById] is called with a POST. The posted value is the jSON string of the primary keys of the
desired categories;

@RequestMapping(value = "/getLongCategoriesByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- URL is called with
POST. The posted value is the jSON string containing the names of the
desired categories;

Now, let’s examine the [saveCategories] method, which does not follow the format of the other methods:


    @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);
        }
}
  1. line 1: the URL [/saveCategories] is accompanied by a posted value. This is the string jSON containing the long versions of the categories to be persisted;
  2. lines 5–10: the categories to be persisted are recreated from the string jSON. The link [produit.categorie] connecting a [Produit] to its [Catégorie] is null because in the long version of a [Categorie], each [Produit] is itself in its short version without its [categorie] field. This is not a problem, because the [DAO] layer implemented with JDBC does not need this information;
  3. line 12: the categories are persisted. The received list of categories has been enriched with the primary keys of the persisted elements, categories, and products. Nothing else has changed. Rather than returning the entire received list, which is costly, we will only return the primary keys of the elements in this list. To do this, we use the following classes [CoreCategorie] and [CoreProduit]:
  

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
    ...
}
  • line 8: the primary key of a product;
  • line 20: the primary keys of its products;

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
...
}
  • line 6: the primary key of a product;

Let's go back to the code for the [saveCategories] method:


...            
// 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);
...
  • lines 5–17: we construct the list of [CoreCategorie] that we will return to the remote client;
  • line 19: the response is returned and serialized as jSON;

17.3.7. jSON Filter Management

For each method of a controller, there are two points for jSON serialization/deserialization:

  1. deserialization of the posted value: this is handled explicitly here;
  2. serialization of the result: this is handled implicitly here;

Let’s start with the deserialization of the posted value 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>>() {
});
  1. line 8: retrieve a mapper configured to handle the jSON [jsonMapperLongCategorie] filter from the Spring context. Let’s go back to the definition of this mapper in the [WebConfig] configuration class:

// -------------------------------- filter configuration [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;
    }
 
  1. lines 32–40: the jSON [jsonMapperLongCategorie] mapper retrieved by the [CategorieController] class;
  2. line 35: this mapper is returned by the [jsonMapper] method in lines 18-21;
  3. lines 18–21: the [jsonMapper] method renders the jSON mapper from the [MappingJackson2HttpMessageConverter] converter in lines 3–9;

In other words, the mapper jSON retrieved by line 4 below in [CategorieController.saveCategories]:


            // 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>>() {
});

is the converter used by default by Spring MVC to deserialize the value posted by the client and serialize the result sent back to it. In the lines above, there was no implicit deserialization of the posted value. To do this, you would have had to write:


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

In this case, there would have been automatic deserialization of the posted value in the [categories] parameter. However, there was an issue with the [jsonFilterCategorie] filter applied to the [Categorie] entities. It needs to be configured. This is why we chose explicit deserialization (lines 4–5). The second point to note is that the mapper on line 4 (which is the one used by default by Spring MVC) is also suitable for serializing the result [Response<List<CoreCategorie>]. In fact, the [CoreCategorie] entity does not have a jSON filter. Therefore, there is no need to configure the jSON mapper obtained with an additional filter. In this case, the response sent to the client will be implicitly serialized.

17.3.8. The [ProduitController] controller

  

The [ProduitController] controller handles the processing of URL requests related to products. Its code is similar to that of the [CategorieController] controller:


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);
    }
 
}

Only the [saveProduits] method has a different structure from the other methods:


@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);
        }
}
  • lines 4–9: From the received string jSON, we reconstruct the list of [Produit] entries to be persisted. Since the received jSON string corresponds to the short versions of the products, the [categorie] field for these is null. Once again, the DAO / JDBC layer does not need this information;
  • line 11: the products are persisted;
  • lines 12–15: the list of [CoreProduit] to be rendered is constructed;
  • Line 18: The response to be serialized (implicit serialization performed by Spring MVC) is returned by the mapper on line 7 before being sent to the remote client (see discussion in section 17.3.7);

17.3.9. The web service execution class / jSON

  

The [Boot] class is the project’s executable class:


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);
    }
}
  • line 10: the static method [SpringApplication.run] is executed. The class [SpringApplication] is a class in the [spring Boot] project (line 3). Two parameters are passed to it:
    • [AppConfig.class]: the class that configures the entire application;
    • [args]: any arguments passed to the [main] method on line 9. This parameter is not used here;

When this class is executed, the following logs are generated:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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]
  • lines 11-15: the beans defining the jSON filters are discovered. They override beans with the same names discovered in the JDBC configuration project;
  • lines 17-18: the Tomcat server is started, which will run the web service /jSON;
  • lines 19–21: the Spring context MVC is initialized;
  • lines 24-43: the exposed URL services are discovered;

17.3.10. Testing the web service / jSON

To perform the tests, we use the client [Advanced Rest Client] (see section 23.11) to query the URL exposed by the web service / jSON (the web service / jSON must be started, as well as the SGBD, of course). To populate the database, we run the execution configuration named [spring-jdbc-generic-04-fillDataBase], which populates the database with 5 categories and 10 products:

 
  1. in [1-3], we request URL and [/getAllLongCategories] via a command for HTTP and GET;

We receive the following response:

  1. in [1], the client's request HTTP;
  2. in [2], the server's response HTTP;
  3. in [3], the status [200 OK] indicates that the server successfully processed the request;
  4. in [4], the server's response jSON;

The complete response jSON is as follows:


{"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]"}]}]}
  1. status:0 means there were no server-side errors;
  2. exception: null means there is no error message;
  3. body: is the body of the response, in this case the list of categories with their products. There are two categories, each with 5 products;

We are going to add the product [produit15] to the category [categorie1]. To do this, we will use the URL [/saveProduits] method, which expects the jSON array of products to be persisted (insertion/update). This array will be as follows:

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

The request to the web service /jSON is made as follows:

  1. in [1], the requested URL;
  2. in [2], it is requested via a POST operation;
  3. in [3], the jSON string is posted;
  4. in [4], the server is notified that jSON will be sent to it;

The server's response is as follows:

  1. in [1], we obtained a list of [CoreProduit] entries with their primary keys. Here, we obtained a list containing a single entry with the primary key of the product we just inserted into the database;

Now, let’s request the long version for the category named [categorie[1]:

  1. in [1], the requested URL;
  2. in [2], we create a POST;
  3. in [3,4], the posted value is a string jSON. This represents the list of category names for which we want the long version;

We get the following result:

  1. in [5], the category [categorie[1]] now has a sixth product;

Now let’s remove this product:

  1. In [1], the requested URL;
  2. In [2], we create a POST;
  3. in [3-4], we post a string jSON representing the list of primary keys of the products we want to delete;

The result is as follows:

 
  1. [status:0] indicates that the deletion was successful;

Now, let’s query the product [produit[1,5]] to verify that it has indeed been deleted:

 

We obtain the following result:

 
  • [status:0] indicates that the operation completed without any exceptions;
  • [body:[0]] indicates that [body] is a list with 0 elements. The entity [produit[1,5]] has therefore been successfully deleted;

All [GET] operations can be performed in a standard web browser:

Readers are invited to test the other URL operations of the web service / jSON.