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:
- the [sgbd-config-jdbc] project, which configures the JDBC layer of one of the six SGBD projects;
- 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);
- the generic project [spring-jdbc-04], which implements the layer [DAO] [1];
- the generic project [spring-jpa-generic], which implements the layer [DAO] [2];
- the generic project [spring-webjson-server-jdbc-generic], which implements a web service based on the project [spring-jdbc-04];
- the generic project [spring-webjson-server-jpa-generic], which implements a web service based on the project [spring-jpa-generic];
- 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:
- the Tomcat server on which the web service will be deployed;
- 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]
...
}
- line 25: the class is a Spring configuration class;
- 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;
- lines 30–31: injection of the application’s Spring context;
- 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;
- lines 39–42: the web service servlet is registered along with the URL it handles. Here we have written [/*], which means all URLs;
- 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;
}
}
- 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;
- line 23: the [configureMessageConverters] method receives a list of converters as a parameter;
- 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;
- 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;
- lines [17-18]: a mapper jSON is created and assigned to the converter [MappingJackson2HttpMessageConverter];
- 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;
- lines 34–41: define a filter jSON for the previous mapper jSON;
- 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;
- To understand these filters, remember that in the [DAO] layer:
- the entity [Produit] has been annotated with the annotation [jsonFilterProduit];
- the entity [Categorie] has been annotated with the annotation [jsonFilterCategorie];
Filters with these names must therefore be defined.
- lines [34-41]: define a filter named [jsonMapperShortCategorie] that provides the representation jSON of a category without its products;
- Lines [43-51]: define a filter named [jsonMapperLongCategorie] that provides the representation jSON of a category with its products;
- Lines [53-60]: define a filter named [jsonMapperShortProduit] that provides the representation jSON of a product without its category;
- 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
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.4.2. The URL exposed by the [ProduitController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
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();
}
- line 1: the class is abstract and implements the generic interface [Iws<T>];
- lines 3-4: injection of the Spring context;
- line 7: the as-yet-unknown reference to the [DAO] layer to be used;
- 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);
- 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);
}
}
- line 5: the value posted by the client will be a string jSON. This is where it is retrieved;
- lines 7–9: the string jSON contains the list of primary keys for the entities for which we want the short version;
- line 11: the method [DAO] of the same name is called. The response of type [List<T>] is encapsulated in a [Response] object;
- line 13: case where the [DAO] layer has thrown an exception;
- 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:
- delegation of processing to the parent class;
- initialization of the jSON mapper, which will serialize the response;
- sending the response;
Let’s take a look at the signature of a few URL methods:
| - the URL [/getAllShortCategories] is called with a GET |
| - URL [/getShortCategoriesById] is called with a POST. The posted value is the jSON string of the primary keys of the desired categories; |
| - 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);
}
}
- 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;
- 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;
- 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:
- deserialization of the posted value: this is handled explicitly here;
- 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>>() {
});
- 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;
}
- lines 32–40: the jSON [jsonMapperLongCategorie] mapper retrieved by the [CategorieController] class;
- line 35: this mapper is returned by the [jsonMapper] method in lines 18-21;
- 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:
![]() |
![]() |
- in [1-3], we request URL and [/getAllLongCategories] via a command for HTTP and GET;
We receive the following response:
![]() |
- in [1], the client's request HTTP;
- in [2], the server's response HTTP;
- in [3], the status [200 OK] indicates that the server successfully processed the request;
- 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]"}]}]}
- status:0 means there were no server-side errors;
- exception: null means there is no error message;
- 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:
![]() |
- in [1], the requested URL;
- in [2], it is requested via a POST operation;
- in [3], the jSON string is posted;
- in [4], the server is notified that jSON will be sent to it;
The server's response is as follows:
![]() |
- 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]:
![]() |
- in [1], the requested URL;
- in [2], we create a POST;
- 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:
![]() |
- in [5], the category [categorie[1]] now has a sixth product;
Now let’s remove this product:
![]() |
- In [1], the requested URL;
- In [2], we create a POST;
- 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:
![]() |
- [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.








































